1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Make the email verification state more configurable on upstream OAuth 2.0 registration

This also marks the email as primary
This commit is contained in:
Quentin Gliech
2023-08-31 11:31:06 +02:00
parent 8e5ebcd03f
commit ae3213fe87
8 changed files with 162 additions and 18 deletions

View File

@ -26,10 +26,10 @@ use tracing::{info, info_span, warn};
use crate::util::database_connection_from_config; use crate::util::database_connection_from_config;
fn map_import_preference( fn map_import_action(
config: &mas_config::UpstreamOAuth2ImportPreference, config: &mas_config::UpstreamOAuth2ImportAction,
) -> mas_data_model::UpstreamOAuthProviderImportPreference { ) -> mas_data_model::UpstreamOAuthProviderImportAction {
let action = match &config.action { match config {
mas_config::UpstreamOAuth2ImportAction::Ignore => { mas_config::UpstreamOAuth2ImportAction::Ignore => {
mas_data_model::UpstreamOAuthProviderImportAction::Ignore mas_data_model::UpstreamOAuthProviderImportAction::Ignore
} }
@ -42,9 +42,15 @@ fn map_import_preference(
mas_config::UpstreamOAuth2ImportAction::Require => { mas_config::UpstreamOAuth2ImportAction::Require => {
mas_data_model::UpstreamOAuthProviderImportAction::Require mas_data_model::UpstreamOAuthProviderImportAction::Require
} }
}; }
}
mas_data_model::UpstreamOAuthProviderImportPreference { action } fn map_import_preference(
config: &mas_config::UpstreamOAuth2ImportPreference,
) -> mas_data_model::UpstreamOAuthProviderImportPreference {
mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(&config.action),
}
} }
fn map_claims_imports( fn map_claims_imports(
@ -64,7 +70,25 @@ fn map_claims_imports(
email: config email: config
.email .email
.as_ref() .as_ref()
.map(map_import_preference) .map(|c| mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(&c.action),
})
.unwrap_or_default(),
// XXX: this is a bit ugly
verify_email: config
.email
.as_ref()
.map(|c| match c.set_email_verification {
mas_config::UpstreamOAuth2SetEmailVerification::Always => {
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Always
}
mas_config::UpstreamOAuth2SetEmailVerification::Never => {
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Never
}
mas_config::UpstreamOAuth2SetEmailVerification::Import => {
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Import
}
})
.unwrap_or_default(), .unwrap_or_default(),
} }
} }

View File

@ -49,8 +49,11 @@ pub use self::{
}, },
templates::TemplatesConfig, templates::TemplatesConfig,
upstream_oauth2::{ upstream_oauth2::{
ClaimsImports as UpstreamOAuth2ClaimsImports, ImportAction as UpstreamOAuth2ImportAction, ClaimsImports as UpstreamOAuth2ClaimsImports,
ImportPreference as UpstreamOAuth2ImportPreference, UpstreamOAuth2Config, EmailImportPreference as UpstreamOAuth2EmailImportPreference,
ImportAction as UpstreamOAuth2ImportAction,
ImportPreference as UpstreamOAuth2ImportPreference,
SetEmailVerification as UpstreamOAuth2SetEmailVerification, UpstreamOAuth2Config,
}, },
}; };
use crate::util::ConfigurationSection; use crate::util::ConfigurationSection;

View File

@ -104,6 +104,34 @@ pub struct ImportPreference {
pub action: ImportAction, pub action: ImportAction,
} }
/// Should the email address be marked as verified
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum SetEmailVerification {
/// Mark the email address as verified
Always,
/// Don't mark the email address as verified
Never,
/// Mark the email address as verified if the upstream provider says it is
/// through the `email_verified` claim
#[default]
Import,
}
/// What should be done with the email claim
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct EmailImportPreference {
/// How to handle the claim
#[serde(default)]
pub action: ImportAction,
/// Should the email address be marked as verified
#[serde(default)]
pub set_email_verification: SetEmailVerification,
}
/// 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 {
@ -118,7 +146,7 @@ pub struct ClaimsImports {
/// 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)]
pub email: Option<ImportPreference>, pub email: Option<EmailImportPreference>,
} }
#[skip_serializing_none] #[skip_serializing_none]

View File

@ -46,9 +46,10 @@ pub use self::{
AccessToken, AccessTokenState, RefreshToken, RefreshTokenState, TokenFormatError, TokenType, AccessToken, AccessTokenState, RefreshToken, RefreshTokenState, TokenFormatError, TokenType,
}, },
upstream_oauth2::{ upstream_oauth2::{
UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState, UpsreamOAuthProviderSetEmailVerification, UpstreamOAuthAuthorizationSession,
UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, UpstreamOAuthAuthorizationSessionState, UpstreamOAuthLink, UpstreamOAuthProvider,
UpstreamOAuthProviderImportAction, UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderImportAction,
UpstreamOAuthProviderImportPreference,
}, },
users::{ users::{
Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail, Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail,

View File

@ -21,7 +21,8 @@ pub use self::{
provider::{ provider::{
ClaimsImports as UpstreamOAuthProviderClaimsImports, ClaimsImports as UpstreamOAuthProviderClaimsImports,
ImportAction as UpstreamOAuthProviderImportAction, ImportAction as UpstreamOAuthProviderImportAction,
ImportPreference as UpstreamOAuthProviderImportPreference, UpstreamOAuthProvider, ImportPreference as UpstreamOAuthProviderImportPreference,
SetEmailVerification as UpsreamOAuthProviderSetEmailVerification, UpstreamOAuthProvider,
}, },
session::{UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState}, session::{UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState},
}; };

View File

@ -31,6 +31,32 @@ pub struct UpstreamOAuthProvider {
pub claims_imports: ClaimsImports, pub claims_imports: ClaimsImports,
} }
/// Whether to set the email as verified when importing it from the upstream
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SetEmailVerification {
/// Set the email as verified
Always,
/// Never set the email as verified
Never,
/// Set the email as verified if the upstream provider claims it is verified
#[default]
Import,
}
impl SetEmailVerification {
#[must_use]
pub fn should_mark_as_verified(&self, upstream_verified: bool) -> bool {
match self {
Self::Always => true,
Self::Never => false,
Self::Import => upstream_verified,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ClaimsImports { pub struct ClaimsImports {
#[serde(default)] #[serde(default)]
@ -41,6 +67,9 @@ pub struct ClaimsImports {
#[serde(default)] #[serde(default)]
pub email: ImportPreference, pub email: ImportPreference,
#[serde(default)]
pub verify_email: SetEmailVerification,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]

View File

@ -527,11 +527,19 @@ pub(crate) async fn post(
.add(&mut rng, &clock, &user, email) .add(&mut rng, &clock, &user, email)
.await?; .await?;
// Mark the email as verified if the upstream provider says it is. // Mark the email as verified according to the policy and whether the provider
if payload.email_verified { // claims it is, and make it the primary email.
repo.user_email() if provider
.claims_imports
.verify_email
.should_mark_as_verified(payload.email_verified)
{
let user_email = repo
.user_email()
.mark_as_verified(&clock, user_email) .mark_as_verified(&clock, user_email)
.await?; .await?;
repo.user_email().set_as_primary(&user_email).await?;
} }
} }

View File

@ -307,7 +307,7 @@
"default": null, "default": null,
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/ImportPreference" "$ref": "#/definitions/EmailImportPreference"
} }
] ]
}, },
@ -687,6 +687,30 @@
} }
} }
}, },
"EmailImportPreference": {
"description": "What should be done with the email claim",
"type": "object",
"properties": {
"action": {
"description": "How to handle the claim",
"default": "ignore",
"allOf": [
{
"$ref": "#/definitions/ImportAction"
}
]
},
"set_email_verification": {
"description": "Should the email address be marked as verified",
"default": "import",
"allOf": [
{
"$ref": "#/definitions/SetEmailVerification"
}
]
}
}
},
"EmailSmtpMode": { "EmailSmtpMode": {
"description": "Encryption mode to use", "description": "Encryption mode to use",
"oneOf": [ "oneOf": [
@ -1752,6 +1776,32 @@
} }
} }
}, },
"SetEmailVerification": {
"description": "Should the email address be marked as verified",
"oneOf": [
{
"description": "Mark the email address as verified",
"type": "string",
"enum": [
"always"
]
},
{
"description": "Don't mark the email address as verified",
"type": "string",
"enum": [
"never"
]
},
{
"description": "Mark the email address as verified if the upstream provider says it is through the `email_verified` claim",
"type": "string",
"enum": [
"import"
]
}
]
},
"TelemetryConfig": { "TelemetryConfig": {
"description": "Configuration related to sending monitoring data", "description": "Configuration related to sending monitoring data",
"type": "object", "type": "object",