Browse Source

Add default organization for SSO users

pull/7422/head
tom27052006 1 week ago
parent
commit
3a27382c2d
  1. 4
      .env.template
  2. 8
      src/config.rs
  3. 55
      src/sso.rs

4
.env.template

@ -501,6 +501,10 @@
## Allow unknown email verification status. Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover.
# SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION=false
## Automatically add users on their first SSO sign-in as accepted members of this organization.
## An administrator must confirm users and assign collections or groups. No invitation email is sent.
# SSO_DEFAULT_ORGANIZATION_UUID=00000000-0000-0000-0000-000000000000
## Base URL of the OIDC server (auto-discovery is used)
## - Should not include the `/.well-known/openid-configuration` part and no trailing `/`
## - ${SSO_AUTHORITY}/.well-known/openid-configuration should return a json document: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse

8
src/config.rs

@ -805,6 +805,8 @@ make_config! {
sso_signups_match_email: bool, true, def, true;
/// Allow unknown email verification status |> Allowing this with `SSO_SIGNUPS_MATCH_EMAIL=true` open potential account takeover.
sso_allow_unknown_email_verification: bool, true, def, false;
/// Default organization UUID |> Automatically add users on their first SSO sign-in as accepted members of this organization. An administrator must confirm them before they can access assigned organization data.
sso_default_organization_uuid: String, true, option;
/// Client ID
sso_client_id: String, true, def, String::new();
/// Client Key
@ -1086,6 +1088,12 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
validate_sso_master_password_policy(cfg.sso_master_password_policy.as_ref())?;
}
if let Some(org_uuid) = &cfg.sso_default_organization_uuid
&& uuid::Uuid::parse_str(org_uuid).is_err()
{
err!("`SSO_DEFAULT_ORGANIZATION_UUID` must be a valid UUID")
}
if cfg._enable_yubico {
if cfg.yubico_client_id.is_some() != cfg.yubico_secret_key.is_some() {
err!("Both `YUBICO_CLIENT_ID` and `YUBICO_SECRET_KEY` must be set for Yubikey OTP support")

55
src/sso.rs

@ -12,7 +12,10 @@ use crate::{
auth::{AuthMethod, AuthTokens, BW_EXPIRATION, DEFAULT_REFRESH_VALIDITY, TokenWrapper},
db::{
DbConn,
models::{Device, OIDCAuthenticatedUser, SsoAuth, SsoUser, User},
models::{
Device, Membership, MembershipStatus, MembershipType, OIDCAuthenticatedUser, Organization, OrganizationId,
SsoAuth, SsoUser, User, UserId,
},
},
sso_client::Client,
};
@ -328,6 +331,8 @@ pub async fn redeem(
sso_auth.delete(conn).await?;
if sso_user.is_none() {
enroll_user_in_default_organization(user, conn).await?;
let user_sso = SsoUser {
user_uuid: user.uuid.clone(),
identifier: auth_user.identifier.clone(),
@ -354,6 +359,54 @@ pub async fn redeem(
}
}
async fn enroll_user_in_default_organization(user: &User, conn: &DbConn) -> ApiResult<()> {
let Some(org_uuid) = CONFIG.sso_default_organization_uuid() else {
return Ok(());
};
let org_id = OrganizationId::from(org_uuid);
if Membership::find_by_user_and_org(&user.uuid, &org_id, conn).await.is_some() {
return Ok(());
}
if Organization::find_by_uuid(&org_id, conn).await.is_none() {
err!("The organization configured in `SSO_DEFAULT_ORGANIZATION_UUID` does not exist")
}
let membership = new_default_sso_membership(user.uuid.clone(), org_id.clone());
membership.save(conn).await?;
info!("Added SSO user {} to default organization {} pending confirmation", user.uuid, org_id);
Ok(())
}
fn new_default_sso_membership(user_uuid: UserId, org_id: OrganizationId) -> Membership {
let mut membership = Membership::new(user_uuid, org_id, None);
membership.status = MembershipStatus::Accepted as i32;
membership.atype = MembershipType::User as i32;
membership
}
#[cfg(test)]
mod default_organization_tests {
use crate::db::models::{MembershipStatus, MembershipType, OrganizationId, UserId};
use super::*;
#[test]
fn default_sso_membership_is_accepted_user_without_full_access() {
let membership = new_default_sso_membership(
UserId::from("00000000-0000-0000-0000-000000000001"),
OrganizationId::from("00000000-0000-0000-0000-000000000002"),
);
assert!(!membership.access_all);
assert_eq!(membership.status, MembershipStatus::Accepted as i32);
assert_eq!(membership.atype, MembershipType::User as i32);
assert!(membership.invited_by_email.is_none());
}
}
// We always return a refresh_token (with no refresh_token some secrets are not displayed in the web front).
// If there is no SSO refresh_token, we keep the access_token to be able to call user_info to check for validity
pub fn create_auth_tokens(

Loading…
Cancel
Save