1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-02-07 11:17:02 +01:00

Split SsoUser::find_by_identifier_or_email

Dieser Commit ist enthalten in:
Timshel 2025-01-02 17:45:03 +01:00
Ursprung 0e8a462eb0
Commit bee619ff52
2 geänderte Dateien mit 80 neuen und 92 gelöschten Zeilen

Datei anzeigen

@ -162,10 +162,21 @@ async fn _sso_login(data: ConnectData, user_uuid: &mut Option<String>, conn: &mu
}; };
let user_infos = sso::exchange_code(code, conn).await?; let user_infos = sso::exchange_code(code, conn).await?;
let user_with_sso = match SsoUser::find_by_identifier(&user_infos.identifier, conn).await {
// Will trigger 2FA flow if needed None => match SsoUser::find_by_mail(&user_infos.email, conn).await {
let user_data = match SsoUser::find_by_identifier_or_email(&user_infos.identifier, &user_infos.email, conn).await {
None => None, None => None,
Some((user, Some(_))) => {
error!(
"Login failure ({}), existing SSO user ({}) with same email ({})",
user_infos.identifier, user.uuid, user.email
);
err_silent!(
"Existing SSO user with same email",
ErrorEvent {
event: EventType::UserFailedLogIn
}
)
}
Some((user, None)) if user.private_key.is_some() && !CONFIG.sso_signups_match_email() => { Some((user, None)) if user.private_key.is_some() && !CONFIG.sso_signups_match_email() => {
error!( error!(
"Login failure ({}), existing non SSO user ({}) with same email ({}) and association is disabled", "Login failure ({}), existing non SSO user ({}) with same email ({}) and association is disabled",
@ -178,40 +189,14 @@ async fn _sso_login(data: ConnectData, user_uuid: &mut Option<String>, conn: &mu
} }
) )
} }
Some((user, Some(sso_user))) if sso_user.identifier != user_infos.identifier => { Some((user, None)) => Some((user, None)),
error!( },
"Login failure ({}), existing SSO user ({}) with same email ({})", Some((user, sso_user)) => Some((user, Some(sso_user))),
user_infos.identifier, user.uuid, user.email
);
err_silent!(
"Existing SSO user with same email",
ErrorEvent {
event: EventType::UserFailedLogIn
}
)
}
Some((user, _)) if !user.enabled => {
err!(
"This user has been disabled",
format!("IP: {}. Username: {}.", ip.ip, user.name),
ErrorEvent {
event: EventType::UserFailedLogIn
}
)
}
Some((user, sso_user)) => {
let (mut device, new_device) = get_device(&data, conn, &user).await?;
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?;
Some((user, device, new_device, twofactor_token, sso_user))
}
}; };
// We passed 2FA get full user informations
let auth_user = sso::redeem(&user_infos.state, conn).await?;
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let (user, mut device, new_device, twofactor_token, sso_user) = match user_data { // Will trigger 2FA flow if needed
let (user, mut device, new_device, twofactor_token, sso_user) = match user_with_sso {
None => { None => {
if !CONFIG.is_email_domain_allowed(&user_infos.email) { if !CONFIG.is_email_domain_allowed(&user_infos.email) {
err!( err!(
@ -247,7 +232,20 @@ async fn _sso_login(data: ConnectData, user_uuid: &mut Option<String>, conn: &mu
(user, device, new_device, None, None) (user, device, new_device, None, None)
} }
Some((mut user, device, new_device, twofactor_token, sso_user)) if user.private_key.is_none() => { Some((user, _)) if !user.enabled => {
err!(
"This user has been disabled",
format!("IP: {}. Username: {}.", ip.ip, user.name),
ErrorEvent {
event: EventType::UserFailedLogIn
}
)
}
Some((mut user, sso_user)) => {
let (mut device, new_device) = get_device(&data, conn, &user).await?;
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?;
if user.private_key.is_none() {
// User was invited a stub was created // User was invited a stub was created
user.verified_at = Some(now); user.verified_at = Some(now);
if let Some(user_name) = user_infos.user_name { if let Some(user_name) = user_infos.user_name {
@ -259,19 +257,22 @@ async fn _sso_login(data: ConnectData, user_uuid: &mut Option<String>, conn: &mu
} }
user.save(conn).await?; user.save(conn).await?;
(user, device, new_device, twofactor_token, sso_user)
} }
Some((user, device, new_device, twofactor_token, sso_user)) => {
if user.email != user_infos.email { if user.email != user_infos.email {
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
mail::send_sso_change_email(&user_infos.email).await?; mail::send_sso_change_email(&user_infos.email).await?;
} }
info!("User {} email changed in SSO provider from {} to {}", user.uuid, user.email, user_infos.email); info!("User {} email changed in SSO provider from {} to {}", user.uuid, user.email, user_infos.email);
} }
(user, device, new_device, twofactor_token, sso_user) (user, device, new_device, twofactor_token, sso_user)
} }
}; };
// We passed 2FA get full user informations
let auth_user = sso::redeem(&user_infos.state, conn).await?;
if sso_user.is_none() { if sso_user.is_none() {
let user_sso = SsoUser { let user_sso = SsoUser {
user_uuid: user.uuid.clone(), user_uuid: user.uuid.clone(),

Datei anzeigen

@ -1,7 +1,6 @@
use crate::util::{format_date, get_uuid, retry}; use crate::util::{format_date, get_uuid, retry};
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use serde_json::Value; use serde_json::Value;
use std::cmp::Ordering;
use crate::crypto; use crate::crypto;
use crate::CONFIG; use crate::CONFIG;
@ -487,41 +486,29 @@ impl SsoUser {
} }
} }
// Written as an union to make the query more lisible than using an `or_filter`. pub async fn find_by_identifier(identifier: &str, conn: &DbConn) -> Option<(User, SsoUser)> {
// If there is a match on identifier and email we want the identifier match. db_run! {conn: {
// We sort results in code since UNION does not garanty order and DBs order NULL differently. users::table
pub async fn find_by_identifier_or_email( .inner_join(sso_users::table)
identifier: &str, .select(<(UserDb, SsoUserDb)>::as_select())
mail: &str, .filter(sso_users::identifier.eq(identifier))
conn: &DbConn, .first::<(UserDb, SsoUserDb)>(conn)
) -> Option<(User, Option<SsoUser>)> { .ok()
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
}}
}
pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<(User, Option<SsoUser>)> {
let lower_mail = mail.to_lowercase(); let lower_mail = mail.to_lowercase();
db_run! {conn: { db_run! {conn: {
let mut res = users::table
.inner_join(sso_users::table)
.select(<(UserDb, Option<SsoUserDb>)>::as_select())
.filter(sso_users::identifier.eq(identifier))
.union(
users::table users::table
.left_join(sso_users::table) .left_join(sso_users::table)
.select(<(UserDb, Option<SsoUserDb>)>::as_select()) .select(<(UserDb, Option<SsoUserDb>)>::as_select())
.filter(users::email.eq(lower_mail)) .filter(users::email.eq(lower_mail))
) .first::<(UserDb, Option<SsoUserDb>)>(conn)
.load(conn) .ok()
.expect("Error searching user by SSO identifier and email")
.into_iter()
.map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) }) .map(|(user, sso_user)| { (user.from_db(), sso_user.from_db()) })
.collect::<Vec<(User, Option<SsoUser>)>>();
res.sort_by(|(_, sso_user), _| {
match sso_user {
Some(db_sso_user) if db_sso_user.identifier == identifier => Ordering::Less,
_ => Ordering::Greater,
}
});
res.into_iter().next()
}} }}
} }
} }