1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-06 06:02:40 +03:00

Flatten the upstream_oauth2 config section

This commit is contained in:
Quentin Gliech
2024-03-22 10:09:44 +01:00
parent aa6178abe6
commit fc7489c5f8
4 changed files with 259 additions and 254 deletions

View File

@@ -24,7 +24,7 @@ use sqlx::{postgres::PgAdvisoryLock, Connection, PgConnection};
use tracing::{error, info, info_span, warn}; use tracing::{error, info, info_span, warn};
fn map_import_action( fn map_import_action(
config: &mas_config::UpstreamOAuth2ImportAction, config: mas_config::UpstreamOAuth2ImportAction,
) -> mas_data_model::UpstreamOAuthProviderImportAction { ) -> mas_data_model::UpstreamOAuthProviderImportAction {
match config { match config {
mas_config::UpstreamOAuth2ImportAction::Ignore => { mas_config::UpstreamOAuth2ImportAction::Ignore => {
@@ -50,15 +50,15 @@ fn map_claims_imports(
template: config.subject.template.clone(), template: config.subject.template.clone(),
}, },
localpart: mas_data_model::UpstreamOAuthProviderImportPreference { localpart: mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(&config.localpart.action), action: map_import_action(config.localpart.action),
template: config.localpart.template.clone(), template: config.localpart.template.clone(),
}, },
displayname: mas_data_model::UpstreamOAuthProviderImportPreference { displayname: mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(&config.displayname.action), action: map_import_action(config.displayname.action),
template: config.displayname.template.clone(), template: config.displayname.template.clone(),
}, },
email: mas_data_model::UpstreamOAuthProviderImportPreference { email: mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(&config.email.action), action: map_import_action(config.email.action),
template: config.email.template.clone(), template: config.email.template.clone(),
}, },
verify_email: match config.email.set_email_verification { verify_email: match config.email.set_email_verification {
@@ -145,11 +145,10 @@ pub async fn config_sync(
} }
let encrypted_client_secret = provider let encrypted_client_secret = provider
.client_secret() .client_secret
.as_deref()
.map(|client_secret| encrypter.encrypt_to_string(client_secret.as_bytes())) .map(|client_secret| encrypter.encrypt_to_string(client_secret.as_bytes()))
.transpose()?; .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 { let discovery_mode = match provider.discovery_mode {
mas_config::UpstreamOAuth2DiscoveryMode::Oidc => { mas_config::UpstreamOAuth2DiscoveryMode::Oidc => {
@@ -198,8 +197,10 @@ pub async fn config_sync(
human_name: provider.human_name, human_name: provider.human_name,
brand_name: provider.brand_name, brand_name: provider.brand_name,
scope: provider.scope.parse()?, scope: provider.scope.parse()?,
token_endpoint_auth_method, token_endpoint_auth_method: provider.token_endpoint_auth_method.into(),
token_endpoint_signing_alg, token_endpoint_signing_alg: provider
.token_endpoint_auth_signing_alg
.clone(),
client_id: provider.client_id, client_id: provider.client_id,
encrypted_client_secret, encrypted_client_secret,
claims_imports: map_claims_imports(&provider.claims_imports), claims_imports: map_claims_imports(&provider.claims_imports),

View File

@@ -53,8 +53,7 @@ pub use self::{
upstream_oauth2::{ upstream_oauth2::{
ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode, ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
EmailImportPreference as UpstreamOAuth2EmailImportPreference, EmailImportPreference as UpstreamOAuth2EmailImportPreference,
ImportAction as UpstreamOAuth2ImportAction, ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
ImportPreference as UpstreamOAuth2ImportPreference, PkceMethod as UpstreamOAuth2PkceMethod,
SetEmailVerification as UpstreamOAuth2SetEmailVerification, UpstreamOAuth2Config, SetEmailVerification as UpstreamOAuth2SetEmailVerification, UpstreamOAuth2Config,
}, },
}; };

View File

@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{collections::BTreeMap, ops::Deref}; use std::collections::BTreeMap;
use async_trait::async_trait; use async_trait::async_trait;
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod}; use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
use rand::Rng; use rand::Rng;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{de::Error, Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;
@@ -43,42 +43,104 @@ impl ConfigurationSection for UpstreamOAuth2Config {
Ok(Self::default()) 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 { fn test() -> Self {
Self::default() Self::default()
} }
} }
/// Authentication methods used against the OAuth 2.0 provider /// Authentication methods used against the OAuth 2.0 provider
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "token_endpoint_auth_method", rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum TokenAuthMethod { pub enum TokenAuthMethod {
/// `none`: No authentication /// `none`: No authentication
None, None,
/// `client_secret_basic`: `client_id` and `client_secret` used as basic /// `client_secret_basic`: `client_id` and `client_secret` used as basic
/// authorization credentials /// authorization credentials
ClientSecretBasic { client_secret: String }, ClientSecretBasic,
/// `client_secret_post`: `client_id` and `client_secret` sent in the /// `client_secret_post`: `client_id` and `client_secret` sent in the
/// request body /// 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` /// signed using the `client_secret`
ClientSecretJwt { ClientSecretJwt,
client_secret: String,
token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
},
/// `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 /// signed by an asymmetric key
PrivateKeyJwt { PrivateKeyJwt,
token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>, }
},
impl From<TokenAuthMethod> 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 /// 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")] #[serde(rename_all = "lowercase")]
pub enum ImportAction { pub enum ImportAction {
/// Ignore the claim /// Ignore the claim
@@ -95,16 +157,15 @@ pub enum ImportAction {
Require, Require,
} }
/// What should be done with a attribute impl ImportAction {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub struct ImportPreference { const fn is_default(&self) -> bool {
/// How to handle the attribute matches!(self, ImportAction::Ignore)
#[serde(default)] }
pub action: ImportAction,
} }
/// Should the email address be marked as verified /// 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")] #[serde(rename_all = "lowercase")]
pub enum SetEmailVerification { pub enum SetEmailVerification {
/// Mark the email address as verified /// Mark the email address as verified
@@ -119,85 +180,130 @@ pub enum SetEmailVerification {
Import, 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 /// What should be done for the subject attribute
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct SubjectImportPreference { pub struct SubjectImportPreference {
/// The Jinja2 template to use for the subject attribute /// The Jinja2 template to use for the subject attribute
/// ///
/// If not provided, the default template is `{{ user.sub }}` /// If not provided, the default template is `{{ user.sub }}`
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
} }
impl SubjectImportPreference {
const fn is_default(&self) -> bool {
self.template.is_none()
}
}
/// What should be done for the localpart attribute /// What should be done for the localpart attribute
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct LocalpartImportPreference { pub struct LocalpartImportPreference {
/// How to handle the attribute /// How to handle the attribute
#[serde(default)] #[serde(default, skip_serializing_if = "ImportAction::is_default")]
pub action: ImportAction, pub action: ImportAction,
/// The Jinja2 template to use for the localpart attribute /// The Jinja2 template to use for the localpart attribute
/// ///
/// If not provided, the default template is `{{ user.preferred_username }}` /// If not provided, the default template is `{{ user.preferred_username }}`
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
} }
impl LocalpartImportPreference {
const fn is_default(&self) -> bool {
self.action.is_default() && self.template.is_none()
}
}
/// What should be done for the displayname attribute /// What should be done for the displayname attribute
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct DisplaynameImportPreference { pub struct DisplaynameImportPreference {
/// How to handle the attribute /// How to handle the attribute
#[serde(default)] #[serde(default, skip_serializing_if = "ImportAction::is_default")]
pub action: ImportAction, pub action: ImportAction,
/// The Jinja2 template to use for the displayname attribute /// The Jinja2 template to use for the displayname attribute
/// ///
/// If not provided, the default template is `{{ user.name }}` /// If not provided, the default template is `{{ user.name }}`
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
} }
impl DisplaynameImportPreference {
const fn is_default(&self) -> bool {
self.action.is_default() && self.template.is_none()
}
}
/// What should be done with the email attribute /// What should be done with the email attribute
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct EmailImportPreference { pub struct EmailImportPreference {
/// How to handle the claim /// How to handle the claim
#[serde(default)] #[serde(default, skip_serializing_if = "ImportAction::is_default")]
pub action: ImportAction, pub action: ImportAction,
/// The Jinja2 template to use for the email address attribute /// The Jinja2 template to use for the email address attribute
/// ///
/// If not provided, the default template is `{{ user.email }}` /// If not provided, the default template is `{{ user.email }}`
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>, pub template: Option<String>,
/// Should the email address be marked as verified /// Should the email address be marked as verified
#[serde(default)] #[serde(default, skip_serializing_if = "SetEmailVerification::is_default")]
pub set_email_verification: SetEmailVerification, 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 /// How claims should be imported
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct ClaimsImports { pub struct ClaimsImports {
/// How to determine the subject of the user /// How to determine the subject of the user
#[serde(default)] #[serde(default, skip_serializing_if = "SubjectImportPreference::is_default")]
pub subject: SubjectImportPreference, pub subject: SubjectImportPreference,
/// Import the localpart of the MXID /// Import the localpart of the MXID
#[serde(default)] #[serde(default, skip_serializing_if = "LocalpartImportPreference::is_default")]
pub localpart: LocalpartImportPreference, pub localpart: LocalpartImportPreference,
/// Import the displayname of the user. /// Import the displayname of the user.
#[serde(default)] #[serde(
default,
skip_serializing_if = "DisplaynameImportPreference::is_default"
)]
pub displayname: DisplaynameImportPreference, pub displayname: DisplaynameImportPreference,
/// Import the email address of the user based on the `email` and /// Import the email address of the user based on the `email` and
/// `email_verified` claims /// `email_verified` claims
#[serde(default)] #[serde(default, skip_serializing_if = "EmailImportPreference::is_default")]
pub email: EmailImportPreference, 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 /// 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")] #[serde(rename_all = "snake_case")]
pub enum DiscoveryMode { pub enum DiscoveryMode {
/// Use OIDC discovery with strict metadata verification /// Use OIDC discovery with strict metadata verification
@@ -211,9 +317,16 @@ pub enum DiscoveryMode {
Disabled, 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 /// Whether to use proof key for code exchange (PKCE) when requesting and
/// exchanging the token. /// exchanging the token.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum PkceMethod { pub enum PkceMethod {
/// Use PKCE if the provider supports it /// Use PKCE if the provider supports it
@@ -229,6 +342,13 @@ pub enum PkceMethod {
Never, Never,
} }
impl PkceMethod {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default(&self) -> bool {
matches!(self, PkceMethod::Auto)
}
}
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Provider { pub struct Provider {
@@ -244,6 +364,7 @@ pub struct Provider {
pub issuer: String, pub issuer: String,
/// A human-readable name for the provider, that will be shown to users /// A human-readable name for the provider, that will be shown to users
#[serde(skip_serializing_if = "Option::is_none")]
pub human_name: Option<String>, pub human_name: Option<String>,
/// A brand identifier used to customise the UI, e.g. `apple`, `google`, /// A brand identifier used to customise the UI, e.g. `apple`, `google`,
@@ -257,108 +378,72 @@ pub struct Provider {
/// - `github` /// - `github`
/// - `gitlab` /// - `gitlab`
/// - `twitter` /// - `twitter`
#[serde(skip_serializing_if = "Option::is_none")]
pub brand_name: Option<String>, pub brand_name: Option<String>,
/// The client ID to use when authenticating with the provider /// The client ID to use when authenticating with the provider
pub client_id: String, 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<String>,
/// 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<JsonWebSignatureAlg>,
/// The scopes to request from the provider /// The scopes to request from the provider
pub scope: String, pub scope: String,
#[serde(flatten)]
pub token_auth_method: TokenAuthMethod,
/// How to discover the provider's configuration /// How to discover the provider's configuration
/// ///
/// Defaults to use OIDC discovery with strict metadata verification /// Defaults to `oidc`, which uses OIDC discovery with strict metadata
#[serde(default)] /// verification
#[serde(default, skip_serializing_if = "DiscoveryMode::is_default")]
pub discovery_mode: DiscoveryMode, pub discovery_mode: DiscoveryMode,
/// Whether to use proof key for code exchange (PKCE) when requesting and /// Whether to use proof key for code exchange (PKCE) when requesting and
/// exchanging the token. /// exchanging the token.
/// ///
/// Defaults to `auto`, which uses PKCE if the provider supports it. /// 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, pub pkce_method: PkceMethod,
/// The URL to use for the provider's authorization endpoint /// The URL to use for the provider's authorization endpoint
/// ///
/// Defaults to the `authorization_endpoint` provided through discovery /// Defaults to the `authorization_endpoint` provided through discovery
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization_endpoint: Option<Url>, pub authorization_endpoint: Option<Url>,
/// The URL to use for the provider's token endpoint /// The URL to use for the provider's token endpoint
/// ///
/// Defaults to the `token_endpoint` provided through discovery /// Defaults to the `token_endpoint` provided through discovery
#[serde(skip_serializing_if = "Option::is_none")]
pub token_endpoint: Option<Url>, pub token_endpoint: Option<Url>,
/// The URL to use for getting the provider's public keys /// The URL to use for getting the provider's public keys
/// ///
/// Defaults to the `jwks_uri` provided through discovery /// Defaults to the `jwks_uri` provided through discovery
#[serde(skip_serializing_if = "Option::is_none")]
pub jwks_uri: Option<Url>, pub jwks_uri: Option<Url>,
/// How claims should be imported from the `id_token` provided by the /// How claims should be imported from the `id_token` provided by the
/// provider /// provider
#[serde(default)] #[serde(default, skip_serializing_if = "ClaimsImports::is_default")]
pub claims_imports: ClaimsImports, pub claims_imports: ClaimsImports,
/// Additional parameters to include in the authorization request /// Additional parameters to include in the authorization request
/// ///
/// Orders of the keys are not preserved. /// Orders of the keys are not preserved.
#[serde(default)] #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub additional_authorization_parameters: BTreeMap<String, String>, pub additional_authorization_parameters: BTreeMap<String, String>,
} }
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<JsonWebSignatureAlg> {
match self {
TokenAuthMethod::ClientSecretJwt {
token_endpoint_auth_signing_alg,
..
}
| TokenAuthMethod::PrivateKeyJwt {
token_endpoint_auth_signing_alg,
..
} => token_endpoint_auth_signing_alg.clone(),
_ => None,
}
}
}

View File

@@ -1612,108 +1612,13 @@
} }
}, },
"Provider": { "Provider": {
"description": "Authentication methods used against the OAuth 2.0 provider",
"type": "object", "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": [ "required": [
"client_id", "client_id",
"id", "id",
"issuer", "issuer",
"scope" "scope",
"token_endpoint_auth_method"
], ],
"properties": { "properties": {
"id": { "id": {
@@ -1737,13 +1642,32 @@
"description": "The client ID to use when authenticating with the provider", "description": "The client ID to use when authenticating with the provider",
"type": "string" "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": { "scope": {
"description": "The scopes to request from the provider", "description": "The scopes to request from the provider",
"type": "string" "type": "string"
}, },
"discovery_mode": { "discovery_mode": {
"description": "How to discover the provider's configuration\n\nDefaults to use OIDC discovery with strict metadata verification", "description": "How to discover the provider's configuration\n\nDefaults to `oidc`, which uses OIDC discovery with strict metadata verification",
"default": "oidc",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/DiscoveryMode" "$ref": "#/definitions/DiscoveryMode"
@@ -1752,7 +1676,6 @@
}, },
"pkce_method": { "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.", "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": [ "allOf": [
{ {
"$ref": "#/definitions/PkceMethod" "$ref": "#/definitions/PkceMethod"
@@ -1776,24 +1699,6 @@
}, },
"claims_imports": { "claims_imports": {
"description": "How claims should be imported from the `id_token` provided by the provider", "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": [ "allOf": [
{ {
"$ref": "#/definitions/ClaimsImports" "$ref": "#/definitions/ClaimsImports"
@@ -1802,7 +1707,6 @@
}, },
"additional_authorization_parameters": { "additional_authorization_parameters": {
"description": "Additional parameters to include in the authorization request\n\nOrders of the keys are not preserved.", "description": "Additional parameters to include in the authorization request\n\nOrders of the keys are not preserved.",
"default": {},
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "string" "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": { "DiscoveryMode": {
"description": "How to discover the provider's configuration", "description": "How to discover the provider's configuration",
"oneOf": [ "oneOf": [
@@ -1868,9 +1812,6 @@
"properties": { "properties": {
"subject": { "subject": {
"description": "How to determine the subject of the user", "description": "How to determine the subject of the user",
"default": {
"template": null
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/SubjectImportPreference" "$ref": "#/definitions/SubjectImportPreference"
@@ -1879,10 +1820,6 @@
}, },
"localpart": { "localpart": {
"description": "Import the localpart of the MXID", "description": "Import the localpart of the MXID",
"default": {
"action": "ignore",
"template": null
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/LocalpartImportPreference" "$ref": "#/definitions/LocalpartImportPreference"
@@ -1891,10 +1828,6 @@
}, },
"displayname": { "displayname": {
"description": "Import the displayname of the user.", "description": "Import the displayname of the user.",
"default": {
"action": "ignore",
"template": null
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/DisplaynameImportPreference" "$ref": "#/definitions/DisplaynameImportPreference"
@@ -1903,11 +1836,6 @@
}, },
"email": { "email": {
"description": "Import the email address of the user based on the `email` and `email_verified` claims", "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": [ "allOf": [
{ {
"$ref": "#/definitions/EmailImportPreference" "$ref": "#/definitions/EmailImportPreference"
@@ -1922,7 +1850,6 @@
"properties": { "properties": {
"template": { "template": {
"description": "The Jinja2 template to use for the subject attribute\n\nIf not provided, the default template is `{{ user.sub }}`", "description": "The Jinja2 template to use for the subject attribute\n\nIf not provided, the default template is `{{ user.sub }}`",
"default": null,
"type": "string" "type": "string"
} }
} }
@@ -1933,7 +1860,6 @@
"properties": { "properties": {
"action": { "action": {
"description": "How to handle the attribute", "description": "How to handle the attribute",
"default": "ignore",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/ImportAction" "$ref": "#/definitions/ImportAction"
@@ -1942,7 +1868,6 @@
}, },
"template": { "template": {
"description": "The Jinja2 template to use for the localpart attribute\n\nIf not provided, the default template is `{{ user.preferred_username }}`", "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" "type": "string"
} }
} }
@@ -1986,7 +1911,6 @@
"properties": { "properties": {
"action": { "action": {
"description": "How to handle the attribute", "description": "How to handle the attribute",
"default": "ignore",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/ImportAction" "$ref": "#/definitions/ImportAction"
@@ -1995,7 +1919,6 @@
}, },
"template": { "template": {
"description": "The Jinja2 template to use for the displayname attribute\n\nIf not provided, the default template is `{{ user.name }}`", "description": "The Jinja2 template to use for the displayname attribute\n\nIf not provided, the default template is `{{ user.name }}`",
"default": null,
"type": "string" "type": "string"
} }
} }
@@ -2006,7 +1929,6 @@
"properties": { "properties": {
"action": { "action": {
"description": "How to handle the claim", "description": "How to handle the claim",
"default": "ignore",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/ImportAction" "$ref": "#/definitions/ImportAction"
@@ -2015,12 +1937,10 @@
}, },
"template": { "template": {
"description": "The Jinja2 template to use for the email address attribute\n\nIf not provided, the default template is `{{ user.email }}`", "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" "type": "string"
}, },
"set_email_verification": { "set_email_verification": {
"description": "Should the email address be marked as verified", "description": "Should the email address be marked as verified",
"default": "import",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/SetEmailVerification" "$ref": "#/definitions/SetEmailVerification"