rustauth-scim 0.3.0

SCIM support for RustAuth.
Documentation
use super::*;

pub(super) fn search_resources_endpoint(
    options: Arc<ScimOptions>,
) -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/.search",
        Method::POST,
        scim_endpoint_options("searchSCIMResources", "Search SCIM resources")
            .allowed_media_types(["application/scim+json", "application/json"]),
        {
            let options = Arc::clone(&options);
            move |context, request| {
                let options = Arc::clone(&options);
                async move {
                    let adapter = required_adapter(&context)?;
                    let Some(provider) = authenticate_scim_request(
                        adapter.as_ref(),
                        &context.secret,
                        &options,
                        &request,
                    )
                    .await?
                    else {
                        return scim_auth_error(&request).into_response();
                    };
                    if let Err(error) = ensure_scim_provider_scope_supported(&context, &provider) {
                        return error.into_response();
                    }
                    let search = match parse_search_request(&request) {
                        Ok(search) => search,
                        Err(error) => return error.into_response(),
                    };
                    let users = match load_user_resources(
                        adapter.as_ref(),
                        &context.base_url,
                        &provider,
                        None,
                    )
                    .await
                    {
                        Ok(resources) => resources,
                        Err(error) => return error.into_response(),
                    };
                    let mut resources = users
                        .into_iter()
                        .map(serde_json::to_value)
                        .collect::<Result<Vec<_>, _>>()
                        .map_err(|error| RustAuthError::Api(error.to_string()))?;
                    if let Some(organization_id) = provider.organization_id.as_deref() {
                        resources.extend(
                            load_group_resources(
                                adapter.as_ref(),
                                &context.base_url,
                                &provider.provider_id,
                                organization_id,
                            )
                            .await?
                            .into_iter()
                            .map(serde_json::to_value)
                            .collect::<Result<Vec<_>, _>>()
                            .map_err(|error| RustAuthError::Api(error.to_string()))?,
                        );
                    }
                    if let Some(filter) = search.filter.as_deref() {
                        let mut filtered = Vec::new();
                        for resource in resources {
                            match resource_matches_filter(&resource, filter) {
                                Ok(true) => filtered.push(resource),
                                Ok(false) => {}
                                Err(error) => return error.into_response(),
                            }
                        }
                        resources = filtered;
                    }
                    if let Some(sort_by) = search.sort_by.as_deref() {
                        if let Err(error) = sort_json_resources(
                            &mut resources,
                            sort_by,
                            search.sort_order.as_deref(),
                        ) {
                            return error.into_response();
                        }
                    }
                    scim_json_projected_from_search(
                        StatusCode::OK,
                        &json_list_response(resources, search.start_index, search.count),
                        &search,
                    )
                }
            }
        },
    )
}

pub(super) fn me_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/Me",
        Method::GET,
        scim_endpoint_options("getSCIMMe", "Get the authenticated SCIM subject alias"),
        |_context, _request| async {
            ScimError::not_implemented("/Me is not supported for provider-scoped SCIM tokens")
                .into_response()
        },
    )
}

pub(super) fn service_provider_config_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/ServiceProviderConfig",
        Method::GET,
        scim_endpoint_options(
            "getSCIMServiceProviderConfig",
            "Get SCIM ServiceProviderConfig",
        ),
        |_context, _request| async {
            scim_json(StatusCode::OK, &metadata::service_provider_config())
        },
    )
}

pub(super) fn schemas_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/Schemas",
        Method::GET,
        scim_endpoint_options("getSCIMSchemas", "List SCIM schemas"),
        |context, _request| async move {
            scim_json(StatusCode::OK, &metadata::schemas(&context.base_url))
        },
    )
}

pub(super) fn schema_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/Schemas/:schemaId",
        Method::GET,
        scim_endpoint_options("getSCIMSchema", "Get a SCIM schema"),
        |context, request| async move {
            let Some(schema_id) = path_param(&request, "schemaId") else {
                return ScimError::not_found("Schema not found").into_response();
            };
            match metadata::schema(&context.base_url, &schema_id) {
                Ok(schema) => scim_json(StatusCode::OK, &schema),
                Err(error) => error.into_response(),
            }
        },
    )
}

pub(super) fn resource_types_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/ResourceTypes",
        Method::GET,
        scim_endpoint_options("getSCIMResourceTypes", "List SCIM resource types"),
        |context, _request| async move {
            scim_json(StatusCode::OK, &metadata::resource_types(&context.base_url))
        },
    )
}

pub(super) fn resource_type_endpoint() -> rustauth_core::api::AsyncAuthEndpoint {
    create_auth_endpoint(
        "/scim/v2/ResourceTypes/:resourceTypeId",
        Method::GET,
        scim_endpoint_options("getSCIMResourceType", "Get a SCIM resource type"),
        |context, request| async move {
            let Some(resource_type_id) = path_param(&request, "resourceTypeId") else {
                return ScimError::not_found("Resource type not found").into_response();
            };
            match metadata::resource_type(&context.base_url, &resource_type_id) {
                Ok(resource_type) => scim_json(StatusCode::OK, &resource_type),
                Err(error) => error.into_response(),
            }
        },
    )
}