You've already forked authentication-service
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:
@@ -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),
|
||||||
|
@@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -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"
|
||||||
|
Reference in New Issue
Block a user