@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
6
6
use std:: collections:: HashMap ;
7
7
use std:: result:: Result as StdResult ;
8
8
use std:: sync:: Mutex ;
9
- use std:: time:: { Duration , Instant , SystemTime , UNIX_EPOCH } ;
9
+ use std:: time:: { Duration , Instant } ;
10
10
11
11
// https://auth0.com/docs/jwks
12
12
#[ derive( Debug , Serialize , Deserialize ) ]
@@ -73,175 +73,6 @@ impl EqCheck {
73
73
}
74
74
}
75
75
76
- // https://infosec.mozilla.org/guidelines/iam/openid_connect#session-handling
77
- const MOZ_SESSION_TIMEOUT : Duration = Duration :: from_secs ( 60 * 15 ) ;
78
- const MOZ_USERINFO_ENDPOINT : & str = "https://auth.mozilla.auth0.com/userinfo" ;
79
-
80
- /// Mozilla-specific check by forwarding the token onto the auth0 userinfo endpoint
81
- pub struct MozillaCheck {
82
- // token, token_expiry
83
- auth_cache : Mutex < HashMap < String , Instant > > ,
84
- client : reqwest:: blocking:: Client ,
85
- required_groups : Vec < String > ,
86
- }
87
-
88
- impl ClientAuthCheck for MozillaCheck {
89
- fn check ( & self , token : & str ) -> StdResult < ( ) , ClientVisibleMsg > {
90
- self . check_mozilla ( token) . map_err ( |e| {
91
- warn ! ( "Mozilla token validation failed: {}" , e) ;
92
- ClientVisibleMsg :: from_nonsensitive (
93
- "Failed to validate Mozilla OAuth token, run sccache --dist-auth" . to_owned ( ) ,
94
- )
95
- } )
96
- }
97
- }
98
-
99
- impl MozillaCheck {
100
- pub fn new ( required_groups : Vec < String > ) -> Self {
101
- Self {
102
- auth_cache : Mutex :: new ( HashMap :: new ( ) ) ,
103
- client : new_reqwest_blocking_client ( ) ,
104
- required_groups,
105
- }
106
- }
107
-
108
- fn check_mozilla ( & self , token : & str ) -> Result < ( ) > {
109
- // azp == client_id
110
- // {
111
- // "iss": "https://auth.mozilla.auth0.com/",
112
- // "sub": "ad|Mozilla-LDAP|asayers",
113
- // "aud": [
114
- // "sccache",
115
- // "https://auth.mozilla.auth0.com/userinfo"
116
- // ],
117
- // "iat": 1541103283,
118
- // "exp": 1541708083,
119
- // "azp": "F1VVD6nRTckSVrviMRaOdLBWIk1AvHYo",
120
- // "scope": "openid"
121
- // }
122
- #[ derive( Deserialize ) ]
123
- struct MozillaToken {
124
- exp : u64 ,
125
- sub : String ,
126
- }
127
- let mut validation = jwt:: Validation :: default ( ) ;
128
- validation. validate_exp = false ;
129
- validation. validate_nbf = false ;
130
- // We don't really do any validation here (just forwarding on) so it's ok to unsafely decode
131
- validation. insecure_disable_signature_validation ( ) ;
132
- let dummy_key = jwt:: DecodingKey :: from_secret ( b"secret" ) ;
133
- let insecure_token = jwt:: decode :: < MozillaToken > ( token, & dummy_key, & validation)
134
- . context ( "Unable to decode jwt" ) ?;
135
- let user = insecure_token. claims . sub ;
136
- trace ! ( "Validating token for user {} with mozilla" , user) ;
137
- if UNIX_EPOCH + Duration :: from_secs ( insecure_token. claims . exp ) < SystemTime :: now ( ) {
138
- bail ! ( "JWT expired" )
139
- }
140
-
141
- // If the token is cached and not expired, return it
142
- let mut auth_cache = self . auth_cache . lock ( ) . unwrap ( ) ;
143
- if let Some ( cached_at) = auth_cache. get ( token) {
144
- if cached_at. elapsed ( ) < MOZ_SESSION_TIMEOUT {
145
- return Ok ( ( ) ) ;
146
- }
147
- }
148
- auth_cache. remove ( token) ;
149
-
150
- debug ! ( "User {} not in cache, validating via auth0 endpoint" , user) ;
151
- // Retrieve the groups from the auth0 /userinfo endpoint, which Mozilla rules populate with groups
152
- // https://github.com/mozilla-iam/auth0-deploy/blob/6889f1dde12b84af50bb4b2e2f00d5e80d5be33f/rules/CIS-Claims-fixups.js#L158-L168
153
- let url = reqwest:: Url :: parse ( MOZ_USERINFO_ENDPOINT )
154
- . expect ( "Failed to parse MOZ_USERINFO_ENDPOINT" ) ;
155
-
156
- let res = self
157
- . client
158
- . get ( url. clone ( ) )
159
- . bearer_auth ( token)
160
- . send ( )
161
- . context ( "Failed to make request to mozilla userinfo" ) ?;
162
- let status = res. status ( ) ;
163
- let res_text = res
164
- . text ( )
165
- . context ( "Failed to interpret response from mozilla userinfo as string" ) ?;
166
- if !status. is_success ( ) {
167
- bail ! ( "JWT forwarded to {} returned {}: {}" , url, status, res_text)
168
- }
169
-
170
- // The API didn't return a HTTP error code, let's check the response
171
- check_mozilla_profile ( & user, & self . required_groups , & res_text)
172
- . with_context ( || format ! ( "Validation of the user profile failed for {}" , user) ) ?;
173
-
174
- // Validation success, cache the token
175
- debug ! ( "Validation for user {} succeeded, caching" , user) ;
176
- auth_cache. insert ( token. to_owned ( ) , Instant :: now ( ) ) ;
177
- Ok ( ( ) )
178
- }
179
- }
180
-
181
- fn check_mozilla_profile ( user : & str , required_groups : & [ String ] , profile : & str ) -> Result < ( ) > {
182
- #[ derive( Deserialize ) ]
183
- struct UserInfo {
184
- sub : String ,
185
- #[ serde( rename = "https://sso.mozilla.com/claim/groups" ) ]
186
- groups : Vec < String > ,
187
- }
188
- let profile: UserInfo = serde_json:: from_str ( profile)
189
- . with_context ( || format ! ( "Could not parse profile: {}" , profile) ) ?;
190
- if user != profile. sub {
191
- bail ! (
192
- "User {} retrieved in profile is different to desired user {}" ,
193
- profile. sub,
194
- user
195
- )
196
- }
197
- for group in required_groups. iter ( ) {
198
- if !profile. groups . contains ( group) {
199
- bail ! ( "User {} is not a member of required group {}" , user, group)
200
- }
201
- }
202
- Ok ( ( ) )
203
- }
204
-
205
- #[ test]
206
- fn test_auth_verify_check_mozilla_profile ( ) {
207
- // A successful response
208
- let profile = r#"{
209
- "sub": "ad|Mozilla-LDAP|asayers",
210
- "https://sso.mozilla.com/claim/groups": [
211
- "everyone",
212
- "hris_dept_firefox",
213
- "hris_individual_contributor",
214
- "hris_nonmanagers",
215
- "hris_is_staff",
216
- "hris_workertype_contractor"
217
- ],
218
- "https://sso.mozilla.com/claim/README_FIRST": "Please refer to https://github.com/mozilla-iam/person-api in order to query Mozilla IAM CIS user profile data"
219
- }"# ;
220
-
221
- // If the user has been deactivated since the token was issued. Note this may be partnered with an error code
222
- // response so may never reach validation
223
- let profile_fail = r#"{
224
- "error": "unauthorized",
225
- "error_description": "user is blocked"
226
- }"# ;
227
-
228
- assert ! ( check_mozilla_profile(
229
- "ad|Mozilla-LDAP|asayers" ,
230
- & [ "hris_dept_firefox" . to_owned( ) ] ,
231
- profile,
232
- )
233
- . is_ok( ) ) ;
234
- assert ! ( check_mozilla_profile( "ad|Mozilla-LDAP|asayers" , & [ ] , profile) . is_ok( ) ) ;
235
- assert ! ( check_mozilla_profile(
236
- "ad|Mozilla-LDAP|asayers" ,
237
- & [ "hris_the_ceo" . to_owned( ) ] ,
238
- profile,
239
- )
240
- . is_err( ) ) ;
241
-
242
- assert ! ( check_mozilla_profile( "ad|Mozilla-LDAP|asayers" , & [ ] , profile_fail) . is_err( ) ) ;
243
- }
244
-
245
76
// Don't check a token is valid (it may not even be a JWT) just forward it to
246
77
// an API and check for success
247
78
pub struct ProxyTokenCheck {
0 commit comments