Skip to content

Commit 9d80ecc

Browse files
committed
Add personal access token introspection test
1 parent 893e426 commit 9d80ecc

File tree

1 file changed

+124
-1
lines changed

1 file changed

+124
-1
lines changed

crates/handlers/src/oauth2/introspection.rs

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,9 @@ pub(crate) async fn post(
743743
mod 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

Comments
 (0)