use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use http::StatusCode;
use rustauth_core::db::User;
use rustauth_core::error::RustAuthError;
use crate::store::ScimProviderRecord;
pub type ScimHookFuture = Pin<Box<dyn Future<Output = Result<(), ScimHookError>> + Send>>;
pub type ScimTokenStorageFuture =
Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send>>;
pub type ScimTokenTransform = Arc<dyn Fn(String) -> ScimTokenStorageFuture + Send + Sync>;
pub type BeforeScimTokenGeneratedHook =
Arc<dyn Fn(BeforeScimTokenGeneratedInput) -> ScimHookFuture + Send + Sync>;
pub type AfterScimTokenGeneratedHook =
Arc<dyn Fn(AfterScimTokenGeneratedInput) -> ScimHookFuture + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScimBulkMode {
#[default]
Independent,
Atomic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScimDeprovisionMode {
DeleteUser,
#[default]
UnlinkAccount,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScimAuditSeverity {
Info,
Warn,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScimAuditEventKind {
TokenGenerated,
UserProvisioned,
UserDeprovisioned,
BulkFailed,
BulkRolledBack,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScimAuditEvent {
pub kind: ScimAuditEventKind,
pub severity: ScimAuditSeverity,
pub provider_id: Option<String>,
pub user_id: Option<String>,
pub organization_id: Option<String>,
pub reason: Option<String>,
}
impl ScimAuditEvent {
pub fn new(kind: ScimAuditEventKind, severity: ScimAuditSeverity) -> Self {
Self {
kind,
severity,
provider_id: None,
user_id: None,
organization_id: None,
reason: None,
}
}
#[must_use]
pub fn with_provider_id(mut self, provider_id: impl Into<String>) -> Self {
self.provider_id = Some(provider_id.into());
self
}
#[must_use]
pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = Some(user_id.into());
self
}
#[must_use]
pub fn with_organization_id(mut self, organization_id: impl Into<String>) -> Self {
self.organization_id = Some(organization_id.into());
self
}
#[must_use]
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
}
#[derive(Clone)]
pub struct ScimOptions {
pub provider_ownership: ProviderOwnershipOptions,
pub required_role: Option<Vec<String>>,
pub default_scim: Vec<DefaultScimProvider>,
pub token_storage: ScimTokenStorage,
pub before_token_generated: Option<BeforeScimTokenGeneratedHook>,
pub after_token_generated: Option<AfterScimTokenGeneratedHook>,
pub bulk_mode: ScimBulkMode,
pub deprovision_mode: ScimDeprovisionMode,
pub audit_event: Option<crate::audit::ScimAuditEventResolver>,
}
impl Default for ScimOptions {
fn default() -> Self {
Self {
provider_ownership: ProviderOwnershipOptions::default(),
required_role: None,
default_scim: Vec::new(),
token_storage: ScimTokenStorage::Hashed,
before_token_generated: None,
after_token_generated: None,
bulk_mode: ScimBulkMode::default(),
deprovision_mode: ScimDeprovisionMode::default(),
audit_event: None,
}
}
}
impl ScimOptions {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn provider_ownership(mut self, ownership: ProviderOwnershipOptions) -> Self {
self.provider_ownership = ownership;
self
}
#[must_use]
pub fn required_role(mut self, roles: Vec<String>) -> Self {
self.required_role = Some(roles);
self
}
#[must_use]
pub fn default_scim(mut self, providers: Vec<DefaultScimProvider>) -> Self {
self.default_scim = providers;
self
}
#[must_use]
pub fn token_storage(mut self, storage: ScimTokenStorage) -> Self {
self.token_storage = storage;
self
}
#[must_use]
pub fn before_token_generated(mut self, hook: BeforeScimTokenGeneratedHook) -> Self {
self.before_token_generated = Some(hook);
self
}
#[must_use]
pub fn after_token_generated(mut self, hook: AfterScimTokenGeneratedHook) -> Self {
self.after_token_generated = Some(hook);
self
}
#[must_use]
pub fn bulk_mode(mut self, mode: ScimBulkMode) -> Self {
self.bulk_mode = mode;
self
}
#[must_use]
pub fn deprovision_mode(mut self, mode: ScimDeprovisionMode) -> Self {
self.deprovision_mode = mode;
self
}
#[must_use]
pub fn audit_event(mut self, resolver: crate::audit::ScimAuditEventResolver) -> Self {
self.audit_event = Some(resolver);
self
}
}
impl std::fmt::Debug for ScimOptions {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ScimOptions")
.field("provider_ownership", &self.provider_ownership)
.field("required_role", &self.required_role)
.field("default_scim", &self.default_scim)
.field("token_storage", &self.token_storage)
.field(
"before_token_generated",
&self.before_token_generated.as_ref().map(|_| "<hook>"),
)
.field(
"after_token_generated",
&self.after_token_generated.as_ref().map(|_| "<hook>"),
)
.field("bulk_mode", &self.bulk_mode)
.field("deprovision_mode", &self.deprovision_mode)
.field(
"audit_event",
&self.audit_event.as_ref().map(|_| "<resolver>"),
)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScimOrganizationMember {
pub organization_id: String,
pub user_id: String,
pub role: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BeforeScimTokenGeneratedInput {
pub user: User,
pub member: Option<ScimOrganizationMember>,
pub scim_token: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AfterScimTokenGeneratedInput {
pub user: User,
pub member: Option<ScimOrganizationMember>,
pub scim_token: String,
pub provider: ScimProviderRecord,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScimHookError {
pub status: StatusCode,
pub code: String,
pub message: String,
}
impl ScimHookError {
pub fn new(status: StatusCode, code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
status,
code: code.into(),
message: message.into(),
}
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self::new(StatusCode::FORBIDDEN, "FORBIDDEN", message)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ProviderOwnershipOptions {
pub enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefaultScimProvider {
pub provider_id: String,
pub scim_token: String,
pub organization_id: Option<String>,
}
#[derive(Clone)]
pub enum ScimTokenStorage {
Plain,
Hashed,
Encrypted,
CustomHash { hash: ScimTokenTransform },
CustomEncryption {
encrypt: ScimTokenTransform,
decrypt: ScimTokenTransform,
},
}
impl ScimTokenStorage {
pub fn custom_hash(
hash: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
) -> Self {
Self::CustomHash {
hash: Arc::new(hash),
}
}
pub fn custom_encryption(
encrypt: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
decrypt: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
) -> Self {
Self::CustomEncryption {
encrypt: Arc::new(encrypt),
decrypt: Arc::new(decrypt),
}
}
}
impl std::fmt::Debug for ScimTokenStorage {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Plain => formatter.write_str("Plain"),
Self::Hashed => formatter.write_str("Hashed"),
Self::Encrypted => formatter.write_str("Encrypted"),
Self::CustomHash { .. } => formatter.write_str("CustomHash"),
Self::CustomEncryption { .. } => formatter.write_str("CustomEncryption"),
}
}
}