From 4b16baae0b2cdde795ac54c28b330da76a5658d6 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 13 Sep 2024 16:11:54 +0200 Subject: [PATCH 1/3] Remove the `contacts` requirement from the client registration policy --- docs/reference/configuration.md | 2 - policies/client_registration.rego | 17 +---- policies/client_registration_test.rego | 89 +------------------------- 3 files changed, 5 insertions(+), 103 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 56183a7c1..7552382dd 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -372,8 +372,6 @@ policy: allow_insecure_uris: false # don't require clients to provide a client_uri. default: false allow_missing_client_uri: false - # don't require clients to provide a contacts field. default: false - allow_missing_contacts: false # Restrict emails on registration to a specific domain # Items in this array are evaluated as a glob diff --git a/policies/client_registration.rego b/policies/client_registration.rego index 0104979c2..cd95b0dd1 100644 --- a/policies/client_registration.rego +++ b/policies/client_registration.rego @@ -96,19 +96,6 @@ violation[{"msg": "logo_uri not on the same host as the client_uri"}] { not host_matches_client_uri(input.client_metadata.logo_uri) } -violation[{"msg": "missing contacts"}] { - not data.client_registration.allow_missing_contacts - not input.client_metadata.contacts -} - -violation[{"msg": "invalid contacts"}] { - not is_array(input.client_metadata.contacts) -} - -violation[{"msg": "empty contacts"}] { - count(input.client_metadata.contacts) == 0 -} - # If the grant_types is missing, we assume it is authorization_code uses_grant_type("authorization_code") { not input.client_metadata.grant_types @@ -143,11 +130,11 @@ violation[{"msg": "missing redirect_uris"}] { not input.client_metadata.redirect_uris } -violation[{"msg": "invalid redirect_uris"}] { +violation[{"msg": "invalid redirect_uris: it must be an array"}] { not is_array(input.client_metadata.redirect_uris) } -violation[{"msg": "empty redirect_uris"}] { +violation[{"msg": "invalid redirect_uris: it must have at least one redirect_uri"}] { requires_redirect_uris count(input.client_metadata.redirect_uris) == 0 } diff --git a/policies/client_registration_test.rego b/policies/client_registration_test.rego index e7510f988..fe4f077e2 100644 --- a/policies/client_registration_test.rego +++ b/policies/client_registration_test.rego @@ -5,20 +5,13 @@ test_valid { "grant_types": ["authorization_code"], "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/callback"], - "contacts": ["contact@example.com"], } } test_missing_client_uri { - not allow with input.client_metadata as { - "grant_types": [], - "contacts": ["contact@example.com"], - } + not allow with input.client_metadata as {"grant_types": []} - allow with input.client_metadata as { - "grant_types": [], - "contacts": ["contact@example.com"], - } + allow with input.client_metadata as {"grant_types": []} with data.client_registration.allow_missing_client_uri as true } @@ -26,7 +19,6 @@ test_insecure_client_uri { not allow with input.client_metadata as { "grant_types": [], "client_uri": "http://example.com/", - "contacts": ["contact@example.com"], } } @@ -35,7 +27,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "https://example.com/tos", - "contacts": ["contact@example.com"], } # Insecure @@ -43,7 +34,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "http://example.com/tos", - "contacts": ["contact@example.com"], } # Insecure, but allowed by the config @@ -51,7 +41,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "http://example.com/tos", - "contacts": ["contact@example.com"], } with data.client_registration.allow_insecure_uris as true @@ -60,7 +49,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "https://example.org/tos", - "contacts": ["contact@example.com"], } # TOS on a subdomain of the client_uri host is allowed @@ -68,7 +56,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "https://tos.example.com/", - "contacts": ["contact@example.com"], } # Host mistmatch, but allowed by the config @@ -76,7 +63,6 @@ test_tos_uri { "grant_types": [], "client_uri": "https://example.com/", "tos_uri": "https://example.org/tos", - "contacts": ["contact@example.com"], } with data.client_registration.allow_host_mismatch as true } @@ -86,7 +72,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "https://example.com/logo.png", - "contacts": ["contact@example.com"], } # Insecure @@ -94,7 +79,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "http://example.com/logo.png", - "contacts": ["contact@example.com"], } # Insecure, but allowed by the config @@ -102,7 +86,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "http://example.com/logo.png", - "contacts": ["contact@example.com"], } with data.client_registration.allow_insecure_uris as true @@ -111,7 +94,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "https://example.org/logo.png", - "contacts": ["contact@example.com"], } # Logo on a subdomain of the client_uri host is allowed @@ -119,7 +101,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "https://static.example.com/logo.png", - "contacts": ["contact@example.com"], } # Host mistmatch, but allowed by the config @@ -127,7 +108,6 @@ test_logo_uri { "grant_types": [], "client_uri": "https://example.com/", "logo_uri": "https://example.org/logo.png", - "contacts": ["contact@example.com"], } with data.client_registration.allow_host_mismatch as true } @@ -137,7 +117,6 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "https://example.com/policy", - "contacts": ["contact@example.com"], } # Insecure @@ -145,7 +124,6 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "http://example.com/policy", - "contacts": ["contact@example.com"], } # Insecure, but allowed by the config @@ -153,7 +131,6 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "http://example.com/policy", - "contacts": ["contact@example.com"], } with data.client_registration.allow_insecure_uris as true @@ -162,7 +139,6 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "https://example.org/policy", - "contacts": ["contact@example.com"], } # Policy on a subdomain of the client_uri host is allowed @@ -170,7 +146,6 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "https://policy.example.com/", - "contacts": ["contact@example.com"], } # Host mistmatch, but allowed by the config @@ -178,51 +153,42 @@ test_policy_uri { "grant_types": [], "client_uri": "https://example.com/", "policy_uri": "https://example.org/policy", - "contacts": ["contact@example.com"], } with data.client_registration.allow_host_mismatch as true } test_redirect_uris { # Missing redirect_uris - not allow with input.client_metadata as { - "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], - } + not allow with input.client_metadata as {"client_uri": "https://example.com/"} # redirect_uris is not an array not allow with input.client_metadata as { "client_uri": "https://example.com/", "redirect_uris": "https://example.com/callback", - "contacts": ["contact@example.com"], } # Empty redirect_uris not allow with input.client_metadata as { "client_uri": "https://example.com/", "redirect_uris": [], - "contacts": ["contact@example.com"], } # Not required for the client_credentials grant allow with input.client_metadata as { "grant_types": ["client_credentials"], "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } # Required for the authorization_code grant not allow with input.client_metadata as { "grant_types": ["client_credentials", "refresh_token", "authorization_code"], "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } # Required for the implicit grant not allow with input.client_metadata as { "grant_types": ["client_credentials", "implicit"], "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } } @@ -231,7 +197,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/second/callback", "https://example.com/callback"], - "contacts": ["contact@example.com"], } # Insecure URL @@ -239,7 +204,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["http://example.com/callback", "https://example.com/callback"], - "contacts": ["contact@example.com"], } # Insecure URL, but allowed by the config @@ -247,7 +211,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["http://example.com/callback", "https://example.com/callback"], - "contacts": ["contact@example.com"], } with data.client_registration.allow_insecure_uris as true @@ -256,7 +219,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"], - "contacts": ["contact@example.com"], } # Host mismatch, but allowed by the config @@ -264,7 +226,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"], - "contacts": ["contact@example.com"], } with data.client_registration.allow_host_mismatch as true @@ -273,7 +234,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["https://app.example.com/callback"], - "contacts": ["contact@example.com"], } # No custom scheme allowed @@ -281,7 +241,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["com.example.app:/callback"], - "contacts": ["contact@example.com"], } # localhost not allowed @@ -289,7 +248,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["http://locahost:1234/callback"], - "contacts": ["contact@example.com"], } # localhost not allowed @@ -297,7 +255,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["http://127.0.0.1:1234/callback"], - "contacts": ["contact@example.com"], } # localhost not allowed @@ -305,7 +262,6 @@ test_web_redirect_uri { "application_type": "web", "client_uri": "https://example.com/", "redirect_uris": ["http://[::1]:1234/callback"], - "contacts": ["contact@example.com"], } } @@ -323,7 +279,6 @@ test_native_redirect_uri { "http://[::1]/callback", "http://[::1]:1234/callback", ], - "contacts": ["contact@example.com"], } # We still allow matching URLs for native apps @@ -331,7 +286,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/"], - "contacts": ["contact@example.com"], } # But not insecure @@ -339,7 +293,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["http://example.com/"], - "contacts": ["contact@example.com"], } # And not a mismatch @@ -347,7 +300,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["http://bad.com/"], - "contacts": ["contact@example.com"], } # We don't allow HTTPS on localhost @@ -355,7 +307,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["https://localhost:1234/"], - "contacts": ["contact@example.com"], } # Ensure we're not allowing localhost as a prefix @@ -363,7 +314,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["http://localhost.com/"], - "contacts": ["contact@example.com"], } # For custom schemes, it should match the client_uri hostname @@ -371,7 +321,6 @@ test_native_redirect_uri { "application_type": "native", "client_uri": "https://example.com/", "redirect_uris": ["org.example.app:/callback"], - "contacts": ["contact@example.com"], } } @@ -381,48 +330,17 @@ test_reverse_dns_match { reverse_dns_match(client_uri.host, redirect_uri.scheme) } -test_contacts { - # Missing contacts - not allow with input.client_metadata as { - "grant_types": [], - "client_uri": "https://example.com/", - } - - # Missing contacts, but allowed by config - allow with input.client_metadata as { - "grant_types": [], - "client_uri": "https://example.com/", - } - with data.client_registration.allow_missing_contacts as true - - # contacts is not an array - not allow with input.client_metadata as { - "grant_types": [], - "client_uri": "https://example.com/", - "contacts": "contact@example.com", - } - - # Empty contacts - not allow with input.client_metadata as { - "grant_types": [], - "client_uri": "https://example.com/", - "contacts": [], - } -} - test_client_credentials_grant { # Allowed for confidential clients allow with input.client_metadata as { "grant_types": ["client_credentials"], "token_endpoint_auth_method": "client_secret_basic", "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } allow with input.client_metadata as { "grant_types": ["client_credentials"], # If omitted, defaults to "client_secret_basic" "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } # Disallowed for public clients @@ -430,7 +348,6 @@ test_client_credentials_grant { "grant_types": ["client_credentials"], "token_endpoint_auth_method": "none", "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], } } From fd511fdcc755b7ce28dd2692dc9853e78e4f75cd Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 13 Sep 2024 16:23:00 +0200 Subject: [PATCH 2/3] Remove contacts from the data model --- crates/data-model/src/oauth2/client.rs | 5 - crates/handlers/src/graphql/model/oauth.rs | 5 - crates/handlers/src/graphql/tests.rs | 3 - .../handlers/src/oauth2/device/authorize.rs | 1 - crates/handlers/src/oauth2/introspection.rs | 3 - crates/handlers/src/oauth2/registration.rs | 7 - crates/handlers/src/oauth2/revoke.rs | 1 - crates/handlers/src/oauth2/token.rs | 5 - crates/handlers/src/test_utils.rs | 1 - ...7b285225aeae7a4d1ca91084ae84f25dcbbec.json | 134 +++++++++++++++++ ...2a14314cc1833a6be272056e09d07c21ba9ef.json | 136 ++++++++++++++++++ ...f5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3.json | 136 ++++++++++++++++++ crates/storage-pg/src/app_session.rs | 2 - crates/storage-pg/src/oauth2/client.rs | 10 +- crates/storage-pg/src/oauth2/mod.rs | 8 -- crates/storage/src/oauth2/client.rs | 3 - frontend/schema.graphql | 4 - frontend/src/gql/graphql.ts | 2 - frontend/src/gql/schema.ts | 17 --- 19 files changed, 407 insertions(+), 76 deletions(-) create mode 100644 crates/storage-pg/.sqlx/query-199819516dce285771a75a48a687b285225aeae7a4d1ca91084ae84f25dcbbec.json create mode 100644 crates/storage-pg/.sqlx/query-1aa4c541af7e12431a58f43a1882a14314cc1833a6be272056e09d07c21ba9ef.json create mode 100644 crates/storage-pg/.sqlx/query-afef7e8248b415dd1fbf86748cbf5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3.json diff --git a/crates/data-model/src/oauth2/client.rs b/crates/data-model/src/oauth2/client.rs index b8340f311..d4159b088 100644 --- a/crates/data-model/src/oauth2/client.rs +++ b/crates/data-model/src/oauth2/client.rs @@ -49,9 +49,6 @@ pub struct Client { /// declaring that it will restrict itself to using. pub grant_types: Vec, - /// Array of e-mail addresses of people responsible for this Client - pub contacts: Vec, - /// Name of the Client to be presented to the End-User pub client_name: Option, // TODO: translations @@ -141,7 +138,6 @@ impl Client { ], response_types: vec![OAuthAuthorizationEndpointResponseType::Code], grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken], - contacts: vec!["foo@client1.example.com".to_owned()], client_name: Some("Client 1".to_owned()), client_uri: Some(Url::parse("https://client1.example.com").unwrap()), logo_uri: Some(Url::parse("https://client1.example.com/logo.png").unwrap()), @@ -165,7 +161,6 @@ impl Client { redirect_uris: vec![Url::parse("https://client2.example.com/redirect").unwrap()], response_types: vec![OAuthAuthorizationEndpointResponseType::Code], grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken], - contacts: vec!["foo@client2.example.com".to_owned()], client_name: None, client_uri: None, logo_uri: None, diff --git a/crates/handlers/src/graphql/model/oauth.rs b/crates/handlers/src/graphql/model/oauth.rs index 4158a2416..22f9d84ec 100644 --- a/crates/handlers/src/graphql/model/oauth.rs +++ b/crates/handlers/src/graphql/model/oauth.rs @@ -182,11 +182,6 @@ impl OAuth2Client { &self.0.redirect_uris } - /// List of contacts advertised by the client. - pub async fn contacts(&self) -> &[String] { - &self.0.contacts - } - /// The application type advertised by the client. pub async fn application_type(&self) -> Option { match self.0.application_type.as_ref()? { diff --git a/crates/handlers/src/graphql/tests.rs b/crates/handlers/src/graphql/tests.rs index 6c5bb9395..1d72eb66b 100644 --- a/crates/handlers/src/graphql/tests.rs +++ b/crates/handlers/src/graphql/tests.rs @@ -38,7 +38,6 @@ async fn create_test_client(state: &TestState) -> Client { None, None, vec![], - vec![], None, None, None, @@ -358,7 +357,6 @@ async fn test_oauth2_client_credentials(pool: PgPool) { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["client_credentials"], })); @@ -582,7 +580,6 @@ async fn test_add_user(pool: PgPool) { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["client_credentials"], })); diff --git a/crates/handlers/src/oauth2/device/authorize.rs b/crates/handlers/src/oauth2/device/authorize.rs index 2c5d859c6..f1dcf2f1e 100644 --- a/crates/handlers/src/oauth2/device/authorize.rs +++ b/crates/handlers/src/oauth2/device/authorize.rs @@ -181,7 +181,6 @@ mod tests { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "none", "grant_types": ["urn:ietf:params:oauth:grant-type:device_code"], "response_types": [], diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index 633575ab0..adf693d05 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -477,7 +477,6 @@ mod tests { // Provision a client which will be used to do introspection requests let request = Request::post(OAuth2RegistrationEndpoint::PATH).json(json!({ - "contacts": ["hello@introspecting.com"], "client_uri": "https://introspecting.com/", "grant_types": [], "token_endpoint_auth_method": "client_secret_basic", @@ -491,7 +490,6 @@ mod tests { // Provision a client which will be used to generate tokens let request = Request::post(OAuth2RegistrationEndpoint::PATH).json(json!({ - "contacts": ["hello@client.com"], "client_uri": "https://client.com/", "redirect_uris": ["https://client.com/"], "response_types": ["code"], @@ -683,7 +681,6 @@ mod tests { // Provision a client which will be used to do introspection requests let request = Request::post(OAuth2RegistrationEndpoint::PATH).json(json!({ - "contacts": ["hello@introspecting.com"], "client_uri": "https://introspecting.com/", "grant_types": [], "token_endpoint_auth_method": "client_secret_basic", diff --git a/crates/handlers/src/oauth2/registration.rs b/crates/handlers/src/oauth2/registration.rs index 31cdf5c46..727320dc2 100644 --- a/crates/handlers/src/oauth2/registration.rs +++ b/crates/handlers/src/oauth2/registration.rs @@ -263,7 +263,6 @@ pub(crate) async fn post( metadata.application_type.clone(), //&metadata.response_types(), metadata.grant_types().to_vec(), - metadata.contacts.clone().unwrap_or_default(), metadata .client_name .clone() @@ -362,7 +361,6 @@ mod tests { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "application_type": "web", - "contacts": ["hello@example.com"], "client_uri": "https://example.com/", "redirect_uris": ["http://this-is-insecure.com/"], })); @@ -375,7 +373,6 @@ mod tests { // Incoherent response types let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ - "contacts": ["hello@example.com"], "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/"], "response_types": ["id_token"], @@ -390,7 +387,6 @@ mod tests { // Using a public suffix let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ - "contacts": ["hello@example.com"], "client_uri": "https://github.io/", "redirect_uris": ["https://github.io/"], "response_types": ["code"], @@ -410,7 +406,6 @@ mod tests { // Using a public suffix in a translated URL let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ - "contacts": ["hello@example.com"], "client_uri": "https://example.com/", "client_uri#fr-FR": "https://github.io/", "redirect_uris": ["https://example.com/"], @@ -438,7 +433,6 @@ mod tests { // secret let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ - "contacts": ["hello@example.com"], "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/"], "response_types": ["code"], @@ -455,7 +449,6 @@ mod tests { // return a client secret let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ - "contacts": ["hello@example.com"], "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/"], "response_types": ["code"], diff --git a/crates/handlers/src/oauth2/revoke.rs b/crates/handlers/src/oauth2/revoke.rs index eb232e31e..4d302e498 100644 --- a/crates/handlers/src/oauth2/revoke.rs +++ b/crates/handlers/src/oauth2/revoke.rs @@ -250,7 +250,6 @@ mod tests { Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(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", "refresh_token"], diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index f9035ce76..8df8d0a62 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -801,7 +801,6 @@ mod tests { Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/callback"], - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "none", "response_types": ["code"], "grant_types": ["authorization_code"], @@ -1011,7 +1010,6 @@ mod tests { Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", "redirect_uris": ["https://example.com/callback"], - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "none", "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], @@ -1133,7 +1131,6 @@ mod tests { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["client_credentials"], })); @@ -1260,7 +1257,6 @@ mod tests { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "none", "grant_types": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"], "response_types": [], @@ -1447,7 +1443,6 @@ mod tests { Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(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", "grant_types": ["password"], "response_types": [], diff --git a/crates/handlers/src/test_utils.rs b/crates/handlers/src/test_utils.rs index a9f682581..1e602411e 100644 --- a/crates/handlers/src/test_utils.rs +++ b/crates/handlers/src/test_utils.rs @@ -277,7 +277,6 @@ impl TestState { let request = Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({ "client_uri": "https://example.com/", - "contacts": ["contact@example.com"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["client_credentials"], })); diff --git a/crates/storage-pg/.sqlx/query-199819516dce285771a75a48a687b285225aeae7a4d1ca91084ae84f25dcbbec.json b/crates/storage-pg/.sqlx/query-199819516dce285771a75a48a687b285225aeae7a4d1ca91084ae84f25dcbbec.json new file mode 100644 index 000000000..49c3731c3 --- /dev/null +++ b/crates/storage-pg/.sqlx/query-199819516dce285771a75a48a687b285225aeae7a4d1ca91084ae84f25dcbbec.json @@ -0,0 +1,134 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , grant_type_client_credentials\n , grant_type_device_code\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n WHERE is_static = TRUE\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "oauth2_client_id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "encrypted_client_secret", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "application_type", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "redirect_uris", + "type_info": "TextArray" + }, + { + "ordinal": 4, + "name": "grant_type_authorization_code", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "grant_type_refresh_token", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "grant_type_client_credentials", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "grant_type_device_code", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "client_name", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "logo_uri", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "client_uri", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "policy_uri", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "tos_uri", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "jwks_uri", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "jwks", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "id_token_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 16, + "name": "userinfo_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "token_endpoint_auth_method", + "type_info": "Text" + }, + { + "ordinal": 18, + "name": "token_endpoint_auth_signing_alg", + "type_info": "Text" + }, + { + "ordinal": 19, + "name": "initiate_login_uri", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + true, + true, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true + ] + }, + "hash": "199819516dce285771a75a48a687b285225aeae7a4d1ca91084ae84f25dcbbec" +} diff --git a/crates/storage-pg/.sqlx/query-1aa4c541af7e12431a58f43a1882a14314cc1833a6be272056e09d07c21ba9ef.json b/crates/storage-pg/.sqlx/query-1aa4c541af7e12431a58f43a1882a14314cc1833a6be272056e09d07c21ba9ef.json new file mode 100644 index 000000000..7f7cd96fe --- /dev/null +++ b/crates/storage-pg/.sqlx/query-1aa4c541af7e12431a58f43a1882a14314cc1833a6be272056e09d07c21ba9ef.json @@ -0,0 +1,136 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , grant_type_client_credentials\n , grant_type_device_code\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = ANY($1::uuid[])\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "oauth2_client_id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "encrypted_client_secret", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "application_type", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "redirect_uris", + "type_info": "TextArray" + }, + { + "ordinal": 4, + "name": "grant_type_authorization_code", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "grant_type_refresh_token", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "grant_type_client_credentials", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "grant_type_device_code", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "client_name", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "logo_uri", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "client_uri", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "policy_uri", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "tos_uri", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "jwks_uri", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "jwks", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "id_token_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 16, + "name": "userinfo_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "token_endpoint_auth_method", + "type_info": "Text" + }, + { + "ordinal": 18, + "name": "token_endpoint_auth_signing_alg", + "type_info": "Text" + }, + { + "ordinal": 19, + "name": "initiate_login_uri", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "UuidArray" + ] + }, + "nullable": [ + false, + true, + true, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true + ] + }, + "hash": "1aa4c541af7e12431a58f43a1882a14314cc1833a6be272056e09d07c21ba9ef" +} diff --git a/crates/storage-pg/.sqlx/query-afef7e8248b415dd1fbf86748cbf5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3.json b/crates/storage-pg/.sqlx/query-afef7e8248b415dd1fbf86748cbf5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3.json new file mode 100644 index 000000000..1dd6fe51a --- /dev/null +++ b/crates/storage-pg/.sqlx/query-afef7e8248b415dd1fbf86748cbf5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3.json @@ -0,0 +1,136 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT oauth2_client_id\n , encrypted_client_secret\n , application_type\n , redirect_uris\n , grant_type_authorization_code\n , grant_type_refresh_token\n , grant_type_client_credentials\n , grant_type_device_code\n , client_name\n , logo_uri\n , client_uri\n , policy_uri\n , tos_uri\n , jwks_uri\n , jwks\n , id_token_signed_response_alg\n , userinfo_signed_response_alg\n , token_endpoint_auth_method\n , token_endpoint_auth_signing_alg\n , initiate_login_uri\n FROM oauth2_clients c\n\n WHERE oauth2_client_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "oauth2_client_id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "encrypted_client_secret", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "application_type", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "redirect_uris", + "type_info": "TextArray" + }, + { + "ordinal": 4, + "name": "grant_type_authorization_code", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "grant_type_refresh_token", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "grant_type_client_credentials", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "grant_type_device_code", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "client_name", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "logo_uri", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "client_uri", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "policy_uri", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "tos_uri", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "jwks_uri", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "jwks", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "id_token_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 16, + "name": "userinfo_signed_response_alg", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "token_endpoint_auth_method", + "type_info": "Text" + }, + { + "ordinal": 18, + "name": "token_endpoint_auth_signing_alg", + "type_info": "Text" + }, + { + "ordinal": 19, + "name": "initiate_login_uri", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + true, + true, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true + ] + }, + "hash": "afef7e8248b415dd1fbf86748cbf5b37fbfeaf6fd0fbdaddfdf4db7feb7546b3" +} diff --git a/crates/storage-pg/src/app_session.rs b/crates/storage-pg/src/app_session.rs index 242b15395..70ab17035 100644 --- a/crates/storage-pg/src/app_session.rs +++ b/crates/storage-pg/src/app_session.rs @@ -560,8 +560,6 @@ mod tests { None, None, vec![GrantType::AuthorizationCode], - Vec::new(), // TODO: contacts are not yet saved - // vec!["contact@example.com".to_owned()], Some("First client".to_owned()), Some("https://example.com/logo.png".parse().unwrap()), Some("https://example.com/".parse().unwrap()), diff --git a/crates/storage-pg/src/oauth2/client.rs b/crates/storage-pg/src/oauth2/client.rs index 08d948409..e34316a9f 100644 --- a/crates/storage-pg/src/oauth2/client.rs +++ b/crates/storage-pg/src/oauth2/client.rs @@ -46,7 +46,7 @@ impl<'c> PgOAuth2ClientRepository<'c> { } } -// XXX: response_types & contacts +// XXX: response_types #[allow(clippy::struct_excessive_bools)] #[derive(Debug)] struct OAuth2ClientLookup { @@ -59,7 +59,6 @@ struct OAuth2ClientLookup { grant_type_refresh_token: bool, grant_type_client_credentials: bool, grant_type_device_code: bool, - contacts: Vec, client_name: Option, logo_uri: Option, client_uri: Option, @@ -256,7 +255,6 @@ impl TryInto for OAuth2ClientLookup { redirect_uris, response_types, grant_types, - contacts: self.contacts, client_name: self.client_name, logo_uri, client_uri, @@ -297,7 +295,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { , grant_type_refresh_token , grant_type_client_credentials , grant_type_device_code - , contacts , client_name , logo_uri , client_uri @@ -349,7 +346,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { , grant_type_refresh_token , grant_type_client_credentials , grant_type_device_code - , contacts , client_name , logo_uri , client_uri @@ -400,7 +396,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { encrypted_client_secret: Option, application_type: Option, grant_types: Vec, - contacts: Vec, client_name: Option, logo_uri: Option, client_uri: Option, @@ -504,7 +499,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { OAuthAuthorizationEndpointResponseType::None, ], grant_types, - contacts, client_name, logo_uri, client_uri, @@ -614,7 +608,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { GrantType::RefreshToken, GrantType::ClientCredentials, ], - contacts: Vec::new(), client_name: None, logo_uri: None, client_uri: None, @@ -649,7 +642,6 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> { , grant_type_refresh_token , grant_type_client_credentials , grant_type_device_code - , contacts , client_name , logo_uri , client_uri diff --git a/crates/storage-pg/src/oauth2/mod.rs b/crates/storage-pg/src/oauth2/mod.rs index 6bd0b1c93..2225b1c83 100644 --- a/crates/storage-pg/src/oauth2/mod.rs +++ b/crates/storage-pg/src/oauth2/mod.rs @@ -69,8 +69,6 @@ mod tests { None, None, vec![GrantType::AuthorizationCode], - Vec::new(), // TODO: contacts are not yet saved - // vec!["contact@example.com".to_owned()], Some("Test client".to_owned()), Some("https://example.com/logo.png".parse().unwrap()), Some("https://example.com/".parse().unwrap()), @@ -424,8 +422,6 @@ mod tests { None, None, vec![GrantType::AuthorizationCode], - Vec::new(), // TODO: contacts are not yet saved - // vec!["contact@first.example.com".to_owned()], Some("First client".to_owned()), Some("https://first.example.com/logo.png".parse().unwrap()), Some("https://first.example.com/".parse().unwrap()), @@ -450,8 +446,6 @@ mod tests { None, None, vec![GrantType::AuthorizationCode], - Vec::new(), // TODO: contacts are not yet saved - // vec!["contact@second.example.com".to_owned()], Some("Second client".to_owned()), Some("https://second.example.com/logo.png".parse().unwrap()), Some("https://second.example.com/".parse().unwrap()), @@ -751,8 +745,6 @@ mod tests { None, None, vec![GrantType::AuthorizationCode], - Vec::new(), // TODO: contacts are not yet saved - // vec!["contact@example.com".to_owned()], Some("Example".to_owned()), Some("https://example.com/logo.png".parse().unwrap()), Some("https://example.com/".parse().unwrap()), diff --git a/crates/storage/src/oauth2/client.rs b/crates/storage/src/oauth2/client.rs index d0455dc50..4f1ea8913 100644 --- a/crates/storage/src/oauth2/client.rs +++ b/crates/storage/src/oauth2/client.rs @@ -74,7 +74,6 @@ pub trait OAuth2ClientRepository: Send + Sync { /// * `encrypted_client_secret`: The encrypted client secret, if any /// * `application_type`: The application type of this client /// * `grant_types`: The list of grant types this client can use - /// * `contacts`: The list of contacts for this client /// * `client_name`: The human-readable name of this client, if given /// * `logo_uri`: The URI of the logo of this client, if given /// * `client_uri`: The URI of a website of this client, if given @@ -105,7 +104,6 @@ pub trait OAuth2ClientRepository: Send + Sync { encrypted_client_secret: Option, application_type: Option, grant_types: Vec, - contacts: Vec, client_name: Option, logo_uri: Option, client_uri: Option, @@ -236,7 +234,6 @@ repository_impl!(OAuth2ClientRepository: encrypted_client_secret: Option, application_type: Option, grant_types: Vec, - contacts: Vec, client_name: Option, logo_uri: Option, client_uri: Option, diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 46a14a2ba..3fd167f10 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -881,10 +881,6 @@ type Oauth2Client implements Node { """ redirectUris: [Url!]! """ - List of contacts advertised by the client. - """ - contacts: [String!]! - """ The application type advertised by the client. """ applicationType: Oauth2ApplicationType diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index d14a4ba55..44fa3332d 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -658,8 +658,6 @@ export type Oauth2Client = Node & { clientName?: Maybe; /** Client URI advertised by the client. */ clientUri?: Maybe; - /** List of contacts advertised by the client. */ - contacts: Array; /** ID of the object. */ id: Scalars['ID']['output']; /** Logo URI advertised by the client. */ diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index db64b98c2..7b4163030 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -1698,23 +1698,6 @@ export default { }, "args": [] }, - { - "name": "contacts", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - }, - "args": [] - }, { "name": "id", "type": { From 0644040367a96c561a015a44f7dff81b6de35f6a Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 13 Sep 2024 17:23:28 +0200 Subject: [PATCH 3/3] Output the registered client metadata in the registration endpoint Fixes #2848 --- crates/data-model/src/oauth2/client.rs | 66 ++++++++++++++++++---- crates/handlers/src/oauth2/registration.rs | 22 +++++++- crates/policy/src/model.rs | 1 + crates/storage-pg/src/oauth2/client.rs | 32 +---------- 4 files changed, 76 insertions(+), 45 deletions(-) diff --git a/crates/data-model/src/oauth2/client.rs b/crates/data-model/src/oauth2/client.rs index d4159b088..73fa0dc0d 100644 --- a/crates/data-model/src/oauth2/client.rs +++ b/crates/data-model/src/oauth2/client.rs @@ -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, - /// 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, - /// 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, @@ -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, rng: &mut impl RngCore) -> Vec { 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, diff --git a/crates/handlers/src/oauth2/registration.rs b/crates/handlers/src/oauth2/registration.rs index 727320dc2..bd608dd69 100644 --- a/crates/handlers/src/oauth2/registration.rs +++ b/crates/handlers/src/oauth2/registration.rs @@ -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))) } diff --git a/crates/policy/src/model.rs b/crates/policy/src/model.rs index 02eef65a2..c8d46599d 100644 --- a/crates/policy/src/model.rs +++ b/crates/policy/src/model.rs @@ -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, pub field: Option, } diff --git a/crates/storage-pg/src/oauth2/client.rs b/crates/storage-pg/src/oauth2/client.rs index e34316a9f..dbfa24d25 100644 --- a/crates/storage-pg/src/oauth2/client.rs +++ b/crates/storage-pg/src/oauth2/client.rs @@ -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, application_type: Option, redirect_uris: Vec, - // response_types: Vec, grant_type_authorization_code: bool, grant_type_refresh_token: bool, grant_type_client_credentials: bool, @@ -100,20 +95,6 @@ impl TryInto for OAuth2ClientLookup { .source(e) })?; - let response_types = vec![ - OAuthAuthorizationEndpointResponseType::Code, - OAuthAuthorizationEndpointResponseType::IdToken, - OAuthAuthorizationEndpointResponseType::None, - ]; - /* XXX - let response_types: Result, _> = - 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 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,