Skip to content

Commit 0644040

Browse files
committed
Output the registered client metadata in the registration endpoint
Fixes #2848
1 parent fd511fd commit 0644040

File tree

4 files changed

+76
-45
lines changed

4 files changed

+76
-45
lines changed

crates/data-model/src/oauth2/client.rs

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
// Please see LICENSE in the repository root for full details.
66

77
use chrono::{DateTime, Utc};
8-
use mas_iana::{
9-
jose::JsonWebSignatureAlg,
10-
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
11-
};
8+
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
129
use mas_jose::jwk::PublicJsonWebKeySet;
13-
use oauth2_types::{oidc::ApplicationType, requests::GrantType};
10+
use oauth2_types::{
11+
oidc::ApplicationType,
12+
registration::{ClientMetadata, Localized},
13+
requests::GrantType,
14+
};
1415
use rand::RngCore;
1516
use serde::Serialize;
1617
use thiserror::Error;
@@ -41,10 +42,6 @@ pub struct Client {
4142
/// Array of Redirection URI values used by the Client
4243
pub redirect_uris: Vec<Url>,
4344

44-
/// Array containing a list of the OAuth 2.0 `response_type` values that the
45-
/// Client is declaring that it will restrict itself to using
46-
pub response_types: Vec<OAuthAuthorizationEndpointResponseType>,
47-
4845
/// Array containing a list of the OAuth 2.0 Grant Types that the Client is
4946
/// declaring that it will restrict itself to using.
5047
pub grant_types: Vec<GrantType>,
@@ -123,6 +120,55 @@ impl Client {
123120
}
124121
}
125122

123+
/// Create a client metadata object for this client
124+
pub fn into_metadata(self) -> ClientMetadata {
125+
let (jwks, jwks_uri) = match self.jwks {
126+
Some(JwksOrJwksUri::Jwks(jwks)) => (Some(jwks), None),
127+
Some(JwksOrJwksUri::JwksUri(jwks_uri)) => (None, Some(jwks_uri)),
128+
_ => (None, None),
129+
};
130+
ClientMetadata {
131+
redirect_uris: Some(self.redirect_uris.clone()),
132+
response_types: None,
133+
grant_types: Some(self.grant_types.into_iter().map(Into::into).collect()),
134+
application_type: self.application_type.clone(),
135+
client_name: self.client_name.map(|n| Localized::new(n, [])),
136+
logo_uri: self.logo_uri.map(|n| Localized::new(n, [])),
137+
client_uri: self.client_uri.map(|n| Localized::new(n, [])),
138+
policy_uri: self.policy_uri.map(|n| Localized::new(n, [])),
139+
tos_uri: self.tos_uri.map(|n| Localized::new(n, [])),
140+
jwks_uri,
141+
jwks,
142+
id_token_signed_response_alg: self.id_token_signed_response_alg,
143+
userinfo_signed_response_alg: self.userinfo_signed_response_alg,
144+
token_endpoint_auth_method: self.token_endpoint_auth_method,
145+
token_endpoint_auth_signing_alg: self.token_endpoint_auth_signing_alg,
146+
initiate_login_uri: self.initiate_login_uri,
147+
contacts: None,
148+
software_id: None,
149+
software_version: None,
150+
sector_identifier_uri: None,
151+
subject_type: None,
152+
id_token_encrypted_response_alg: None,
153+
id_token_encrypted_response_enc: None,
154+
userinfo_encrypted_response_alg: None,
155+
userinfo_encrypted_response_enc: None,
156+
request_object_signing_alg: None,
157+
request_object_encryption_alg: None,
158+
request_object_encryption_enc: None,
159+
default_max_age: None,
160+
require_auth_time: None,
161+
default_acr_values: None,
162+
request_uris: None,
163+
require_signed_request_object: None,
164+
require_pushed_authorization_requests: None,
165+
introspection_signed_response_alg: None,
166+
introspection_encrypted_response_alg: None,
167+
introspection_encrypted_response_enc: None,
168+
post_logout_redirect_uris: None,
169+
}
170+
}
171+
126172
#[doc(hidden)]
127173
pub fn samples(now: DateTime<Utc>, rng: &mut impl RngCore) -> Vec<Client> {
128174
vec![
@@ -136,7 +182,6 @@ impl Client {
136182
Url::parse("https://client1.example.com/redirect").unwrap(),
137183
Url::parse("https://client1.example.com/redirect2").unwrap(),
138184
],
139-
response_types: vec![OAuthAuthorizationEndpointResponseType::Code],
140185
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
141186
client_name: Some("Client 1".to_owned()),
142187
client_uri: Some(Url::parse("https://client1.example.com").unwrap()),
@@ -159,7 +204,6 @@ impl Client {
159204
encrypted_client_secret: None,
160205
application_type: Some(ApplicationType::Native),
161206
redirect_uris: vec![Url::parse("https://client2.example.com/redirect").unwrap()],
162-
response_types: vec![OAuthAuthorizationEndpointResponseType::Code],
163207
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
164208
client_name: None,
165209
client_uri: None,

crates/handlers/src/oauth2/registration.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ use oauth2_types::{
1515
errors::{ClientError, ClientErrorCode},
1616
registration::{
1717
ClientMetadata, ClientMetadataVerificationError, ClientRegistrationResponse, Localized,
18+
VerifiedClientMetadata,
1819
},
1920
};
2021
use psl::Psl;
2122
use rand::distributions::{Alphanumeric, DistString};
23+
use serde::Serialize;
2224
use thiserror::Error;
2325
use tracing::info;
2426
use url::Url;
@@ -149,6 +151,14 @@ impl IntoResponse for RouteError {
149151
}
150152
}
151153

154+
#[derive(Serialize)]
155+
struct RouteResponse {
156+
#[serde(flatten)]
157+
response: ClientRegistrationResponse,
158+
#[serde(flatten)]
159+
metadata: VerifiedClientMetadata,
160+
}
161+
152162
/// Check if the host of the given URL is a public suffix
153163
fn host_is_public_suffix(url: &Url) -> bool {
154164
let host = url.host_str().unwrap_or_default().as_bytes();
@@ -282,16 +292,22 @@ pub(crate) async fn post(
282292
)
283293
.await?;
284294

285-
repo.save().await?;
286-
287295
let response = ClientRegistrationResponse {
288-
client_id: client.client_id,
296+
client_id: client.client_id.clone(),
289297
client_secret,
290298
// XXX: we should have a `created_at` field on the clients
291299
client_id_issued_at: Some(client.id.datetime().into()),
292300
client_secret_expires_at: None,
293301
};
294302

303+
// We round-trip back to the metadata to output it in the response
304+
// This should never fail, as the client is valid
305+
let metadata = client.into_metadata().validate()?;
306+
307+
repo.save().await?;
308+
309+
let response = RouteResponse { response, metadata };
310+
295311
Ok((StatusCode::CREATED, Json(response)))
296312
}
297313

crates/policy/src/model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
1818
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1919
pub struct Violation {
2020
pub msg: String,
21+
pub redirect_uri: Option<String>,
2122
pub field: Option<String>,
2223
}
2324

crates/storage-pg/src/oauth2/client.rs

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ use std::{
1212

1313
use async_trait::async_trait;
1414
use mas_data_model::{Client, JwksOrJwksUri, User};
15-
use mas_iana::{
16-
jose::JsonWebSignatureAlg,
17-
oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
18-
};
15+
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
1916
use mas_jose::jwk::PublicJsonWebKeySet;
2017
use mas_storage::{oauth2::OAuth2ClientRepository, Clock};
2118
use oauth2_types::{
@@ -46,15 +43,13 @@ impl<'c> PgOAuth2ClientRepository<'c> {
4643
}
4744
}
4845

49-
// XXX: response_types
5046
#[allow(clippy::struct_excessive_bools)]
5147
#[derive(Debug)]
5248
struct OAuth2ClientLookup {
5349
oauth2_client_id: Uuid,
5450
encrypted_client_secret: Option<String>,
5551
application_type: Option<String>,
5652
redirect_uris: Vec<String>,
57-
// response_types: Vec<String>,
5853
grant_type_authorization_code: bool,
5954
grant_type_refresh_token: bool,
6055
grant_type_client_credentials: bool,
@@ -100,20 +95,6 @@ impl TryInto<Client> for OAuth2ClientLookup {
10095
.source(e)
10196
})?;
10297

103-
let response_types = vec![
104-
OAuthAuthorizationEndpointResponseType::Code,
105-
OAuthAuthorizationEndpointResponseType::IdToken,
106-
OAuthAuthorizationEndpointResponseType::None,
107-
];
108-
/* XXX
109-
let response_types: Result<Vec<OAuthAuthorizationEndpointResponseType>, _> =
110-
self.response_types.iter().map(|s| s.parse()).collect();
111-
let response_types = response_types.map_err(|source| ClientFetchError::ParseField {
112-
field: "response_types",
113-
source,
114-
})?;
115-
*/
116-
11798
let mut grant_types = Vec::new();
11899
if self.grant_type_authorization_code {
119100
grant_types.push(GrantType::AuthorizationCode);
@@ -253,7 +234,6 @@ impl TryInto<Client> for OAuth2ClientLookup {
253234
encrypted_client_secret: self.encrypted_client_secret,
254235
application_type,
255236
redirect_uris,
256-
response_types,
257237
grant_types,
258238
client_name: self.client_name,
259239
logo_uri,
@@ -493,11 +473,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
493473
encrypted_client_secret,
494474
application_type,
495475
redirect_uris,
496-
response_types: vec![
497-
OAuthAuthorizationEndpointResponseType::Code,
498-
OAuthAuthorizationEndpointResponseType::IdToken,
499-
OAuthAuthorizationEndpointResponseType::None,
500-
],
501476
grant_types,
502477
client_name,
503478
logo_uri,
@@ -598,11 +573,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
598573
encrypted_client_secret,
599574
application_type: None,
600575
redirect_uris,
601-
response_types: vec![
602-
OAuthAuthorizationEndpointResponseType::Code,
603-
OAuthAuthorizationEndpointResponseType::IdToken,
604-
OAuthAuthorizationEndpointResponseType::None,
605-
],
606576
grant_types: vec![
607577
GrantType::AuthorizationCode,
608578
GrantType::RefreshToken,

0 commit comments

Comments
 (0)