@@ -16,8 +16,12 @@ use axum_extra::TypedHeader;
1616use headers:: { Authorization , authorization:: Bearer } ;
1717use hyper:: StatusCode ;
1818use mas_axum_utils:: record_error;
19- use mas_data_model:: { BoxClock , Session , User } ;
19+ use mas_data_model:: {
20+ BoxClock , Session , TokenFormatError , TokenType , User ,
21+ personal:: session:: { PersonalSession , PersonalSessionOwner } ,
22+ } ;
2023use mas_storage:: { BoxRepository , RepositoryError } ;
24+ use oauth2_types:: scope:: Scope ;
2125use ulid:: Ulid ;
2226
2327use super :: response:: ErrorResponse ;
@@ -41,6 +45,10 @@ pub enum Rejection {
4145 #[ error( "Invalid repository operation" ) ]
4246 Repository ( #[ from] RepositoryError ) ,
4347
48+ /// The access token was not of the correct type for the Admin API
49+ #[ error( "Invalid type of access token" ) ]
50+ InvalidAccessTokenType ( #[ from] Option < TokenFormatError > ) ,
51+
4452 /// The access token could not be found in the database
4553 #[ error( "Unknown access token" ) ]
4654 UnknownAccessToken ,
@@ -90,7 +98,8 @@ impl IntoResponse for Rejection {
9098 | Rejection :: TokenExpired
9199 | Rejection :: SessionRevoked
92100 | Rejection :: UserLocked
93- | Rejection :: MissingScope => StatusCode :: UNAUTHORIZED ,
101+ | Rejection :: MissingScope
102+ | Rejection :: InvalidAccessTokenType ( _) => StatusCode :: UNAUTHORIZED ,
94103
95104 Rejection :: RepositorySetup ( _)
96105 | Rejection :: Repository ( _)
@@ -113,7 +122,7 @@ pub struct CallContext {
113122 pub repo : BoxRepository ,
114123 pub clock : BoxClock ,
115124 pub user : Option < User > ,
116- pub session : Session ,
125+ pub session : CallerSession ,
117126}
118127
119128impl < S > FromRequestParts < S > for CallContext
@@ -154,56 +163,126 @@ where
154163 } ) ?;
155164
156165 let token = token. token ( ) ;
166+ let token_type = TokenType :: check ( token) ?;
167+
168+ let session = match token_type {
169+ TokenType :: AccessToken => {
170+ // Look for the access token in the database
171+ let token = repo
172+ . oauth2_access_token ( )
173+ . find_by_token ( token)
174+ . await ?
175+ . ok_or ( Rejection :: UnknownAccessToken ) ?;
176+
177+ // Look for the associated session in the database
178+ let session = repo
179+ . oauth2_session ( )
180+ . lookup ( token. session_id )
181+ . await ?
182+ . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
183+
184+ if !session. is_valid ( ) {
185+ return Err ( Rejection :: SessionRevoked ) ;
186+ }
187+
188+ if !token. is_valid ( clock. now ( ) ) {
189+ return Err ( Rejection :: TokenExpired ) ;
190+ }
157191
158- // Look for the access token in the database
159- let token = repo
160- . oauth2_access_token ( )
161- . find_by_token ( token)
162- . await ?
163- . ok_or ( Rejection :: UnknownAccessToken ) ?;
164-
165- // Look for the associated session in the database
166- let session = repo
167- . oauth2_session ( )
168- . lookup ( token. session_id )
169- . await ?
170- . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
171-
172- // Record the activity on the session
173- activity_tracker
174- . record_oauth2_session ( & clock, & session)
175- . await ;
192+ // Record the activity on the session
193+ activity_tracker
194+ . record_oauth2_session ( & clock, & session)
195+ . await ;
196+
197+ CallerSession :: OAuth2Session ( session)
198+ }
199+ TokenType :: PersonalAccessToken => {
200+ // Look for the access token in the database
201+ let token = repo
202+ . personal_access_token ( )
203+ . find_by_token ( token)
204+ . await ?
205+ . ok_or ( Rejection :: UnknownAccessToken ) ?;
206+
207+ // Look for the associated session in the database
208+ let session = repo
209+ . personal_session ( )
210+ . lookup ( token. session_id )
211+ . await ?
212+ . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
213+
214+ if !session. is_valid ( ) {
215+ return Err ( Rejection :: SessionRevoked ) ;
216+ }
217+
218+ if !token. is_valid ( clock. now ( ) ) {
219+ return Err ( Rejection :: TokenExpired ) ;
220+ }
221+
222+ // Check the validity of the owner of the personal session
223+ match session. owner {
224+ PersonalSessionOwner :: User ( owner_user_id) => {
225+ let owner_user = repo
226+ . user ( )
227+ . lookup ( owner_user_id)
228+ . await ?
229+ . ok_or_else ( || Rejection :: LoadUser ( owner_user_id) ) ?;
230+ if !owner_user. is_valid ( ) {
231+ return Err ( Rejection :: UserLocked ) ;
232+ }
233+ }
234+ PersonalSessionOwner :: OAuth2Client ( _) => {
235+ // nop: Client owners are always valid
236+ }
237+ }
238+
239+ // Record the activity on the session
240+ activity_tracker
241+ . record_personal_session ( & clock, & session)
242+ . await ;
243+
244+ CallerSession :: PersonalSession ( session)
245+ }
246+ _other => {
247+ return Err ( Rejection :: InvalidAccessTokenType ( None ) ) ;
248+ }
249+ } ;
176250
177251 // Load the user if there is one
178- let user = if let Some ( user_id) = session. user_id {
252+ let user = if let Some ( user_id) = session. user_id ( ) {
179253 let user = repo
180254 . user ( )
181255 . lookup ( user_id)
182256 . await ?
183257 . ok_or_else ( || Rejection :: LoadUser ( user_id) ) ?;
258+
259+ match session {
260+ CallerSession :: OAuth2Session ( _) => {
261+ // For OAuth2 sessions: check that the user is valid enough
262+ // to be a user.
263+ if !user. is_valid ( ) {
264+ return Err ( Rejection :: UserLocked ) ;
265+ }
266+ }
267+ CallerSession :: PersonalSession ( _) => {
268+ // For personal sessions: check that the actor is valid enough
269+ // to be an actor.
270+ if !user. is_valid_actor ( ) {
271+ return Err ( Rejection :: UserLocked ) ;
272+ }
273+ }
274+ }
275+
184276 Some ( user)
185277 } else {
278+ // Double check we're not using a PersonalSession
279+ assert ! ( matches!( session, CallerSession :: OAuth2Session ( _) ) ) ;
186280 None
187281 } ;
188282
189- // If there is a user for this session, check that it is not locked
190- if let Some ( user) = & user
191- && !user. is_valid ( )
192- {
193- return Err ( Rejection :: UserLocked ) ;
194- }
195-
196- if !session. is_valid ( ) {
197- return Err ( Rejection :: SessionRevoked ) ;
198- }
199-
200- if !token. is_valid ( clock. now ( ) ) {
201- return Err ( Rejection :: TokenExpired ) ;
202- }
203-
204283 // For now, we only check that the session has the admin scope
205284 // Later we might want to check other route-specific scopes
206- if !session. scope . contains ( "urn:mas:admin" ) {
285+ if !session. scope ( ) . contains ( "urn:mas:admin" ) {
207286 return Err ( Rejection :: MissingScope ) ;
208287 }
209288
@@ -215,3 +294,26 @@ where
215294 } )
216295 }
217296}
297+
298+ /// The session representing the caller of the Admin API;
299+ /// could either be an OAuth session or a personal session.
300+ pub enum CallerSession {
301+ OAuth2Session ( Session ) ,
302+ PersonalSession ( PersonalSession ) ,
303+ }
304+
305+ impl CallerSession {
306+ pub fn scope ( & self ) -> & Scope {
307+ match self {
308+ CallerSession :: OAuth2Session ( session) => & session. scope ,
309+ CallerSession :: PersonalSession ( session) => & session. scope ,
310+ }
311+ }
312+
313+ pub fn user_id ( & self ) -> Option < Ulid > {
314+ match self {
315+ CallerSession :: OAuth2Session ( session) => session. user_id ,
316+ CallerSession :: PersonalSession ( session) => Some ( session. actor_user_id ) ,
317+ }
318+ }
319+ }
0 commit comments