Skip to main content

alibi_sqlx/store/
scim_user_replace.rs

1//! Canonical User replacement over a fenced identity subject.
2use super::{SqlxStore, entities::scim_user as user};
3use crate::{
4    model::{self, ActiveRow},
5    pool::Exec,
6};
7use alibi_core::{
8    AuthResult, AuthSchema,
9    store::{ReplaceScimUser, ScimProjectionPreparedUser, ScimUserMutationContext, ScimUserRecord},
10};
11impl<S: AuthSchema> SqlxStore<S> {
12    pub(super) async fn lock_scim_user_with_exec(
13        &self,
14        exec: Exec<'_>,
15        id: &str,
16        connection: &str,
17        domain: &str,
18        attributes: Option<&alibi_core::store::ScimUserAttributes>,
19        now: chrono::DateTime<chrono::Utc>,
20    ) -> AuthResult<ScimUserMutationContext> {
21        let mut source = self
22            .scim_user_owned_with_exec(exec, id, connection, domain)
23            .await?;
24        let subject = self
25            .acquire_scim_subject_with_exec(exec, &source.user_id, now, attributes.is_none())
26            .await?;
27        if let Some(attributes) = attributes {
28            self.assert_scim_user_keys_with_exec(
29                exec,
30                connection,
31                &attributes.user_name_key,
32                attributes.external_id_key.as_deref(),
33                None,
34                Some(id),
35            )
36            .await?;
37        } else {
38            let after = self
39                .scim_user_owned_with_exec(exec, id, connection, domain)
40                .await?;
41            if after.user_id != source.user_id {
42                return Err(alibi_core::AuthError::Api {
43                    status: 409,
44                    code: None,
45                    message: "The SCIM User identity changed concurrently".into(),
46                });
47            }
48            source = after;
49        }
50        Ok(ScimUserMutationContext {
51            user_id: source.user_id.clone(),
52            source: source.into(),
53            manage_profile: subject.profile_source_id.as_deref() == Some(id),
54        })
55    }
56    pub(super) async fn replace_scim_user_with_exec(
57        &self,
58        exec: Exec<'_>,
59        id: &str,
60        data: ReplaceScimUser,
61        roles: bool,
62    ) -> AuthResult<(ScimUserRecord, Vec<ScimProjectionPreparedUser>)> {
63        let current = self
64            .scim_user_owned_with_exec(exec, id, &data.connection_id, &data.provisioning_domain_id)
65            .await?;
66        self.assert_scim_user_keys_with_exec(
67            exec,
68            &data.connection_id,
69            &data.attributes.user_name_key,
70            data.attributes.external_id_key.as_deref(),
71            None,
72            Some(id),
73        )
74        .await?;
75        let mut row = ActiveRow::new();
76        row.set("id", id);
77        row.set("userName", data.attributes.user_name.clone());
78        row.set("userNameKey", data.attributes.user_name_key.clone());
79        row.set("primaryEmail", data.attributes.primary_email.clone());
80        row.set(
81            "workEmailValueIndex",
82            data.attributes.work_email_value_index.clone(),
83        );
84        row.set("emailValueIndex", data.attributes.email_value_index.clone());
85        row.set("displayName", data.attributes.display_name.clone());
86        row.set("formattedName", data.attributes.formatted_name.clone());
87        row.set("givenName", data.attributes.given_name.clone());
88        row.set("familyName", data.attributes.family_name.clone());
89        row.set(
90            "serializedEmails",
91            data.attributes.serialized_emails.clone(),
92        );
93        row.set(
94            "serializedAttributes",
95            data.attributes.serialized_attributes.clone(),
96        );
97        row.set("externalId", data.attributes.external_id.clone());
98        row.set("externalIdKey", data.attributes.external_id_key.clone());
99        row.set("active", data.attributes.active);
100        row.set("updatedAt", data.updated_at);
101        let stored = model::update::<user::Model>(exec, &row)
102            .await?
103            .ok_or_else(|| alibi_core::AuthError::Api {
104                status: 404,
105                code: None,
106                message: "SCIM User not found".into(),
107            })?;
108        let states = self
109            .prepare_scim_projection_for_users_with_exec(
110                exec,
111                &data.provisioning_domain_id,
112                vec![current.user_id],
113                roles,
114            )
115            .await?;
116        Ok((
117            ScimUserRecord {
118                id: stored.id,
119                connection_id: stored.connection_id,
120                provisioning_domain_id: stored.provisioning_domain_id,
121                user_name: stored.user_name,
122                primary_email: stored.primary_email,
123                display_name: stored.display_name,
124                active: stored.active,
125                external_id: stored.external_id,
126                formatted_name: stored.formatted_name,
127                given_name: stored.given_name,
128                family_name: stored.family_name,
129                serialized_emails: stored.serialized_emails,
130                serialized_attributes: stored.serialized_attributes,
131                created_at: stored.created_at,
132                updated_at: stored.updated_at,
133            },
134            states,
135        ))
136    }
137}
138
139impl From<user::Model> for ScimUserRecord {
140    fn from(row: user::Model) -> Self {
141        Self {
142            id: row.id,
143            connection_id: row.connection_id,
144            provisioning_domain_id: row.provisioning_domain_id,
145            user_name: row.user_name,
146            primary_email: row.primary_email,
147            display_name: row.display_name,
148            active: row.active,
149            external_id: row.external_id,
150            formatted_name: row.formatted_name,
151            given_name: row.given_name,
152            family_name: row.family_name,
153            serialized_emails: row.serialized_emails,
154            serialized_attributes: row.serialized_attributes,
155            created_at: row.created_at,
156            updated_at: row.updated_at,
157        }
158    }
159}