Skip to content

Commit

Permalink
Output the registered client metadata in the registration endpoint
Browse files Browse the repository at this point in the history
Fixes #2848
  • Loading branch information
sandhose committed Sep 20, 2024
1 parent 21fb01d commit 87f3452
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 45 deletions.
66 changes: 55 additions & 11 deletions crates/data-model/src/oauth2/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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![
Expand All @@ -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()),
Expand All @@ -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,
Expand Down
22 changes: 19 additions & 3 deletions crates/handlers/src/oauth2/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)))
}

Expand Down
1 change: 1 addition & 0 deletions crates/policy/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}

Expand Down
32 changes: 1 addition & 31 deletions crates/storage-pg/src/oauth2/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -46,15 +43,13 @@ impl<'c> PgOAuth2ClientRepository<'c> {
}
}

// XXX: response_types
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
struct OAuth2ClientLookup {
oauth2_client_id: Uuid,
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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 87f3452

Please sign in to comment.