Skip to main content

alibi_plugins/sso/
organization_provisioning.rs

1//! Organization membership policies for SSO provider-bound identities.
2#[derive(Clone, Debug)]
3pub struct SsoOrganizationProvisioning {
4    pub disabled: bool,
5    pub default_role: String,
6    pub get_role: Option<SsoRoleResolver>,
7}
8impl Default for SsoOrganizationProvisioning {
9    fn default() -> Self {
10        Self {
11            disabled: false,
12            default_role: "member".into(),
13            get_role: None,
14        }
15    }
16}
17
18impl super::SsoPlugin {
19    pub(super) async fn provision_organization<S: alibi_core::AuthSchema>(
20        &self,
21        user: &alibi_core::wire::UserView,
22        provider: &alibi_core::store::SsoProvider,
23        ctx: &alibi_core::AuthContext<S>,
24        profile: &serde_json::Value,
25        token: Option<&crate::oauth::OAuthTokenSet>,
26    ) -> alibi_core::AuthResult<()> {
27        if self.config.organization_provisioning.disabled
28            || ctx
29                .get_metadata(crate::organization::METADATA_ENABLED)
30                .is_none()
31        {
32            return Ok(());
33        }
34        let Some(organization_id) = provider
35            .organization_id
36            .as_deref()
37            .filter(|id| !id.is_empty())
38        else {
39            return Ok(());
40        };
41        if ctx
42            .database
43            .get_member(organization_id, &user.id)
44            .await?
45            .is_some()
46        {
47            return Ok(());
48        }
49        if let Some(email) = user.email.as_deref()
50            && ctx
51                .database
52                .get_pending_invitation(organization_id, &email.to_lowercase())
53                .await?
54                .is_some()
55        {
56            return Ok(());
57        }
58        let role = if let Some(resolver) = &self.config.organization_provisioning.get_role {
59            resolver
60                .0
61                .get_role(super::SsoProvisioningData {
62                    user: user.clone(),
63                    user_info: profile.clone(),
64                    token: token.cloned(),
65                    provider: provider.clone(),
66                })
67                .await?
68        } else {
69            let role = &self.config.organization_provisioning.default_role;
70            if role.is_empty() {
71                "member".into()
72            } else {
73                role.clone()
74            }
75        };
76        _ = ctx
77            .database
78            .create_member(alibi_core::CreateMember::new(
79                organization_id,
80                &user.id,
81                role,
82            ))
83            .await?;
84        Ok(())
85    }
86}
87
88/// An application role policy sees the authenticated user and protocol profile.
89#[async_trait::async_trait]
90pub trait SsoOrganizationRoleResolver: Send + Sync {
91    async fn get_role(&self, data: super::SsoProvisioningData) -> alibi_core::AuthResult<String>;
92}
93#[derive(Clone)]
94pub struct SsoRoleResolver(pub std::sync::Arc<dyn SsoOrganizationRoleResolver>);
95impl std::fmt::Debug for SsoRoleResolver {
96    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        formatter
98            .debug_struct("SsoRoleResolver")
99            .finish_non_exhaustive()
100    }
101}