1
1
use crate :: controllers;
2
2
use crate :: db:: RequestTransaction ;
3
3
use crate :: middleware:: log_request;
4
+ use crate :: models:: token:: { CrateScope , EndpointScope } ;
4
5
use crate :: models:: { ApiToken , User } ;
5
6
use crate :: util:: errors:: {
6
7
account_locked, forbidden, internal, AppError , AppResult , InsecurelyGeneratedTokenRevoked ,
@@ -13,17 +14,43 @@ use http::header;
13
14
#[ derive( Debug , Clone ) ]
14
15
pub struct AuthCheck {
15
16
allow_token : bool ,
17
+ endpoint_scope : Option < EndpointScope > ,
18
+ crate_name : Option < String > ,
16
19
}
17
20
18
21
impl AuthCheck {
19
22
#[ must_use]
20
23
pub fn default ( ) -> Self {
21
- Self { allow_token : true }
24
+ Self {
25
+ allow_token : true ,
26
+ endpoint_scope : None ,
27
+ crate_name : None ,
28
+ }
22
29
}
23
30
24
31
#[ must_use]
25
32
pub fn only_cookie ( ) -> Self {
26
- Self { allow_token : false }
33
+ Self {
34
+ allow_token : false ,
35
+ endpoint_scope : None ,
36
+ crate_name : None ,
37
+ }
38
+ }
39
+
40
+ pub fn with_endpoint_scope ( & self , endpoint_scope : EndpointScope ) -> Self {
41
+ Self {
42
+ allow_token : self . allow_token ,
43
+ endpoint_scope : Some ( endpoint_scope) ,
44
+ crate_name : self . crate_name . clone ( ) ,
45
+ }
46
+ }
47
+
48
+ pub fn for_crate ( & self , crate_name : & str ) -> Self {
49
+ Self {
50
+ allow_token : self . allow_token ,
51
+ endpoint_scope : self . endpoint_scope ,
52
+ crate_name : Some ( crate_name. to_string ( ) ) ,
53
+ }
27
54
}
28
55
29
56
pub fn check ( & self , request : & dyn RequestExt ) -> AppResult < AuthenticatedUser > {
@@ -47,13 +74,57 @@ impl AuthCheck {
47
74
log_request:: add_custom_metadata ( "tokenid" , id) ;
48
75
}
49
76
50
- if !self . allow_token && auth. token . is_some ( ) {
51
- let error_message = "API Token authentication was explicitly disallowed for this API" ;
52
- return Err ( internal ( error_message) . chain ( forbidden ( ) ) ) ;
77
+ if let Some ( ref token) = auth. token {
78
+ if !self . allow_token {
79
+ let error_message =
80
+ "API Token authentication was explicitly disallowed for this API" ;
81
+ return Err ( internal ( error_message) . chain ( forbidden ( ) ) ) ;
82
+ }
83
+
84
+ if !self . endpoint_scope_matches ( token. endpoint_scopes . as_ref ( ) ) {
85
+ let error_message = "Endpoint scope mismatch" ;
86
+ return Err ( internal ( error_message) . chain ( forbidden ( ) ) ) ;
87
+ }
88
+
89
+ if !self . crate_scope_matches ( token. crate_scopes . as_ref ( ) ) {
90
+ let error_message = "Crate scope mismatch" ;
91
+ return Err ( internal ( error_message) . chain ( forbidden ( ) ) ) ;
92
+ }
53
93
}
54
94
55
95
Ok ( auth)
56
96
}
97
+
98
+ fn endpoint_scope_matches ( & self , token_scopes : Option < & Vec < EndpointScope > > ) -> bool {
99
+ match ( & token_scopes, & self . endpoint_scope ) {
100
+ // The token is a legacy token.
101
+ ( None , _) => true ,
102
+
103
+ // The token is NOT a legacy token, and the endpoint only allows legacy tokens.
104
+ ( Some ( _) , None ) => false ,
105
+
106
+ // The token is NOT a legacy token, and the endpoint allows a certain endpoint scope or a legacy token.
107
+ ( Some ( token_scopes) , Some ( endpoint_scope) ) => token_scopes. contains ( endpoint_scope) ,
108
+ }
109
+ }
110
+
111
+ fn crate_scope_matches ( & self , token_scopes : Option < & Vec < CrateScope > > ) -> bool {
112
+ match ( & token_scopes, & self . crate_name ) {
113
+ // The token is a legacy token.
114
+ ( None , _) => true ,
115
+
116
+ // The token does not have any crate scopes.
117
+ ( Some ( token_scopes) , _) if token_scopes. is_empty ( ) => true ,
118
+
119
+ // The token has crate scopes, but the endpoint does not deal with crates.
120
+ ( Some ( _) , None ) => false ,
121
+
122
+ // The token is NOT a legacy token, and the endpoint allows a certain endpoint scope or a legacy token.
123
+ ( Some ( token_scopes) , Some ( crate_name) ) => token_scopes
124
+ . iter ( )
125
+ . any ( |token_scope| token_scope. matches ( crate_name) ) ,
126
+ }
127
+ }
57
128
}
58
129
59
130
#[ derive( Debug ) ]
@@ -120,3 +191,103 @@ fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
120
191
// Unable to authenticate the user
121
192
return Err ( internal ( "no cookie session or auth header found" ) . chain ( forbidden ( ) ) ) ;
122
193
}
194
+
195
+ #[ cfg( test) ]
196
+ mod tests {
197
+ use super :: * ;
198
+
199
+ fn cs ( scope : & str ) -> CrateScope {
200
+ CrateScope :: try_from ( scope) . unwrap ( )
201
+ }
202
+
203
+ #[ test]
204
+ fn regular_endpoint ( ) {
205
+ let auth_check = AuthCheck :: default ( ) ;
206
+
207
+ assert ! ( auth_check. endpoint_scope_matches( None ) ) ;
208
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishNew ] ) ) ) ;
209
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishUpdate ] ) ) ) ;
210
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: Yank ] ) ) ) ;
211
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: ChangeOwners ] ) ) ) ;
212
+
213
+ assert ! ( auth_check. crate_scope_matches( None ) ) ;
214
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-console" ) ] ) ) ) ;
215
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-*" ) ] ) ) ) ;
216
+ }
217
+
218
+ #[ test]
219
+ fn publish_new_endpoint ( ) {
220
+ let auth_check = AuthCheck :: default ( )
221
+ . with_endpoint_scope ( EndpointScope :: PublishNew )
222
+ . for_crate ( "tokio-console" ) ;
223
+
224
+ assert ! ( auth_check. endpoint_scope_matches( None ) ) ;
225
+ assert ! ( auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishNew ] ) ) ) ;
226
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishUpdate ] ) ) ) ;
227
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: Yank ] ) ) ) ;
228
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: ChangeOwners ] ) ) ) ;
229
+
230
+ assert ! ( auth_check. crate_scope_matches( None ) ) ;
231
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-console" ) ] ) ) ) ;
232
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-*" ) ] ) ) ) ;
233
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "anyhow" ) ] ) ) ) ;
234
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "actix-*" ) ] ) ) ) ;
235
+ }
236
+
237
+ #[ test]
238
+ fn publish_update_endpoint ( ) {
239
+ let auth_check = AuthCheck :: default ( )
240
+ . with_endpoint_scope ( EndpointScope :: PublishUpdate )
241
+ . for_crate ( "tokio-console" ) ;
242
+
243
+ assert ! ( auth_check. endpoint_scope_matches( None ) ) ;
244
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishNew ] ) ) ) ;
245
+ assert ! ( auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishUpdate ] ) ) ) ;
246
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: Yank ] ) ) ) ;
247
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: ChangeOwners ] ) ) ) ;
248
+
249
+ assert ! ( auth_check. crate_scope_matches( None ) ) ;
250
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-console" ) ] ) ) ) ;
251
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-*" ) ] ) ) ) ;
252
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "anyhow" ) ] ) ) ) ;
253
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "actix-*" ) ] ) ) ) ;
254
+ }
255
+
256
+ #[ test]
257
+ fn yank_endpoint ( ) {
258
+ let auth_check = AuthCheck :: default ( )
259
+ . with_endpoint_scope ( EndpointScope :: Yank )
260
+ . for_crate ( "tokio-console" ) ;
261
+
262
+ assert ! ( auth_check. endpoint_scope_matches( None ) ) ;
263
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishNew ] ) ) ) ;
264
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishUpdate ] ) ) ) ;
265
+ assert ! ( auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: Yank ] ) ) ) ;
266
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: ChangeOwners ] ) ) ) ;
267
+
268
+ assert ! ( auth_check. crate_scope_matches( None ) ) ;
269
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-console" ) ] ) ) ) ;
270
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-*" ) ] ) ) ) ;
271
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "anyhow" ) ] ) ) ) ;
272
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "actix-*" ) ] ) ) ) ;
273
+ }
274
+
275
+ #[ test]
276
+ fn owner_change_endpoint ( ) {
277
+ let auth_check = AuthCheck :: default ( )
278
+ . with_endpoint_scope ( EndpointScope :: ChangeOwners )
279
+ . for_crate ( "tokio-console" ) ;
280
+
281
+ assert ! ( auth_check. endpoint_scope_matches( None ) ) ;
282
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishNew ] ) ) ) ;
283
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: PublishUpdate ] ) ) ) ;
284
+ assert ! ( !auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: Yank ] ) ) ) ;
285
+ assert ! ( auth_check. endpoint_scope_matches( Some ( & vec![ EndpointScope :: ChangeOwners ] ) ) ) ;
286
+
287
+ assert ! ( auth_check. crate_scope_matches( None ) ) ;
288
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-console" ) ] ) ) ) ;
289
+ assert ! ( auth_check. crate_scope_matches( Some ( & vec![ cs( "tokio-*" ) ] ) ) ) ;
290
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "anyhow" ) ] ) ) ) ;
291
+ assert ! ( !auth_check. crate_scope_matches( Some ( & vec![ cs( "actix-*" ) ] ) ) ) ;
292
+ }
293
+ }
0 commit comments