From fc7489c5f8b022342fe1a663e4de4fdf22c43d4c Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 22 Mar 2024 10:09:44 +0100 Subject: [PATCH] Flatten the upstream_oauth2 config section --- crates/cli/src/sync.rs | 19 +- crates/config/src/sections/mod.rs | 3 +- crates/config/src/sections/upstream_oauth2.rs | 285 ++++++++++++------ docs/config.schema.json | 206 ++++--------- 4 files changed, 259 insertions(+), 254 deletions(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index dab53a68..bdbb4e7f 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -24,7 +24,7 @@ use sqlx::{postgres::PgAdvisoryLock, Connection, PgConnection}; use tracing::{error, info, info_span, warn}; fn map_import_action( - config: &mas_config::UpstreamOAuth2ImportAction, + config: mas_config::UpstreamOAuth2ImportAction, ) -> mas_data_model::UpstreamOAuthProviderImportAction { match config { mas_config::UpstreamOAuth2ImportAction::Ignore => { @@ -50,15 +50,15 @@ fn map_claims_imports( template: config.subject.template.clone(), }, localpart: mas_data_model::UpstreamOAuthProviderImportPreference { - action: map_import_action(&config.localpart.action), + action: map_import_action(config.localpart.action), template: config.localpart.template.clone(), }, displayname: mas_data_model::UpstreamOAuthProviderImportPreference { - action: map_import_action(&config.displayname.action), + action: map_import_action(config.displayname.action), template: config.displayname.template.clone(), }, email: mas_data_model::UpstreamOAuthProviderImportPreference { - action: map_import_action(&config.email.action), + action: map_import_action(config.email.action), template: config.email.template.clone(), }, verify_email: match config.email.set_email_verification { @@ -145,11 +145,10 @@ pub async fn config_sync( } let encrypted_client_secret = provider - .client_secret() + .client_secret + .as_deref() .map(|client_secret| encrypter.encrypt_to_string(client_secret.as_bytes())) .transpose()?; - let token_endpoint_auth_method = provider.client_auth_method(); - let token_endpoint_signing_alg = provider.client_auth_signing_alg(); let discovery_mode = match provider.discovery_mode { mas_config::UpstreamOAuth2DiscoveryMode::Oidc => { @@ -198,8 +197,10 @@ pub async fn config_sync( human_name: provider.human_name, brand_name: provider.brand_name, scope: provider.scope.parse()?, - token_endpoint_auth_method, - token_endpoint_signing_alg, + token_endpoint_auth_method: provider.token_endpoint_auth_method.into(), + token_endpoint_signing_alg: provider + .token_endpoint_auth_signing_alg + .clone(), client_id: provider.client_id, encrypted_client_secret, claims_imports: map_claims_imports(&provider.claims_imports), diff --git a/crates/config/src/sections/mod.rs b/crates/config/src/sections/mod.rs index ea0936be..5c69d1ac 100644 --- a/crates/config/src/sections/mod.rs +++ b/crates/config/src/sections/mod.rs @@ -53,8 +53,7 @@ pub use self::{ upstream_oauth2::{ ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode, EmailImportPreference as UpstreamOAuth2EmailImportPreference, - ImportAction as UpstreamOAuth2ImportAction, - ImportPreference as UpstreamOAuth2ImportPreference, PkceMethod as UpstreamOAuth2PkceMethod, + ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod, SetEmailVerification as UpstreamOAuth2SetEmailVerification, UpstreamOAuth2Config, }, }; diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index b73d7707..716297ce 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{collections::BTreeMap, ops::Deref}; +use std::collections::BTreeMap; use async_trait::async_trait; use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod}; use rand::Rng; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::{de::Error, Deserialize, Serialize}; use serde_with::skip_serializing_none; use ulid::Ulid; use url::Url; @@ -43,42 +43,104 @@ impl ConfigurationSection for UpstreamOAuth2Config { Ok(Self::default()) } + fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + for (index, provider) in self.providers.iter().enumerate() { + let annotate = |mut error: figment::Error| { + error.metadata = figment + .find_metadata(&format!("{root}.providers", root = Self::PATH.unwrap())) + .cloned(); + error.profile = Some(figment::Profile::Default); + error.path = vec![ + Self::PATH.unwrap().to_owned(), + "providers".to_owned(), + index.to_string(), + ]; + Err(error) + }; + + match provider.token_endpoint_auth_method { + TokenAuthMethod::None | TokenAuthMethod::PrivateKeyJwt => { + if provider.client_secret.is_some() { + return annotate(figment::Error::custom("Unexpected field `client_secret` for the selected authentication method")); + } + } + TokenAuthMethod::ClientSecretBasic + | TokenAuthMethod::ClientSecretPost + | TokenAuthMethod::ClientSecretJwt => { + if provider.client_secret.is_none() { + return annotate(figment::Error::missing_field("client_secret")); + } + } + } + + match provider.token_endpoint_auth_method { + TokenAuthMethod::None + | TokenAuthMethod::ClientSecretBasic + | TokenAuthMethod::ClientSecretPost => { + if provider.token_endpoint_auth_signing_alg.is_some() { + return annotate(figment::Error::custom( + "Unexpected field `token_endpoint_auth_signing_alg` for the selected authentication method", + )); + } + } + TokenAuthMethod::ClientSecretJwt | TokenAuthMethod::PrivateKeyJwt => { + if provider.token_endpoint_auth_signing_alg.is_none() { + return annotate(figment::Error::missing_field( + "token_endpoint_auth_signing_alg", + )); + } + } + } + } + + Ok(()) + } + fn test() -> Self { Self::default() } } /// Authentication methods used against the OAuth 2.0 provider -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "token_endpoint_auth_method", rename_all = "snake_case")] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum TokenAuthMethod { /// `none`: No authentication None, /// `client_secret_basic`: `client_id` and `client_secret` used as basic /// authorization credentials - ClientSecretBasic { client_secret: String }, + ClientSecretBasic, /// `client_secret_post`: `client_id` and `client_secret` sent in the /// request body - ClientSecretPost { client_secret: String }, + ClientSecretPost, - /// `client_secret_basic`: a `client_assertion` sent in the request body and + /// `client_secret_jwt`: a `client_assertion` sent in the request body and /// signed using the `client_secret` - ClientSecretJwt { - client_secret: String, - token_endpoint_auth_signing_alg: Option, - }, + ClientSecretJwt, - /// `client_secret_basic`: a `client_assertion` sent in the request body and + /// `private_key_jwt`: a `client_assertion` sent in the request body and /// signed by an asymmetric key - PrivateKeyJwt { - token_endpoint_auth_signing_alg: Option, - }, + PrivateKeyJwt, +} + +impl From for OAuthClientAuthenticationMethod { + fn from(method: TokenAuthMethod) -> Self { + match method { + TokenAuthMethod::None => OAuthClientAuthenticationMethod::None, + TokenAuthMethod::ClientSecretBasic => { + OAuthClientAuthenticationMethod::ClientSecretBasic + } + TokenAuthMethod::ClientSecretPost => OAuthClientAuthenticationMethod::ClientSecretPost, + TokenAuthMethod::ClientSecretJwt => OAuthClientAuthenticationMethod::ClientSecretJwt, + TokenAuthMethod::PrivateKeyJwt => OAuthClientAuthenticationMethod::PrivateKeyJwt, + } + } } /// How to handle a claim -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum ImportAction { /// Ignore the claim @@ -95,16 +157,15 @@ pub enum ImportAction { Require, } -/// What should be done with a attribute -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] -pub struct ImportPreference { - /// How to handle the attribute - #[serde(default)] - pub action: ImportAction, +impl ImportAction { + #[allow(clippy::trivially_copy_pass_by_ref)] + const fn is_default(&self) -> bool { + matches!(self, ImportAction::Ignore) + } } /// Should the email address be marked as verified -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum SetEmailVerification { /// Mark the email address as verified @@ -119,85 +180,130 @@ pub enum SetEmailVerification { Import, } +impl SetEmailVerification { + #[allow(clippy::trivially_copy_pass_by_ref)] + const fn is_default(&self) -> bool { + matches!(self, SetEmailVerification::Import) + } +} + /// What should be done for the subject attribute #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct SubjectImportPreference { /// The Jinja2 template to use for the subject attribute /// /// If not provided, the default template is `{{ user.sub }}` - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub template: Option, } +impl SubjectImportPreference { + const fn is_default(&self) -> bool { + self.template.is_none() + } +} + /// What should be done for the localpart attribute #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct LocalpartImportPreference { /// How to handle the attribute - #[serde(default)] + #[serde(default, skip_serializing_if = "ImportAction::is_default")] pub action: ImportAction, /// The Jinja2 template to use for the localpart attribute /// /// If not provided, the default template is `{{ user.preferred_username }}` - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub template: Option, } +impl LocalpartImportPreference { + const fn is_default(&self) -> bool { + self.action.is_default() && self.template.is_none() + } +} + /// What should be done for the displayname attribute #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct DisplaynameImportPreference { /// How to handle the attribute - #[serde(default)] + #[serde(default, skip_serializing_if = "ImportAction::is_default")] pub action: ImportAction, /// The Jinja2 template to use for the displayname attribute /// /// If not provided, the default template is `{{ user.name }}` - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub template: Option, } +impl DisplaynameImportPreference { + const fn is_default(&self) -> bool { + self.action.is_default() && self.template.is_none() + } +} + /// What should be done with the email attribute #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct EmailImportPreference { /// How to handle the claim - #[serde(default)] + #[serde(default, skip_serializing_if = "ImportAction::is_default")] pub action: ImportAction, /// The Jinja2 template to use for the email address attribute /// /// If not provided, the default template is `{{ user.email }}` - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub template: Option, /// Should the email address be marked as verified - #[serde(default)] + #[serde(default, skip_serializing_if = "SetEmailVerification::is_default")] pub set_email_verification: SetEmailVerification, } +impl EmailImportPreference { + const fn is_default(&self) -> bool { + self.action.is_default() + && self.template.is_none() + && self.set_email_verification.is_default() + } +} + /// How claims should be imported #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct ClaimsImports { /// How to determine the subject of the user - #[serde(default)] + #[serde(default, skip_serializing_if = "SubjectImportPreference::is_default")] pub subject: SubjectImportPreference, /// Import the localpart of the MXID - #[serde(default)] + #[serde(default, skip_serializing_if = "LocalpartImportPreference::is_default")] pub localpart: LocalpartImportPreference, /// Import the displayname of the user. - #[serde(default)] + #[serde( + default, + skip_serializing_if = "DisplaynameImportPreference::is_default" + )] pub displayname: DisplaynameImportPreference, /// Import the email address of the user based on the `email` and /// `email_verified` claims - #[serde(default)] + #[serde(default, skip_serializing_if = "EmailImportPreference::is_default")] pub email: EmailImportPreference, } +impl ClaimsImports { + const fn is_default(&self) -> bool { + self.subject.is_default() + && self.localpart.is_default() + && self.displayname.is_default() + && self.email.is_default() + } +} + /// How to discover the provider's configuration -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum DiscoveryMode { /// Use OIDC discovery with strict metadata verification @@ -211,9 +317,16 @@ pub enum DiscoveryMode { Disabled, } +impl DiscoveryMode { + #[allow(clippy::trivially_copy_pass_by_ref)] + const fn is_default(&self) -> bool { + matches!(self, DiscoveryMode::Oidc) + } +} + /// Whether to use proof key for code exchange (PKCE) when requesting and /// exchanging the token. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum PkceMethod { /// Use PKCE if the provider supports it @@ -229,6 +342,13 @@ pub enum PkceMethod { Never, } +impl PkceMethod { + #[allow(clippy::trivially_copy_pass_by_ref)] + const fn is_default(&self) -> bool { + matches!(self, PkceMethod::Auto) + } +} + #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct Provider { @@ -244,6 +364,7 @@ pub struct Provider { pub issuer: String, /// A human-readable name for the provider, that will be shown to users + #[serde(skip_serializing_if = "Option::is_none")] pub human_name: Option, /// A brand identifier used to customise the UI, e.g. `apple`, `google`, @@ -257,108 +378,72 @@ pub struct Provider { /// - `github` /// - `gitlab` /// - `twitter` + #[serde(skip_serializing_if = "Option::is_none")] pub brand_name: Option, /// The client ID to use when authenticating with the provider pub client_id: String, + /// The client secret to use when authenticating with the provider + /// + /// Used by the `client_secret_basic`, `client_secret_post`, and + /// `client_secret_jwt` methods + #[serde(skip_serializing_if = "Option::is_none")] + pub client_secret: Option, + + /// The method to authenticate the client with the provider + pub token_endpoint_auth_method: TokenAuthMethod, + + /// The JWS algorithm to use when authenticating the client with the + /// provider + /// + /// Used by the `client_secret_jwt` and `private_key_jwt` methods + #[serde(skip_serializing_if = "Option::is_none")] + pub token_endpoint_auth_signing_alg: Option, + /// The scopes to request from the provider pub scope: String, - #[serde(flatten)] - pub token_auth_method: TokenAuthMethod, - /// How to discover the provider's configuration /// - /// Defaults to use OIDC discovery with strict metadata verification - #[serde(default)] + /// Defaults to `oidc`, which uses OIDC discovery with strict metadata + /// verification + #[serde(default, skip_serializing_if = "DiscoveryMode::is_default")] pub discovery_mode: DiscoveryMode, /// Whether to use proof key for code exchange (PKCE) when requesting and /// exchanging the token. /// /// Defaults to `auto`, which uses PKCE if the provider supports it. - #[serde(default)] + #[serde(default, skip_serializing_if = "PkceMethod::is_default")] pub pkce_method: PkceMethod, /// The URL to use for the provider's authorization endpoint /// /// Defaults to the `authorization_endpoint` provided through discovery + #[serde(skip_serializing_if = "Option::is_none")] pub authorization_endpoint: Option, /// The URL to use for the provider's token endpoint /// /// Defaults to the `token_endpoint` provided through discovery + #[serde(skip_serializing_if = "Option::is_none")] pub token_endpoint: Option, /// The URL to use for getting the provider's public keys /// /// Defaults to the `jwks_uri` provided through discovery + #[serde(skip_serializing_if = "Option::is_none")] pub jwks_uri: Option, /// How claims should be imported from the `id_token` provided by the /// provider - #[serde(default)] + #[serde(default, skip_serializing_if = "ClaimsImports::is_default")] pub claims_imports: ClaimsImports, /// Additional parameters to include in the authorization request /// /// Orders of the keys are not preserved. - #[serde(default)] + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub additional_authorization_parameters: BTreeMap, } - -impl Deref for Provider { - type Target = TokenAuthMethod; - - fn deref(&self) -> &Self::Target { - &self.token_auth_method - } -} - -impl TokenAuthMethod { - #[doc(hidden)] - #[must_use] - pub fn client_auth_method(&self) -> OAuthClientAuthenticationMethod { - match self { - TokenAuthMethod::None => OAuthClientAuthenticationMethod::None, - TokenAuthMethod::ClientSecretBasic { .. } => { - OAuthClientAuthenticationMethod::ClientSecretBasic - } - TokenAuthMethod::ClientSecretPost { .. } => { - OAuthClientAuthenticationMethod::ClientSecretPost - } - TokenAuthMethod::ClientSecretJwt { .. } => { - OAuthClientAuthenticationMethod::ClientSecretJwt - } - TokenAuthMethod::PrivateKeyJwt { .. } => OAuthClientAuthenticationMethod::PrivateKeyJwt, - } - } - - #[doc(hidden)] - #[must_use] - pub fn client_secret(&self) -> Option<&str> { - match self { - TokenAuthMethod::None | TokenAuthMethod::PrivateKeyJwt { .. } => None, - TokenAuthMethod::ClientSecretBasic { client_secret } - | TokenAuthMethod::ClientSecretPost { client_secret } - | TokenAuthMethod::ClientSecretJwt { client_secret, .. } => Some(client_secret), - } - } - - #[doc(hidden)] - #[must_use] - pub fn client_auth_signing_alg(&self) -> Option { - match self { - TokenAuthMethod::ClientSecretJwt { - token_endpoint_auth_signing_alg, - .. - } - | TokenAuthMethod::PrivateKeyJwt { - token_endpoint_auth_signing_alg, - .. - } => token_endpoint_auth_signing_alg.clone(), - _ => None, - } - } -} diff --git a/docs/config.schema.json b/docs/config.schema.json index 449666fb..8eafebb1 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -1612,108 +1612,13 @@ } }, "Provider": { - "description": "Authentication methods used against the OAuth 2.0 provider", "type": "object", - "oneOf": [ - { - "description": "`none`: No authentication", - "type": "object", - "required": [ - "token_endpoint_auth_method" - ], - "properties": { - "token_endpoint_auth_method": { - "type": "string", - "enum": [ - "none" - ] - } - } - }, - { - "description": "`client_secret_basic`: `client_id` and `client_secret` used as basic authorization credentials", - "type": "object", - "required": [ - "client_secret", - "token_endpoint_auth_method" - ], - "properties": { - "token_endpoint_auth_method": { - "type": "string", - "enum": [ - "client_secret_basic" - ] - }, - "client_secret": { - "type": "string" - } - } - }, - { - "description": "`client_secret_post`: `client_id` and `client_secret` sent in the request body", - "type": "object", - "required": [ - "client_secret", - "token_endpoint_auth_method" - ], - "properties": { - "token_endpoint_auth_method": { - "type": "string", - "enum": [ - "client_secret_post" - ] - }, - "client_secret": { - "type": "string" - } - } - }, - { - "description": "`client_secret_basic`: a `client_assertion` sent in the request body and signed using the `client_secret`", - "type": "object", - "required": [ - "client_secret", - "token_endpoint_auth_method" - ], - "properties": { - "token_endpoint_auth_method": { - "type": "string", - "enum": [ - "client_secret_jwt" - ] - }, - "client_secret": { - "type": "string" - }, - "token_endpoint_auth_signing_alg": { - "$ref": "#/definitions/JsonWebSignatureAlg" - } - } - }, - { - "description": "`client_secret_basic`: a `client_assertion` sent in the request body and signed by an asymmetric key", - "type": "object", - "required": [ - "token_endpoint_auth_method" - ], - "properties": { - "token_endpoint_auth_method": { - "type": "string", - "enum": [ - "private_key_jwt" - ] - }, - "token_endpoint_auth_signing_alg": { - "$ref": "#/definitions/JsonWebSignatureAlg" - } - } - } - ], "required": [ "client_id", "id", "issuer", - "scope" + "scope", + "token_endpoint_auth_method" ], "properties": { "id": { @@ -1737,13 +1642,32 @@ "description": "The client ID to use when authenticating with the provider", "type": "string" }, + "client_secret": { + "description": "The client secret to use when authenticating with the provider\n\nUsed by the `client_secret_basic`, `client_secret_post`, and `client_secret_jwt` methods", + "type": "string" + }, + "token_endpoint_auth_method": { + "description": "The method to authenticate the client with the provider", + "allOf": [ + { + "$ref": "#/definitions/TokenAuthMethod" + } + ] + }, + "token_endpoint_auth_signing_alg": { + "description": "The JWS algorithm to use when authenticating the client with the provider\n\nUsed by the `client_secret_jwt` and `private_key_jwt` methods", + "allOf": [ + { + "$ref": "#/definitions/JsonWebSignatureAlg" + } + ] + }, "scope": { "description": "The scopes to request from the provider", "type": "string" }, "discovery_mode": { - "description": "How to discover the provider's configuration\n\nDefaults to use OIDC discovery with strict metadata verification", - "default": "oidc", + "description": "How to discover the provider's configuration\n\nDefaults to `oidc`, which uses OIDC discovery with strict metadata verification", "allOf": [ { "$ref": "#/definitions/DiscoveryMode" @@ -1752,7 +1676,6 @@ }, "pkce_method": { "description": "Whether to use proof key for code exchange (PKCE) when requesting and exchanging the token.\n\nDefaults to `auto`, which uses PKCE if the provider supports it.", - "default": "auto", "allOf": [ { "$ref": "#/definitions/PkceMethod" @@ -1776,24 +1699,6 @@ }, "claims_imports": { "description": "How claims should be imported from the `id_token` provided by the provider", - "default": { - "subject": { - "template": null - }, - "localpart": { - "action": "ignore", - "template": null - }, - "displayname": { - "action": "ignore", - "template": null - }, - "email": { - "action": "ignore", - "template": null, - "set_email_verification": "import" - } - }, "allOf": [ { "$ref": "#/definitions/ClaimsImports" @@ -1802,7 +1707,6 @@ }, "additional_authorization_parameters": { "description": "Additional parameters to include in the authorization request\n\nOrders of the keys are not preserved.", - "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1810,6 +1714,46 @@ } } }, + "TokenAuthMethod": { + "description": "Authentication methods used against the OAuth 2.0 provider", + "oneOf": [ + { + "description": "`none`: No authentication", + "type": "string", + "enum": [ + "none" + ] + }, + { + "description": "`client_secret_basic`: `client_id` and `client_secret` used as basic authorization credentials", + "type": "string", + "enum": [ + "client_secret_basic" + ] + }, + { + "description": "`client_secret_post`: `client_id` and `client_secret` sent in the request body", + "type": "string", + "enum": [ + "client_secret_post" + ] + }, + { + "description": "`client_secret_jwt`: a `client_assertion` sent in the request body and signed using the `client_secret`", + "type": "string", + "enum": [ + "client_secret_jwt" + ] + }, + { + "description": "`private_key_jwt`: a `client_assertion` sent in the request body and signed by an asymmetric key", + "type": "string", + "enum": [ + "private_key_jwt" + ] + } + ] + }, "DiscoveryMode": { "description": "How to discover the provider's configuration", "oneOf": [ @@ -1868,9 +1812,6 @@ "properties": { "subject": { "description": "How to determine the subject of the user", - "default": { - "template": null - }, "allOf": [ { "$ref": "#/definitions/SubjectImportPreference" @@ -1879,10 +1820,6 @@ }, "localpart": { "description": "Import the localpart of the MXID", - "default": { - "action": "ignore", - "template": null - }, "allOf": [ { "$ref": "#/definitions/LocalpartImportPreference" @@ -1891,10 +1828,6 @@ }, "displayname": { "description": "Import the displayname of the user.", - "default": { - "action": "ignore", - "template": null - }, "allOf": [ { "$ref": "#/definitions/DisplaynameImportPreference" @@ -1903,11 +1836,6 @@ }, "email": { "description": "Import the email address of the user based on the `email` and `email_verified` claims", - "default": { - "action": "ignore", - "template": null, - "set_email_verification": "import" - }, "allOf": [ { "$ref": "#/definitions/EmailImportPreference" @@ -1922,7 +1850,6 @@ "properties": { "template": { "description": "The Jinja2 template to use for the subject attribute\n\nIf not provided, the default template is `{{ user.sub }}`", - "default": null, "type": "string" } } @@ -1933,7 +1860,6 @@ "properties": { "action": { "description": "How to handle the attribute", - "default": "ignore", "allOf": [ { "$ref": "#/definitions/ImportAction" @@ -1942,7 +1868,6 @@ }, "template": { "description": "The Jinja2 template to use for the localpart attribute\n\nIf not provided, the default template is `{{ user.preferred_username }}`", - "default": null, "type": "string" } } @@ -1986,7 +1911,6 @@ "properties": { "action": { "description": "How to handle the attribute", - "default": "ignore", "allOf": [ { "$ref": "#/definitions/ImportAction" @@ -1995,7 +1919,6 @@ }, "template": { "description": "The Jinja2 template to use for the displayname attribute\n\nIf not provided, the default template is `{{ user.name }}`", - "default": null, "type": "string" } } @@ -2006,7 +1929,6 @@ "properties": { "action": { "description": "How to handle the claim", - "default": "ignore", "allOf": [ { "$ref": "#/definitions/ImportAction" @@ -2015,12 +1937,10 @@ }, "template": { "description": "The Jinja2 template to use for the email address attribute\n\nIf not provided, the default template is `{{ user.email }}`", - "default": null, "type": "string" }, "set_email_verification": { "description": "Should the email address be marked as verified", - "default": "import", "allOf": [ { "$ref": "#/definitions/SetEmailVerification"