1use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use http::StatusCode;
8use rustauth_core::db::User;
9use rustauth_core::error::RustAuthError;
10
11use crate::store::ScimProviderRecord;
12
13pub type ScimHookFuture = Pin<Box<dyn Future<Output = Result<(), ScimHookError>> + Send>>;
15
16pub type ScimTokenStorageFuture =
18 Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send>>;
19
20pub type ScimTokenTransform = Arc<dyn Fn(String) -> ScimTokenStorageFuture + Send + Sync>;
22
23pub type BeforeScimTokenGeneratedHook =
25 Arc<dyn Fn(BeforeScimTokenGeneratedInput) -> ScimHookFuture + Send + Sync>;
26
27pub type AfterScimTokenGeneratedHook =
29 Arc<dyn Fn(AfterScimTokenGeneratedInput) -> ScimHookFuture + Send + Sync>;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub enum ScimBulkMode {
42 #[default]
44 Independent,
45 Atomic,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum ScimDeprovisionMode {
52 DeleteUser,
55 #[default]
57 UnlinkAccount,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ScimAuditSeverity {
63 Info,
65 Warn,
67 Error,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum ScimAuditEventKind {
74 TokenGenerated,
76 UserProvisioned,
78 UserDeprovisioned,
80 BulkFailed,
82 BulkRolledBack,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ScimAuditEvent {
89 pub kind: ScimAuditEventKind,
91 pub severity: ScimAuditSeverity,
93 pub provider_id: Option<String>,
95 pub user_id: Option<String>,
97 pub organization_id: Option<String>,
99 pub reason: Option<String>,
101}
102
103impl ScimAuditEvent {
104 pub fn new(kind: ScimAuditEventKind, severity: ScimAuditSeverity) -> Self {
106 Self {
107 kind,
108 severity,
109 provider_id: None,
110 user_id: None,
111 organization_id: None,
112 reason: None,
113 }
114 }
115
116 #[must_use]
117 pub fn with_provider_id(mut self, provider_id: impl Into<String>) -> Self {
118 self.provider_id = Some(provider_id.into());
119 self
120 }
121
122 #[must_use]
123 pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
124 self.user_id = Some(user_id.into());
125 self
126 }
127
128 #[must_use]
129 pub fn with_organization_id(mut self, organization_id: impl Into<String>) -> Self {
130 self.organization_id = Some(organization_id.into());
131 self
132 }
133
134 #[must_use]
135 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
136 self.reason = Some(reason.into());
137 self
138 }
139}
140
141#[derive(Clone)]
150pub struct ScimOptions {
151 pub provider_ownership: ProviderOwnershipOptions,
153 pub required_role: Option<Vec<String>>,
155 pub default_scim: Vec<DefaultScimProvider>,
157 pub token_storage: ScimTokenStorage,
159 pub before_token_generated: Option<BeforeScimTokenGeneratedHook>,
161 pub after_token_generated: Option<AfterScimTokenGeneratedHook>,
163 pub bulk_mode: ScimBulkMode,
165 pub deprovision_mode: ScimDeprovisionMode,
167 pub audit_event: Option<crate::audit::ScimAuditEventResolver>,
169}
170
171impl Default for ScimOptions {
172 fn default() -> Self {
173 Self {
174 provider_ownership: ProviderOwnershipOptions::default(),
175 required_role: None,
176 default_scim: Vec::new(),
177 token_storage: ScimTokenStorage::Hashed,
178 before_token_generated: None,
179 after_token_generated: None,
180 bulk_mode: ScimBulkMode::default(),
181 deprovision_mode: ScimDeprovisionMode::default(),
182 audit_event: None,
183 }
184 }
185}
186
187impl ScimOptions {
188 pub fn new() -> Self {
190 Self::default()
191 }
192
193 #[must_use]
194 pub fn provider_ownership(mut self, ownership: ProviderOwnershipOptions) -> Self {
196 self.provider_ownership = ownership;
197 self
198 }
199
200 #[must_use]
201 pub fn required_role(mut self, roles: Vec<String>) -> Self {
203 self.required_role = Some(roles);
204 self
205 }
206
207 #[must_use]
208 pub fn default_scim(mut self, providers: Vec<DefaultScimProvider>) -> Self {
210 self.default_scim = providers;
211 self
212 }
213
214 #[must_use]
215 pub fn token_storage(mut self, storage: ScimTokenStorage) -> Self {
217 self.token_storage = storage;
218 self
219 }
220
221 #[must_use]
222 pub fn before_token_generated(mut self, hook: BeforeScimTokenGeneratedHook) -> Self {
224 self.before_token_generated = Some(hook);
225 self
226 }
227
228 #[must_use]
229 pub fn after_token_generated(mut self, hook: AfterScimTokenGeneratedHook) -> Self {
231 self.after_token_generated = Some(hook);
232 self
233 }
234
235 #[must_use]
236 pub fn bulk_mode(mut self, mode: ScimBulkMode) -> Self {
238 self.bulk_mode = mode;
239 self
240 }
241
242 #[must_use]
243 pub fn deprovision_mode(mut self, mode: ScimDeprovisionMode) -> Self {
245 self.deprovision_mode = mode;
246 self
247 }
248
249 #[must_use]
250 pub fn audit_event(mut self, resolver: crate::audit::ScimAuditEventResolver) -> Self {
252 self.audit_event = Some(resolver);
253 self
254 }
255}
256
257impl std::fmt::Debug for ScimOptions {
258 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 formatter
260 .debug_struct("ScimOptions")
261 .field("provider_ownership", &self.provider_ownership)
262 .field("required_role", &self.required_role)
263 .field("default_scim", &self.default_scim)
264 .field("token_storage", &self.token_storage)
265 .field(
266 "before_token_generated",
267 &self.before_token_generated.as_ref().map(|_| "<hook>"),
268 )
269 .field(
270 "after_token_generated",
271 &self.after_token_generated.as_ref().map(|_| "<hook>"),
272 )
273 .field("bulk_mode", &self.bulk_mode)
274 .field("deprovision_mode", &self.deprovision_mode)
275 .field(
276 "audit_event",
277 &self.audit_event.as_ref().map(|_| "<resolver>"),
278 )
279 .finish()
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct ScimOrganizationMember {
286 pub organization_id: String,
288 pub user_id: String,
290 pub role: String,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
296pub struct BeforeScimTokenGeneratedInput {
297 pub user: User,
299 pub member: Option<ScimOrganizationMember>,
301 pub scim_token: String,
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct AfterScimTokenGeneratedInput {
308 pub user: User,
310 pub member: Option<ScimOrganizationMember>,
312 pub scim_token: String,
314 pub provider: ScimProviderRecord,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct ScimHookError {
321 pub status: StatusCode,
323 pub code: String,
325 pub message: String,
327}
328
329impl ScimHookError {
330 pub fn new(status: StatusCode, code: impl Into<String>, message: impl Into<String>) -> Self {
332 Self {
333 status,
334 code: code.into(),
335 message: message.into(),
336 }
337 }
338
339 pub fn forbidden(message: impl Into<String>) -> Self {
341 Self::new(StatusCode::FORBIDDEN, "FORBIDDEN", message)
342 }
343}
344
345#[derive(Debug, Clone, Default, PartialEq, Eq)]
347pub struct ProviderOwnershipOptions {
348 pub enabled: bool,
353}
354
355#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct DefaultScimProvider {
362 pub provider_id: String,
364 pub scim_token: String,
366 pub organization_id: Option<String>,
368}
369
370#[derive(Clone)]
372pub enum ScimTokenStorage {
373 Plain,
375 Hashed,
377 Encrypted,
379 CustomHash { hash: ScimTokenTransform },
381 CustomEncryption {
383 encrypt: ScimTokenTransform,
384 decrypt: ScimTokenTransform,
385 },
386}
387
388impl ScimTokenStorage {
389 pub fn custom_hash(
399 hash: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
400 ) -> Self {
401 Self::CustomHash {
402 hash: Arc::new(hash),
403 }
404 }
405
406 pub fn custom_encryption(
417 encrypt: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
418 decrypt: impl Fn(String) -> ScimTokenStorageFuture + Send + Sync + 'static,
419 ) -> Self {
420 Self::CustomEncryption {
421 encrypt: Arc::new(encrypt),
422 decrypt: Arc::new(decrypt),
423 }
424 }
425}
426
427impl std::fmt::Debug for ScimTokenStorage {
428 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429 match self {
430 Self::Plain => formatter.write_str("Plain"),
431 Self::Hashed => formatter.write_str("Hashed"),
432 Self::Encrypted => formatter.write_str("Encrypted"),
433 Self::CustomHash { .. } => formatter.write_str("CustomHash"),
434 Self::CustomEncryption { .. } => formatter.write_str("CustomEncryption"),
435 }
436 }
437}