mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-06-04 12:31:37 +00:00
Output the registered client metadata in the registration endpoint
Fixes #2848
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_iana::{
|
||||
jose::JsonWebSignatureAlg,
|
||||
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
|
||||
};
|
||||
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
|
||||
use mas_jose::jwk::PublicJsonWebKeySet;
|
||||
use oauth2_types::{oidc::ApplicationType, requests::GrantType};
|
||||
use oauth2_types::{
|
||||
oidc::ApplicationType,
|
||||
registration::{ClientMetadata, Localized},
|
||||
requests::GrantType,
|
||||
};
|
||||
use rand::RngCore;
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
@@ -41,10 +42,6 @@ pub struct Client {
|
||||
/// Array of Redirection URI values used by the Client
|
||||
pub redirect_uris: Vec<Url>,
|
||||
|
||||
/// Array containing a list of the OAuth 2.0 `response_type` values that the
|
||||
/// Client is declaring that it will restrict itself to using
|
||||
pub response_types: Vec<OAuthAuthorizationEndpointResponseType>,
|
||||
|
||||
/// Array containing a list of the OAuth 2.0 Grant Types that the Client is
|
||||
/// declaring that it will restrict itself to using.
|
||||
pub grant_types: Vec<GrantType>,
|
||||
@@ -123,6 +120,55 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a client metadata object for this client
|
||||
pub fn into_metadata(self) -> ClientMetadata {
|
||||
let (jwks, jwks_uri) = match self.jwks {
|
||||
Some(JwksOrJwksUri::Jwks(jwks)) => (Some(jwks), None),
|
||||
Some(JwksOrJwksUri::JwksUri(jwks_uri)) => (None, Some(jwks_uri)),
|
||||
_ => (None, None),
|
||||
};
|
||||
ClientMetadata {
|
||||
redirect_uris: Some(self.redirect_uris.clone()),
|
||||
response_types: None,
|
||||
grant_types: Some(self.grant_types.into_iter().map(Into::into).collect()),
|
||||
application_type: self.application_type.clone(),
|
||||
client_name: self.client_name.map(|n| Localized::new(n, [])),
|
||||
logo_uri: self.logo_uri.map(|n| Localized::new(n, [])),
|
||||
client_uri: self.client_uri.map(|n| Localized::new(n, [])),
|
||||
policy_uri: self.policy_uri.map(|n| Localized::new(n, [])),
|
||||
tos_uri: self.tos_uri.map(|n| Localized::new(n, [])),
|
||||
jwks_uri,
|
||||
jwks,
|
||||
id_token_signed_response_alg: self.id_token_signed_response_alg,
|
||||
userinfo_signed_response_alg: self.userinfo_signed_response_alg,
|
||||
token_endpoint_auth_method: self.token_endpoint_auth_method,
|
||||
token_endpoint_auth_signing_alg: self.token_endpoint_auth_signing_alg,
|
||||
initiate_login_uri: self.initiate_login_uri,
|
||||
contacts: None,
|
||||
software_id: None,
|
||||
software_version: None,
|
||||
sector_identifier_uri: None,
|
||||
subject_type: None,
|
||||
id_token_encrypted_response_alg: None,
|
||||
id_token_encrypted_response_enc: None,
|
||||
userinfo_encrypted_response_alg: None,
|
||||
userinfo_encrypted_response_enc: None,
|
||||
request_object_signing_alg: None,
|
||||
request_object_encryption_alg: None,
|
||||
request_object_encryption_enc: None,
|
||||
default_max_age: None,
|
||||
require_auth_time: None,
|
||||
default_acr_values: None,
|
||||
request_uris: None,
|
||||
require_signed_request_object: None,
|
||||
require_pushed_authorization_requests: None,
|
||||
introspection_signed_response_alg: None,
|
||||
introspection_encrypted_response_alg: None,
|
||||
introspection_encrypted_response_enc: None,
|
||||
post_logout_redirect_uris: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn samples(now: DateTime<Utc>, rng: &mut impl RngCore) -> Vec<Client> {
|
||||
vec![
|
||||
@@ -136,7 +182,6 @@ impl Client {
|
||||
Url::parse("https://client1.example.com/redirect").unwrap(),
|
||||
Url::parse("https://client1.example.com/redirect2").unwrap(),
|
||||
],
|
||||
response_types: vec![OAuthAuthorizationEndpointResponseType::Code],
|
||||
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
|
||||
client_name: Some("Client 1".to_owned()),
|
||||
client_uri: Some(Url::parse("https://client1.example.com").unwrap()),
|
||||
@@ -159,7 +204,6 @@ impl Client {
|
||||
encrypted_client_secret: None,
|
||||
application_type: Some(ApplicationType::Native),
|
||||
redirect_uris: vec![Url::parse("https://client2.example.com/redirect").unwrap()],
|
||||
response_types: vec![OAuthAuthorizationEndpointResponseType::Code],
|
||||
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
|
||||
client_name: None,
|
||||
client_uri: None,
|
||||
|
||||
@@ -15,10 +15,12 @@ use oauth2_types::{
|
||||
errors::{ClientError, ClientErrorCode},
|
||||
registration::{
|
||||
ClientMetadata, ClientMetadataVerificationError, ClientRegistrationResponse, Localized,
|
||||
VerifiedClientMetadata,
|
||||
},
|
||||
};
|
||||
use psl::Psl;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
@@ -149,6 +151,14 @@ impl IntoResponse for RouteError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RouteResponse {
|
||||
#[serde(flatten)]
|
||||
response: ClientRegistrationResponse,
|
||||
#[serde(flatten)]
|
||||
metadata: VerifiedClientMetadata,
|
||||
}
|
||||
|
||||
/// Check if the host of the given URL is a public suffix
|
||||
fn host_is_public_suffix(url: &Url) -> bool {
|
||||
let host = url.host_str().unwrap_or_default().as_bytes();
|
||||
@@ -282,16 +292,22 @@ pub(crate) async fn post(
|
||||
)
|
||||
.await?;
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
let response = ClientRegistrationResponse {
|
||||
client_id: client.client_id,
|
||||
client_id: client.client_id.clone(),
|
||||
client_secret,
|
||||
// XXX: we should have a `created_at` field on the clients
|
||||
client_id_issued_at: Some(client.id.datetime().into()),
|
||||
client_secret_expires_at: None,
|
||||
};
|
||||
|
||||
// We round-trip back to the metadata to output it in the response
|
||||
// This should never fail, as the client is valid
|
||||
let metadata = client.into_metadata().validate()?;
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
let response = RouteResponse { response, metadata };
|
||||
|
||||
Ok((StatusCode::CREATED, Json(response)))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
|
||||
pub struct Violation {
|
||||
pub msg: String,
|
||||
pub redirect_uri: Option<String>,
|
||||
pub field: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ use std::{
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mas_data_model::{Client, JwksOrJwksUri, User};
|
||||
use mas_iana::{
|
||||
jose::JsonWebSignatureAlg,
|
||||
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
|
||||
};
|
||||
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
|
||||
use mas_jose::jwk::PublicJsonWebKeySet;
|
||||
use mas_storage::{oauth2::OAuth2ClientRepository, Clock};
|
||||
use oauth2_types::{
|
||||
@@ -46,7 +43,6 @@ impl<'c> PgOAuth2ClientRepository<'c> {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: response_types
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug)]
|
||||
struct OAuth2ClientLookup {
|
||||
@@ -54,7 +50,6 @@ struct OAuth2ClientLookup {
|
||||
encrypted_client_secret: Option<String>,
|
||||
application_type: Option<String>,
|
||||
redirect_uris: Vec<String>,
|
||||
// response_types: Vec<String>,
|
||||
grant_type_authorization_code: bool,
|
||||
grant_type_refresh_token: bool,
|
||||
grant_type_client_credentials: bool,
|
||||
@@ -100,20 +95,6 @@ impl TryInto<Client> for OAuth2ClientLookup {
|
||||
.source(e)
|
||||
})?;
|
||||
|
||||
let response_types = vec![
|
||||
OAuthAuthorizationEndpointResponseType::Code,
|
||||
OAuthAuthorizationEndpointResponseType::IdToken,
|
||||
OAuthAuthorizationEndpointResponseType::None,
|
||||
];
|
||||
/* XXX
|
||||
let response_types: Result<Vec<OAuthAuthorizationEndpointResponseType>, _> =
|
||||
self.response_types.iter().map(|s| s.parse()).collect();
|
||||
let response_types = response_types.map_err(|source| ClientFetchError::ParseField {
|
||||
field: "response_types",
|
||||
source,
|
||||
})?;
|
||||
*/
|
||||
|
||||
let mut grant_types = Vec::new();
|
||||
if self.grant_type_authorization_code {
|
||||
grant_types.push(GrantType::AuthorizationCode);
|
||||
@@ -253,7 +234,6 @@ impl TryInto<Client> for OAuth2ClientLookup {
|
||||
encrypted_client_secret: self.encrypted_client_secret,
|
||||
application_type,
|
||||
redirect_uris,
|
||||
response_types,
|
||||
grant_types,
|
||||
client_name: self.client_name,
|
||||
logo_uri,
|
||||
@@ -493,11 +473,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
|
||||
encrypted_client_secret,
|
||||
application_type,
|
||||
redirect_uris,
|
||||
response_types: vec![
|
||||
OAuthAuthorizationEndpointResponseType::Code,
|
||||
OAuthAuthorizationEndpointResponseType::IdToken,
|
||||
OAuthAuthorizationEndpointResponseType::None,
|
||||
],
|
||||
grant_types,
|
||||
client_name,
|
||||
logo_uri,
|
||||
@@ -598,11 +573,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
|
||||
encrypted_client_secret,
|
||||
application_type: None,
|
||||
redirect_uris,
|
||||
response_types: vec![
|
||||
OAuthAuthorizationEndpointResponseType::Code,
|
||||
OAuthAuthorizationEndpointResponseType::IdToken,
|
||||
OAuthAuthorizationEndpointResponseType::None,
|
||||
],
|
||||
grant_types: vec![
|
||||
GrantType::AuthorizationCode,
|
||||
GrantType::RefreshToken,
|
||||
|
||||
Reference in New Issue
Block a user