You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Write tests for the token revocation endpoint
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3072,6 +3072,7 @@ dependencies = [
|
|||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
"ulid",
|
"ulid",
|
||||||
"url",
|
"url",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
@ -76,8 +76,11 @@ oauth2-types = { path = "../oauth2-types" }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
indoc = "2.0.0"
|
indoc = "2.0.0"
|
||||||
insta = "1.26.0"
|
insta = "1.26.0"
|
||||||
|
tracing-subscriber = "0.3.16"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["webpki-roots"]
|
||||||
|
|
||||||
# Use the native root certificates
|
# Use the native root certificates
|
||||||
native-roots = ["mas-axum-utils/native-roots", "mas-http/native-roots"]
|
native-roots = ["mas-axum-utils/native-roots", "mas-http/native-roots"]
|
||||||
# Use the webpki root certificates
|
# Use the webpki root certificates
|
||||||
|
@ -367,6 +367,7 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn test_state(pool: sqlx::PgPool) -> Result<AppState, anyhow::Error> {
|
async fn test_state(pool: sqlx::PgPool) -> Result<AppState, anyhow::Error> {
|
||||||
use mas_email::MailTransport;
|
use mas_email::MailTransport;
|
||||||
|
use mas_keystore::{JsonWebKey, JsonWebKeySet, PrivateKey};
|
||||||
|
|
||||||
use crate::passwords::Hasher;
|
use crate::passwords::Hasher;
|
||||||
|
|
||||||
@ -378,8 +379,13 @@ async fn test_state(pool: sqlx::PgPool) -> Result<AppState, anyhow::Error> {
|
|||||||
|
|
||||||
let templates = Templates::load(workspace_root.join("templates"), url_builder.clone()).await?;
|
let templates = Templates::load(workspace_root.join("templates"), url_builder.clone()).await?;
|
||||||
|
|
||||||
// TODO: add test keys to the store
|
// TODO: add more test keys to the store
|
||||||
let key_store = Keystore::default();
|
let rsa =
|
||||||
|
PrivateKey::load_pem(include_str!("../../keystore/tests/keys/rsa.pkcs1.pem")).unwrap();
|
||||||
|
let rsa = JsonWebKey::new(rsa).with_kid("test-rsa");
|
||||||
|
|
||||||
|
let jwks = JsonWebKeySet::new(vec![rsa]);
|
||||||
|
let key_store = Keystore::new(jwks);
|
||||||
|
|
||||||
let encrypter = Encrypter::new(&[0x42; 32]);
|
let encrypter = Encrypter::new(&[0x42; 32]);
|
||||||
|
|
||||||
|
@ -198,3 +198,178 @@ pub(crate) async fn post(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use hyper::{
|
||||||
|
header::{AUTHORIZATION, CONTENT_TYPE},
|
||||||
|
Request,
|
||||||
|
};
|
||||||
|
use mas_data_model::AuthorizationCode;
|
||||||
|
use mas_router::SimpleRoute;
|
||||||
|
use mas_storage::{RepositoryAccess, RepositoryTransaction, SystemClock};
|
||||||
|
use mas_storage_pg::PgRepository;
|
||||||
|
use oauth2_types::{
|
||||||
|
registration::ClientRegistrationResponse,
|
||||||
|
requests::{AccessTokenResponse, ResponseMode},
|
||||||
|
scope::{Scope, OPENID},
|
||||||
|
};
|
||||||
|
use rand::SeedableRng;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||||
|
async fn test_revoke_access_token(pool: PgPool) {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(tracing::Level::INFO)
|
||||||
|
.with_test_writer()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let clock = SystemClock::default();
|
||||||
|
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(42);
|
||||||
|
|
||||||
|
let state = crate::test_state(pool.clone()).await.unwrap();
|
||||||
|
let mut app = crate::api_router().with_state(state);
|
||||||
|
|
||||||
|
let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(
|
||||||
|
serde_json::json!({
|
||||||
|
"client_uri": "https://example.com/",
|
||||||
|
"redirect_uris": ["https://example.com/callback"],
|
||||||
|
"contacts": ["contact@example.com"],
|
||||||
|
"token_endpoint_auth_method": "client_secret_post",
|
||||||
|
"response_types": ["code"],
|
||||||
|
"grant_types": ["authorization_code"],
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
|
||||||
|
let client_registration: ClientRegistrationResponse =
|
||||||
|
serde_json::from_slice(&body).unwrap();
|
||||||
|
|
||||||
|
let client_id = client_registration.client_id;
|
||||||
|
let client_secret = client_registration.client_secret.unwrap();
|
||||||
|
|
||||||
|
// Let's provision a user and create a session for them. This part is hard to
|
||||||
|
// test with just HTTP requests, so we'll use the repository directly.
|
||||||
|
let mut repo = PgRepository::from_pool(&pool).await.unwrap();
|
||||||
|
|
||||||
|
let user = repo
|
||||||
|
.user()
|
||||||
|
.add(&mut rng, &clock, "alice".to_owned())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let browser_session = repo
|
||||||
|
.browser_session()
|
||||||
|
.add(&mut rng, &clock, &user)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Lookup the client in the database.
|
||||||
|
let client = repo
|
||||||
|
.oauth2_client()
|
||||||
|
.find_by_client_id(&client_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Start a grant
|
||||||
|
let grant = repo
|
||||||
|
.oauth2_authorization_grant()
|
||||||
|
.add(
|
||||||
|
&mut rng,
|
||||||
|
&clock,
|
||||||
|
&client,
|
||||||
|
"https://example.com/redirect".parse().unwrap(),
|
||||||
|
Scope::from_iter([OPENID]),
|
||||||
|
Some(AuthorizationCode {
|
||||||
|
code: "thisisaverysecurecode".to_owned(),
|
||||||
|
pkce: None,
|
||||||
|
}),
|
||||||
|
Some("state".to_owned()),
|
||||||
|
Some("nonce".to_owned()),
|
||||||
|
None,
|
||||||
|
ResponseMode::Query,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let session = repo
|
||||||
|
.oauth2_session()
|
||||||
|
.create_from_grant(&mut rng, &clock, &grant, &browser_session)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let grant = repo
|
||||||
|
.oauth2_authorization_grant()
|
||||||
|
.fulfill(&clock, &session, grant)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Box::new(repo).save().await.unwrap();
|
||||||
|
|
||||||
|
// Now call the token endpoint to get an access token.
|
||||||
|
let request = Request::post(mas_router::OAuth2TokenEndpoint::PATH)
|
||||||
|
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||||
|
.body(
|
||||||
|
format!(
|
||||||
|
"grant_type=authorization_code&code={code}&redirect_uri={redirect_uri}&client_id={client_id}&client_secret={client_secret}",
|
||||||
|
code = grant.code.unwrap().code,
|
||||||
|
redirect_uri = grant.redirect_uri,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
let status = response.status();
|
||||||
|
assert_eq!(status, StatusCode::OK);
|
||||||
|
|
||||||
|
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
|
||||||
|
let token: AccessTokenResponse = serde_json::from_slice(&body).unwrap();
|
||||||
|
|
||||||
|
// Let's call the userinfo endpoint to make sure we can access it.
|
||||||
|
let request = Request::get(mas_router::OidcUserinfo::PATH)
|
||||||
|
.header(AUTHORIZATION, format!("Bearer {}", token.access_token))
|
||||||
|
.body(String::new())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
let status = response.status();
|
||||||
|
assert_eq!(status, StatusCode::OK);
|
||||||
|
|
||||||
|
// Now let's revoke the access token.
|
||||||
|
let request = Request::post(mas_router::OAuth2Revocation::PATH)
|
||||||
|
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||||
|
.body(format!(
|
||||||
|
"token={token}&token_type_hint=access_token&client_id={client_id}&client_secret={client_secret}",
|
||||||
|
token = token.access_token
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
let status = response.status();
|
||||||
|
assert_eq!(status, StatusCode::OK);
|
||||||
|
|
||||||
|
// Call the userinfo endpoint again to make sure we can't access it anymore.
|
||||||
|
let request = Request::get(mas_router::OidcUserinfo::PATH)
|
||||||
|
.header(AUTHORIZATION, format!("Bearer {}", token.access_token))
|
||||||
|
.body(String::new())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.ready().await.unwrap().call(request).await.unwrap();
|
||||||
|
let status = response.status();
|
||||||
|
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||||
|
// TODO: test refreshing the access token, test refresh token revocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user