@@ -743,7 +743,9 @@ pub(crate) async fn post(
743743mod tests {
744744 use chrono:: Duration ;
745745 use hyper:: { Request , StatusCode } ;
746- use mas_data_model:: { AccessToken , Clock , RefreshToken } ;
746+ use mas_data_model:: {
747+ AccessToken , Clock , RefreshToken , TokenType , personal:: session:: PersonalSessionOwner ,
748+ } ;
747749 use mas_iana:: oauth:: OAuthTokenTypeHint ;
748750 use mas_matrix:: { HomeserverConnection , MockHomeserverConnection , ProvisionRequest } ;
749751 use mas_router:: { OAuth2Introspection , OAuth2RegistrationEndpoint , SimpleRoute } ;
@@ -1176,4 +1178,125 @@ mod tests {
11761178 let response: ClientError = response. json ( ) ;
11771179 assert_eq ! ( response. error, ClientErrorCode :: AccessDenied ) ;
11781180 }
1181+
1182+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
1183+ async fn test_introspect_personal_access_tokens ( pool : PgPool ) {
1184+ setup ( ) ;
1185+ let state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
1186+
1187+ // Provision a client which will be used to do introspection requests
1188+ let request = Request :: post ( OAuth2RegistrationEndpoint :: PATH ) . json ( json ! ( {
1189+ "client_uri" : "https://introspecting.com/" ,
1190+ "grant_types" : [ ] ,
1191+ "token_endpoint_auth_method" : "client_secret_basic" ,
1192+ } ) ) ;
1193+
1194+ let response = state. request ( request) . await ;
1195+ response. assert_status ( StatusCode :: CREATED ) ;
1196+ let client: ClientRegistrationResponse = response. json ( ) ;
1197+ let introspecting_client_id = client. client_id ;
1198+ let introspecting_client_secret = client. client_secret . unwrap ( ) ;
1199+
1200+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1201+
1202+ // Provision an owner user (who provisions the personal session)
1203+ let owner_user = repo
1204+ . user ( )
1205+ . add ( & mut state. rng ( ) , & state. clock , "admin" . to_owned ( ) )
1206+ . await
1207+ . unwrap ( ) ;
1208+
1209+ // Provision an actor user (which the token represents)
1210+ let actor_user = repo
1211+ . user ( )
1212+ . add ( & mut state. rng ( ) , & state. clock , "bruce" . to_owned ( ) )
1213+ . await
1214+ . unwrap ( ) ;
1215+
1216+ // admin creates a personal session to control bruce's account
1217+ let personal_session = repo
1218+ . personal_session ( )
1219+ . add (
1220+ & mut state. rng ( ) ,
1221+ & state. clock ,
1222+ PersonalSessionOwner :: User ( owner_user. id ) ,
1223+ & actor_user,
1224+ "Test Personal Access Token" . to_owned ( ) ,
1225+ Scope :: from_iter ( [ OPENID ] ) ,
1226+ )
1227+ . await
1228+ . unwrap ( ) ;
1229+
1230+ // Generate a personal access token with proper token format
1231+ let token_string = TokenType :: PersonalAccessToken . generate ( & mut state. rng ( ) ) ;
1232+ let _personal_access_token = repo
1233+ . personal_access_token ( )
1234+ . add (
1235+ & mut state. rng ( ) ,
1236+ & state. clock ,
1237+ & personal_session,
1238+ & token_string,
1239+ Some ( Duration :: try_hours ( 1 ) . unwrap ( ) ) ,
1240+ )
1241+ . await
1242+ . unwrap ( ) ;
1243+
1244+ repo. save ( ) . await . unwrap ( ) ;
1245+
1246+ // Now that we have a personal access token, we can introspect it
1247+ let request = Request :: post ( OAuth2Introspection :: PATH )
1248+ . basic_auth ( & introspecting_client_id, & introspecting_client_secret)
1249+ . form ( json ! ( { "token" : token_string } ) ) ;
1250+ let response = state. request ( request) . await ;
1251+ response. assert_status ( StatusCode :: OK ) ;
1252+ let response: IntrospectionResponse = response. json ( ) ;
1253+ assert ! ( response. active) ;
1254+ // Actor user
1255+ assert_eq ! ( response. username, Some ( "bruce" . to_owned( ) ) ) ;
1256+ // Not owned by a client
1257+ assert_eq ! ( response. client_id, None ) ;
1258+ assert_eq ! ( response. token_type, Some ( OAuthTokenTypeHint :: AccessToken ) ) ;
1259+ assert_eq ! ( response. scope, Some ( Scope :: from_iter( [ OPENID ] ) ) ) ;
1260+
1261+ // Do the same request, but with a token_type_hint
1262+ let last_active = state. clock . now ( ) ;
1263+ let request = Request :: post ( OAuth2Introspection :: PATH )
1264+ . basic_auth ( & introspecting_client_id, & introspecting_client_secret)
1265+ . form ( json ! ( { "token" : token_string, "token_type_hint" : "access_token" } ) ) ;
1266+ let response = state. request ( request) . await ;
1267+ response. assert_status ( StatusCode :: OK ) ;
1268+ let response: IntrospectionResponse = response. json ( ) ;
1269+ assert ! ( response. active) ;
1270+
1271+ // Do the same request, but with the wrong token_type_hint
1272+ let request = Request :: post ( OAuth2Introspection :: PATH )
1273+ . basic_auth ( & introspecting_client_id, & introspecting_client_secret)
1274+ . form ( json ! ( { "token" : token_string, "token_type_hint" : "refresh_token" } ) ) ;
1275+ let response = state. request ( request) . await ;
1276+ response. assert_status ( StatusCode :: OK ) ;
1277+ let response: IntrospectionResponse = response. json ( ) ;
1278+ assert ! ( !response. active) ; // It shouldn't be active with wrong hint
1279+
1280+ // Advance the clock to invalidate the access token
1281+ state. clock . advance ( Duration :: try_hours ( 2 ) . unwrap ( ) ) ;
1282+
1283+ let request = Request :: post ( OAuth2Introspection :: PATH )
1284+ . basic_auth ( & introspecting_client_id, & introspecting_client_secret)
1285+ . form ( json ! ( { "token" : token_string } ) ) ;
1286+ let response = state. request ( request) . await ;
1287+ response. assert_status ( StatusCode :: OK ) ;
1288+ let response: IntrospectionResponse = response. json ( ) ;
1289+ assert ! ( !response. active) ; // It shouldn't be active anymore
1290+
1291+ state. activity_tracker . flush ( ) . await ;
1292+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1293+ let session = repo
1294+ . personal_session ( )
1295+ . lookup ( personal_session. id )
1296+ . await
1297+ . unwrap ( )
1298+ . unwrap ( ) ;
1299+ assert_eq ! ( session. last_active_at, Some ( last_active) ) ;
1300+ repo. save ( ) . await . unwrap ( ) ;
1301+ }
11791302}
0 commit comments