1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

Allow to validate client metadata

According to OpenID Connect Dynamic Client Registration Spec 1.0.
Introduce VerifiedClientMetadata.
This commit is contained in:
Kévin Commaille
2022-08-16 11:25:40 +02:00
committed by Quentin Gliech
parent a543af4de3
commit e202c3dd6d
9 changed files with 1923 additions and 235 deletions

View File

@@ -16,13 +16,13 @@ use std::sync::Arc;
use axum::{response::IntoResponse, Extension, Json};
use hyper::StatusCode;
use mas_iana::oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod};
use mas_policy::{PolicyFactory, Violation};
use mas_storage::oauth2::client::insert_client;
use oauth2_types::{
errors::{INVALID_CLIENT_METADATA, INVALID_REDIRECT_URI, SERVER_ERROR},
registration::{ClientMetadata, ClientRegistrationResponse},
requests::GrantType,
registration::{
ClientMetadata, ClientMetadataVerificationError, ClientRegistrationResponse, Localized,
},
};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sqlx::PgPool;
@@ -53,6 +53,18 @@ impl From<sqlx::Error> for RouteError {
}
}
impl From<ClientMetadataVerificationError> for RouteError {
fn from(e: ClientMetadataVerificationError) -> Self {
match e {
ClientMetadataVerificationError::MissingRedirectUris
| ClientMetadataVerificationError::RedirectUriWithFragment(_) => {
Self::InvalidRedirectUri
}
_ => Self::InvalidClientMetadata,
}
}
}
// TODO: there is probably a better way to do achieve this. ClientError only
// works for static strings
#[derive(serde::Serialize)]
@@ -111,55 +123,18 @@ pub(crate) async fn post(
) -> Result<impl IntoResponse, RouteError> {
info!(?body, "Client registration");
// Let's validate a bunch of things on the client body first
for uri in &body.redirect_uris {
if uri.fragment().is_some() {
return Err(RouteError::InvalidRedirectUri);
}
}
// Check that the client did not send both a jwks and a jwks_uri
if body.jwks_uri.is_some() && body.jwks.is_some() {
return Err(RouteError::InvalidClientMetadata);
}
// Check that the grant_types and the response_types are coherent
let has_implicit = body.grant_types.contains(&GrantType::Implicit);
let has_authorization_code = body.grant_types.contains(&GrantType::AuthorizationCode);
let has_both = has_implicit && has_authorization_code;
for response_type in &body.response_types {
let is_ok = match response_type {
OAuthAuthorizationEndpointResponseType::Code => has_authorization_code,
OAuthAuthorizationEndpointResponseType::CodeIdToken
| OAuthAuthorizationEndpointResponseType::CodeIdTokenToken
| OAuthAuthorizationEndpointResponseType::CodeToken => has_both,
OAuthAuthorizationEndpointResponseType::IdToken
| OAuthAuthorizationEndpointResponseType::IdTokenToken
| OAuthAuthorizationEndpointResponseType::Token => has_implicit,
OAuthAuthorizationEndpointResponseType::None => true,
};
if !is_ok {
return Err(RouteError::InvalidClientMetadata);
}
}
// If the private_key_jwt auth method is used, check that we actually have a
// JWKS for that client
if body.token_endpoint_auth_method == Some(OAuthClientAuthenticationMethod::PrivateKeyJwt)
&& body.jwks_uri.is_none()
&& body.jwks.is_none()
{
return Err(RouteError::InvalidClientMetadata);
}
// Validate the body
let metadata = body.validate()?;
let mut policy = policy_factory.instantiate().await?;
let res = policy.evaluate_client_registration(&body).await?;
let res = policy.evaluate_client_registration(&metadata).await?;
if !res.valid() {
return Err(RouteError::PolicyDenied(res.violations));
}
// Contacts was checked by the policy
let contacts = metadata.contacts.as_deref().unwrap_or_default();
// Grab a txn
let mut txn = pool.begin().await?;
@@ -173,23 +148,26 @@ pub(crate) async fn post(
insert_client(
&mut txn,
&client_id,
&body.redirect_uris,
metadata.redirect_uris(),
None,
&body.response_types,
&body.grant_types,
&body.contacts,
body.client_name.as_deref(),
body.logo_uri.as_ref(),
body.client_uri.as_ref(),
body.policy_uri.as_ref(),
body.tos_uri.as_ref(),
body.jwks_uri.as_ref(),
body.jwks.as_ref(),
body.id_token_signed_response_alg,
body.userinfo_signed_response_alg,
body.token_endpoint_auth_method,
body.token_endpoint_auth_signing_alg,
body.initiate_login_uri.as_ref(),
metadata.response_types(),
metadata.grant_types(),
contacts,
metadata
.client_name
.as_ref()
.map(|l| l.non_localized().as_ref()),
metadata.logo_uri.as_ref().map(Localized::non_localized),
metadata.client_uri.as_ref().map(Localized::non_localized),
metadata.policy_uri.as_ref().map(Localized::non_localized),
metadata.tos_uri.as_ref().map(Localized::non_localized),
metadata.jwks_uri.as_ref(),
metadata.jwks.as_ref(),
metadata.id_token_signed_response_alg,
metadata.userinfo_signed_response_alg,
metadata.token_endpoint_auth_method,
metadata.token_endpoint_auth_signing_alg,
metadata.initiate_login_uri.as_ref(),
)
.await?;