Skip to main content

alibi_plugins/sso/
mutation_guard.rs

1//! Application mutation admission using the provider's locked transaction.
2use alibi_core::{AuthResult, AuthSchema, AuthTransaction};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::{any::Any, sync::Arc};
6#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "camelCase")]
8pub enum SsoMutationAction {
9    Update,
10    Delete,
11}
12/// Provider identity supplied to an application policy without credentials.
13#[derive(Clone, Debug, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct SsoMutationProvider {
16    pub id: String,
17    pub provider_id: String,
18    pub organization_id: Option<String>,
19}
20/// The current provider reference and proposed mutation's boundary classification.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct SsoProviderMutation {
24    pub action: SsoMutationAction,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub is_authentication_boundary_change: Option<bool>,
27    pub provider: SsoMutationProvider,
28    pub provider_reference: Value,
29}
30#[async_trait::async_trait]
31pub trait SsoProviderMutationGuard<S: AuthSchema>: Send + Sync {
32    /// Runs inside the provider mutation transaction. An error rejects and rolls back it.
33    async fn guard(
34        &self,
35        mutation: SsoProviderMutation,
36        database: &dyn AuthTransaction<S>,
37    ) -> AuthResult<()>;
38}
39/// A schema-bound application policy retained in non-generic plugin configuration.
40#[derive(Clone)]
41pub struct SsoMutationGuard {
42    callback: Arc<dyn Any + Send + Sync>,
43}
44impl SsoMutationGuard {
45    pub fn new<S: AuthSchema>(callback: Arc<dyn SsoProviderMutationGuard<S>>) -> Self {
46        Self {
47            callback: Arc::new(callback),
48        }
49    }
50    pub(super) fn resolve<S: AuthSchema>(
51        &self,
52    ) -> AuthResult<Arc<dyn SsoProviderMutationGuard<S>>> {
53        self.callback
54            .downcast_ref::<Arc<dyn SsoProviderMutationGuard<S>>>()
55            .cloned()
56            .ok_or_else(|| {
57                alibi_core::AuthError::config(
58                    "SSO mutation guard schema does not match the application schema",
59                )
60            })
61    }
62}
63impl std::fmt::Debug for SsoMutationGuard {
64    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        formatter
66            .debug_struct("SsoMutationGuard")
67            .finish_non_exhaustive()
68    }
69}
70
71pub(super) async fn run<S: AuthSchema>(
72    guard: &dyn SsoProviderMutationGuard<S>,
73    provider: &alibi_core::store::SsoProvider,
74    action: SsoMutationAction,
75    boundary: Option<bool>,
76    verified: bool,
77    database: &dyn AuthTransaction<S>,
78) -> AuthResult<()> {
79    let mutation = SsoProviderMutation {
80        action,
81        is_authentication_boundary_change: boundary,
82        provider: SsoMutationProvider {
83            id: provider.id.clone(),
84            provider_id: provider.provider_id.clone(),
85            organization_id: provider.organization_id.clone(),
86        },
87        provider_reference: super::sign_in::mutation_provider_reference(provider, verified)?,
88    };
89    guard.guard(mutation, database).await.map_err(|_| {
90        tracing::error!("SSO provider mutation guard rejected the mutation");
91        alibi_core::AuthError::Api {
92            status: 409,
93            code: Some("SSO_PROVIDER_MUTATION_REJECTED".into()),
94            message: "SSO provider mutation is not allowed".into(),
95        }
96    })
97}