use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::error::{IdentityError, Result};
use crate::identity::new_id;
use crate::model::{AccountStatus, Identity};
use crate::store::UserStore;
const SCHEMA_USER: &str = "urn:ietf:params:scim:schemas:core:2.0:User";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScimName {
#[serde(default, rename = "givenName", skip_serializing_if = "Option::is_none")]
pub given_name: Option<String>,
#[serde(
default,
rename = "familyName",
skip_serializing_if = "Option::is_none"
)]
pub family_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScimEmail {
pub value: String,
#[serde(default)]
pub primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScimUser {
pub schemas: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "userName")]
pub user_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<ScimName>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub emails: Vec<ScimEmail>,
#[serde(default = "default_true")]
pub active: bool,
#[serde(
default,
rename = "externalId",
skip_serializing_if = "Option::is_none"
)]
pub external_id: Option<String>,
}
fn default_true() -> bool {
true
}
impl ScimUser {
fn primary_email(&self) -> Option<&str> {
self.emails
.iter()
.find(|e| e.primary)
.or_else(|| self.emails.first())
.map(|e| e.value.as_str())
}
}
pub struct ScimProvisioner {
users: Arc<dyn UserStore>,
tenant: Option<String>,
}
impl ScimProvisioner {
pub fn new(users: Arc<dyn UserStore>, tenant: Option<String>) -> Self {
Self { users, tenant }
}
pub async fn create(&self, scim: &ScimUser) -> Result<ScimUser> {
let email = scim.primary_email().unwrap_or(&scim.user_name).to_owned();
if self
.users
.find_by_email(self.tenant.as_deref(), &email)
.await?
.is_some()
{
return Err(IdentityError::AlreadyExists);
}
let identity = Identity {
id: new_id(),
tenant: self.tenant.clone(),
email: Some(email),
email_verified: true,
phone: None,
phone_verified: false,
status: if scim.active {
AccountStatus::Active
} else {
AccountStatus::Suspended
},
roles: vec!["user".to_owned()],
perms: Vec::new(),
mfa: Default::default(),
attributes: Default::default(),
};
self.users.insert(&identity).await?;
Ok(to_scim(&identity))
}
pub async fn replace(&self, id: &str, scim: &ScimUser) -> Result<ScimUser> {
let mut identity = self
.users
.find_by_id(id)
.await?
.ok_or(IdentityError::NotFound)?;
if let Some(email) = scim.primary_email() {
identity.email = Some(email.to_owned());
}
identity.status = if scim.active {
AccountStatus::Active
} else {
AccountStatus::Suspended
};
self.users.update(&identity).await?;
Ok(to_scim(&identity))
}
pub async fn set_active(&self, id: &str, active: bool) -> Result<ScimUser> {
let mut identity = self
.users
.find_by_id(id)
.await?
.ok_or(IdentityError::NotFound)?;
identity.status = if active {
AccountStatus::Active
} else {
AccountStatus::Suspended
};
self.users.update(&identity).await?;
Ok(to_scim(&identity))
}
pub async fn get(&self, id: &str) -> Result<ScimUser> {
self.users
.find_by_id(id)
.await?
.map(|i| to_scim(&i))
.ok_or(IdentityError::NotFound)
}
pub async fn delete(&self, id: &str) -> Result<()> {
let mut identity = self
.users
.find_by_id(id)
.await?
.ok_or(IdentityError::NotFound)?;
identity.status = AccountStatus::Deleted;
self.users.update(&identity).await
}
}
fn to_scim(identity: &Identity) -> ScimUser {
ScimUser {
schemas: vec![SCHEMA_USER.to_owned()],
id: Some(identity.id.clone()),
user_name: identity
.email
.clone()
.unwrap_or_else(|| identity.id.clone()),
name: None,
emails: identity
.email
.as_ref()
.map(|e| {
vec![ScimEmail {
value: e.clone(),
primary: true,
}]
})
.unwrap_or_default(),
active: matches!(identity.status, AccountStatus::Active),
external_id: None,
}
}