mz_frontegg_mock/handlers/
scim.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use crate::models::*;
11use crate::server::Context;
12use crate::utils::decode_access_token;
13use axum::{
14    Json,
15    extract::{Path, State},
16    http::StatusCode,
17};
18use axum_extra::TypedHeader;
19use axum_extra::headers::Authorization;
20use axum_extra::headers::authorization::Bearer;
21use chrono::Utc;
22use jsonwebtoken::TokenData;
23use std::sync::Arc;
24use uuid::Uuid;
25
26pub async fn handle_list_scim_configurations(
27    State(context): State<Arc<Context>>,
28) -> Json<Vec<SCIM2ConfigurationResponse>> {
29    let configs = context.scim_configurations.lock().unwrap();
30    let responses: Vec<SCIM2ConfigurationResponse> = configs
31        .values()
32        .map(|config| SCIM2ConfigurationResponse {
33            id: config.id.clone(),
34            source: config.source.clone(),
35            tenant_id: config.tenant_id.clone(),
36            connection_name: config.connection_name.clone(),
37            sync_to_user_management: config.sync_to_user_management,
38            created_at: config.created_at,
39            token: config.token.clone(),
40        })
41        .collect();
42    Json(responses)
43}
44
45pub async fn handle_create_scim_configuration(
46    State(context): State<Arc<Context>>,
47    TypedHeader(authorization): TypedHeader<Authorization<Bearer>>,
48    Json(request): Json<SCIM2ConfigurationCreateRequest>,
49) -> Result<Json<SCIM2ConfigurationResponse>, StatusCode> {
50    // Extract claims from the access token
51    let claims = match decode_access_token(&context, authorization.token()) {
52        Ok(TokenData { claims, .. }) => claims,
53        Err(_) => return Err(StatusCode::UNAUTHORIZED),
54    };
55
56    let now = Utc::now();
57    let new_config = SCIM2ConfigurationStorage {
58        id: Uuid::new_v4().to_string(),
59        source: request.source,
60        tenant_id: claims.tenant_id.to_string(),
61        connection_name: request.connection_name,
62        sync_to_user_management: request.sync_to_user_management,
63        created_at: now,
64        token: Uuid::new_v4().to_string(),
65    };
66
67    let response = SCIM2ConfigurationResponse {
68        id: new_config.id.clone(),
69        source: new_config.source.clone(),
70        tenant_id: new_config.tenant_id.clone(),
71        connection_name: new_config.connection_name.clone(),
72        sync_to_user_management: new_config.sync_to_user_management,
73        created_at: new_config.created_at,
74        token: new_config.token.clone(),
75    };
76
77    let mut configs = context.scim_configurations.lock().unwrap();
78    configs.insert(new_config.id.clone(), new_config);
79
80    Ok(Json(response))
81}
82
83pub async fn handle_delete_scim_configuration(
84    State(context): State<Arc<Context>>,
85    Path(config_id): Path<String>,
86) -> StatusCode {
87    let mut configs = context.scim_configurations.lock().unwrap();
88    if configs.remove(&config_id).is_some() {
89        StatusCode::NO_CONTENT
90    } else {
91        StatusCode::NOT_FOUND
92    }
93}