kanidmd_core/https/
v1_scim.rs

1use super::apidocs::response_schema::{ApiResponseWithout200, DefaultApiResponse};
2use super::errors::WebError;
3use super::middleware::KOpId;
4use super::v1::{
5    json_rest_event_get, json_rest_event_get_id, json_rest_event_get_id_attr, json_rest_event_post,
6    json_rest_event_put_attr,
7};
8use super::ServerState;
9use crate::https::extractors::VerifiedClientInformation;
10use axum::extract::{rejection::JsonRejection, DefaultBodyLimit, Path, Query, State};
11use axum::response::{Html, IntoResponse, Response};
12use axum::routing::{delete, get, post};
13use axum::{Extension, Json, Router};
14use kanidm_proto::scim_v1::{
15    client::ScimEntryPostGeneric,
16    server::{ScimEntryKanidm, ScimListResponse},
17    ScimApplicationPassword, ScimApplicationPasswordCreate, ScimEntryGetQuery, ScimSyncRequest,
18    ScimSyncState,
19};
20use kanidm_proto::v1::Entry as ProtoEntry;
21use kanidmd_lib::prelude::*;
22
23const DEFAULT_SCIM_SYNC_BYTES: usize = 1024 * 1024 * 32;
24
25#[utoipa::path(
26    get,
27    path = "/v1/sync_account",
28    responses(
29        (status = 200,content_type="application/json", body=Vec<ProtoEntry>),
30        ApiResponseWithout200,
31    ),
32    security(("token_jwt" = [])),
33    tag = "v1/sync_account",
34    operation_id = "sync_account_get"
35)]
36/// Get all? the sync accounts.
37pub async fn sync_account_get(
38    State(state): State<ServerState>,
39    Extension(kopid): Extension<KOpId>,
40    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
41) -> Result<Json<Vec<ProtoEntry>>, WebError> {
42    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
43    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
44}
45
46#[utoipa::path(
47    post,
48    path = "/v1/sync_account",
49    // request_body=ProtoEntry,
50    responses(
51        DefaultApiResponse,
52    ),
53    security(("token_jwt" = [])),
54    tag = "v1/sync_account",
55    operation_id = "sync_account_post"
56)]
57pub async fn sync_account_post(
58    State(state): State<ServerState>,
59    Extension(kopid): Extension<KOpId>,
60    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
61    Json(obj): Json<ProtoEntry>,
62) -> Result<Json<()>, WebError> {
63    let classes: Vec<String> = vec![EntryClass::SyncAccount.into(), EntryClass::Object.into()];
64    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
65}
66
67#[utoipa::path(
68    get,
69    path = "/v1/sync_account/{id}",
70    responses(
71        (status = 200,content_type="application/json", body=Option<ProtoEntry>),
72        ApiResponseWithout200,
73    ),
74    security(("token_jwt" = [])),
75    tag = "v1/sync_account",
76)]
77/// Get the details of a sync account
78pub async fn sync_account_id_get(
79    State(state): State<ServerState>,
80    Path(id): Path<String>,
81    Extension(kopid): Extension<KOpId>,
82    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
83) -> Result<Json<Option<ProtoEntry>>, WebError> {
84    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
85    json_rest_event_get_id(state, id, filter, None, kopid, client_auth_info).await
86}
87
88#[utoipa::path(
89    patch,
90    path = "/v1/sync_account/{id}",
91    request_body=ProtoEntry,
92    responses(
93        DefaultApiResponse,
94    ),
95    security(("token_jwt" = [])),
96    tag = "v1/sync_account",
97    operation_id = "sync_account_id_patch"
98)]
99/// Modify a sync account in-place
100pub async fn sync_account_id_patch(
101    State(state): State<ServerState>,
102    Path(id): Path<String>,
103    Extension(kopid): Extension<KOpId>,
104    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
105    Json(obj): Json<ProtoEntry>,
106) -> Result<Json<()>, WebError> {
107    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
108    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
109
110    state
111        .qe_w_ref
112        .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
113        .await
114        .map(Json::from)
115        .map_err(WebError::from)
116}
117
118#[utoipa::path(
119    get,
120    path = "/v1/sync_account/{id}/_finalise",
121    responses(
122        DefaultApiResponse,
123    ),
124    security(("token_jwt" = [])),
125    tag = "v1/sync_account",
126    operation_id = "sync_account_id_finalise_get"
127)]
128pub async fn sync_account_id_finalise_get(
129    State(state): State<ServerState>,
130    Path(id): Path<String>,
131    Extension(kopid): Extension<KOpId>,
132    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
133) -> Result<Json<()>, WebError> {
134    state
135        .qe_w_ref
136        .handle_sync_account_finalise(client_auth_info, id, kopid.eventid)
137        .await
138        .map(Json::from)
139        .map_err(WebError::from)
140}
141
142#[utoipa::path(
143    get,
144    path = "/v1/sync_account/{id}/_terminate",
145    responses(
146        DefaultApiResponse,
147    ),
148    security(("token_jwt" = [])),
149    tag = "v1/sync_account",
150    operation_id = "sync_account_id_terminate_get"
151)]
152pub async fn sync_account_id_terminate_get(
153    State(state): State<ServerState>,
154    Path(id): Path<String>,
155    Extension(kopid): Extension<KOpId>,
156    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
157) -> Result<Json<()>, WebError> {
158    state
159        .qe_w_ref
160        .handle_sync_account_terminate(client_auth_info, id, kopid.eventid)
161        .await
162        .map(Json::from)
163        .map_err(WebError::from)
164}
165
166#[utoipa::path(
167    post,
168    path = "/v1/sync_account/{id}/_sync_token",
169    responses(
170        (status = 200, body=String, content_type="application/json"),
171        ApiResponseWithout200,
172    ),
173    security(("token_jwt" = [])),
174    tag = "v1/sync_account",
175    operation_id = "sync_account_token_post"
176)]
177pub async fn sync_account_token_post(
178    State(state): State<ServerState>,
179    Path(id): Path<String>,
180    Extension(kopid): Extension<KOpId>,
181    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
182    Json(label): Json<String>,
183) -> Result<Json<String>, WebError> {
184    state
185        .qe_w_ref
186        .handle_sync_account_token_generate(client_auth_info, id, label, kopid.eventid)
187        .await
188        .map(Json::from)
189        .map_err(WebError::from)
190}
191
192#[utoipa::path(
193    delete,
194    path = "/v1/sync_account/{id}/_sync_token",
195    responses(
196        DefaultApiResponse,
197    ),
198    security(("token_jwt" = [])),
199    tag = "v1/sync_account",
200    operation_id = "sync_account_token_delete"
201)]
202pub async fn sync_account_token_delete(
203    State(state): State<ServerState>,
204    Path(id): Path<String>,
205    Extension(kopid): Extension<KOpId>,
206    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
207) -> Result<Json<()>, WebError> {
208    state
209        .qe_w_ref
210        .handle_sync_account_token_destroy(client_auth_info, id, kopid.eventid)
211        .await
212        .map(Json::from)
213        .map_err(WebError::from)
214}
215
216#[utoipa::path(
217    get,
218    path = "/v1/sync_account/{id}/_attr/{attr}",
219    responses(
220        (status = 200, body=Option<Vec<String>>, content_type="application/json"),
221        ApiResponseWithout200,
222    ),
223    security(("token_jwt" = [])),
224    tag = "v1/sync_account",
225    operation_id = "sync_account_id_attr_get"
226)]
227pub async fn sync_account_id_attr_get(
228    State(state): State<ServerState>,
229    Extension(kopid): Extension<KOpId>,
230    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
231    Path((id, attr)): Path<(String, String)>,
232) -> Result<Json<Option<Vec<String>>>, WebError> {
233    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
234    json_rest_event_get_id_attr(state, id, attr, filter, kopid, client_auth_info).await
235}
236
237#[utoipa::path(
238    post,
239    path = "/v1/sync_account/{id}/_attr/{attr}",
240    request_body=Vec<String>,
241    responses(
242        DefaultApiResponse,
243    ),
244    security(("token_jwt" = [])),
245    tag = "v1/sync_account",
246    operation_id = "sync_account_id_attr_put"
247)]
248pub async fn sync_account_id_attr_put(
249    State(state): State<ServerState>,
250    Extension(kopid): Extension<KOpId>,
251    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
252    Path((id, attr)): Path<(String, String)>,
253    Json(values): Json<Vec<String>>,
254) -> Result<Json<()>, WebError> {
255    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
256    json_rest_event_put_attr(state, id, attr, filter, values, kopid, client_auth_info).await
257}
258
259/// When you want the kitchen Sink
260async fn scim_sink_get() -> Html<&'static str> {
261    Html::from(include_str!("scim/sink.html"))
262}
263
264#[utoipa::path(
265    post,
266    path = "/scim/v1/Sync",
267    request_body = ScimSyncRequest,
268    responses(
269        DefaultApiResponse,
270    ),
271    security(("token_jwt" = [])),
272    tag = "scim",
273    operation_id = "scim_sync_post"
274)]
275async fn scim_sync_post(
276    State(state): State<ServerState>,
277    Extension(kopid): Extension<KOpId>,
278    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
279    payload: Result<Json<ScimSyncRequest>, JsonRejection>,
280) -> Response {
281    match payload {
282        Ok(Json(changes)) => {
283            let res = state
284                .qe_w_ref
285                .handle_scim_sync_apply(client_auth_info, changes, kopid.eventid)
286                .await;
287
288            match res {
289                Ok(data) => Json::from(data).into_response(),
290                Err(err) => WebError::from(err).into_response(),
291            }
292        }
293        Err(rejection) => {
294            error!(?rejection, "Unable to process JSON");
295            rejection.into_response()
296        }
297    }
298}
299
300#[utoipa::path(
301    get,
302    path = "/scim/v1/Sync",
303    responses(
304        (status = 200, content_type="application/json", body=ScimSyncState), // TODO: response content
305        ApiResponseWithout200,
306    ),
307    security(("token_jwt" = [])),
308    tag = "scim",
309    operation_id = "scim_sync_get"
310)]
311async fn scim_sync_get(
312    State(state): State<ServerState>,
313    Extension(kopid): Extension<KOpId>,
314    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
315) -> Result<Json<ScimSyncState>, WebError> {
316    // Given the token, what is it's connected sync state?
317    state
318        .qe_r_ref
319        .handle_scim_sync_status(client_auth_info, kopid.eventid)
320        .await
321        .map(Json::from)
322        .map_err(WebError::from)
323}
324
325#[utoipa::path(
326    get,
327    path = "/scim/v1/Entry/{id}",
328    responses(
329        (status = 200, content_type="application/json", body=ScimEntry),
330        ApiResponseWithout200,
331    ),
332    security(("token_jwt" = [])),
333    tag = "scim",
334    operation_id = "scim_entry_id_get"
335)]
336async fn scim_entry_id_get(
337    State(state): State<ServerState>,
338    Path(id): Path<String>,
339    Extension(kopid): Extension<KOpId>,
340    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
341    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
342) -> Result<Json<ScimEntryKanidm>, WebError> {
343    state
344        .qe_r_ref
345        .scim_entry_id_get(
346            client_auth_info,
347            kopid.eventid,
348            id,
349            EntryClass::Object,
350            scim_entry_get_query,
351        )
352        .await
353        .map(Json::from)
354        .map_err(WebError::from)
355}
356
357#[utoipa::path(
358    get,
359    path = "/scim/v1/Person/{id}",
360    responses(
361        (status = 200, content_type="application/json", body=ScimEntry),
362        ApiResponseWithout200,
363    ),
364    security(("token_jwt" = [])),
365    tag = "scim",
366    operation_id = "scim_person_id_get"
367)]
368async fn scim_person_id_get(
369    State(state): State<ServerState>,
370    Path(id): Path<String>,
371    Extension(kopid): Extension<KOpId>,
372    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
373    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
374) -> Result<Json<ScimEntryKanidm>, WebError> {
375    state
376        .qe_r_ref
377        .scim_entry_id_get(
378            client_auth_info,
379            kopid.eventid,
380            id,
381            EntryClass::Person,
382            scim_entry_get_query,
383        )
384        .await
385        .map(Json::from)
386        .map_err(WebError::from)
387}
388
389#[utoipa::path(
390    get,
391    path = "/scim/v1/Person/{id}/Application/_create_password",
392    request_body = ScimApplicationPasswordCreate,
393    responses(
394        (status = 200, content_type="application/json", body=ScimApplicationPassword),
395        ApiResponseWithout200,
396    ),
397    security(("token_jwt" = [])),
398    tag = "scim",
399    operation_id = "scim_person_id_application_create_password"
400)]
401async fn scim_person_id_application_create_password(
402    State(state): State<ServerState>,
403    Path(id): Path<String>,
404    Extension(kopid): Extension<KOpId>,
405    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
406    Json(request): Json<ScimApplicationPasswordCreate>,
407) -> Result<Json<ScimApplicationPassword>, WebError> {
408    state
409        .qe_w_ref
410        .scim_person_application_create_password(client_auth_info, kopid.eventid, id, request)
411        .await
412        .map(Json::from)
413        .map_err(WebError::from)
414}
415
416#[utoipa::path(
417    get,
418    path = "/scim/v1/Person/{id}/Application/{apppwd_uuid}",
419    responses(
420        DefaultApiResponse,
421    ),
422    security(("token_jwt" = [])),
423    tag = "scim",
424    operation_id = "scim_person_id_application_delete_password"
425)]
426async fn scim_person_id_application_delete_password(
427    State(state): State<ServerState>,
428    Path((id, apppwd_id)): Path<(String, Uuid)>,
429    Extension(kopid): Extension<KOpId>,
430    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
431) -> Result<Json<()>, WebError> {
432    state
433        .qe_w_ref
434        .scim_person_application_delete_password(client_auth_info, kopid.eventid, id, apppwd_id)
435        .await
436        .map(Json::from)
437        .map_err(WebError::from)
438}
439
440#[utoipa::path(
441    get,
442    path = "/scim/v1/Application",
443    responses(
444        (status = 200, content_type="application/json", body=ScimEntry),
445        ApiResponseWithout200,
446    ),
447    security(("token_jwt" = [])),
448    tag = "scim",
449    operation_id = "scim_application_get"
450)]
451async fn scim_application_get(
452    State(state): State<ServerState>,
453    Extension(kopid): Extension<KOpId>,
454    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
455    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
456) -> Result<Json<ScimListResponse>, WebError> {
457    state
458        .qe_r_ref
459        .scim_entry_search(
460            client_auth_info,
461            kopid.eventid,
462            EntryClass::Application.into(),
463            scim_entry_get_query,
464        )
465        .await
466        .map(Json::from)
467        .map_err(WebError::from)
468}
469
470#[utoipa::path(
471    post,
472    path = "/scim/v1/Application",
473    request_body = ScimEntryPostGeneric,
474    responses(
475        (status = 200, content_type="application/json", body=ScimEntry),
476        ApiResponseWithout200,
477    ),
478    security(("token_jwt" = [])),
479    tag = "scim",
480    operation_id = "scim_application_post"
481)]
482async fn scim_application_post(
483    State(state): State<ServerState>,
484    Extension(kopid): Extension<KOpId>,
485    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
486    Json(entry_post): Json<ScimEntryPostGeneric>,
487) -> Result<Json<ScimEntryKanidm>, WebError> {
488    state
489        .qe_w_ref
490        .scim_entry_create(
491            client_auth_info,
492            kopid.eventid,
493            &[
494                EntryClass::Account,
495                EntryClass::ServiceAccount,
496                EntryClass::Application,
497            ],
498            entry_post,
499        )
500        .await
501        .map(Json::from)
502        .map_err(WebError::from)
503}
504
505#[utoipa::path(
506    delete,
507    path = "/scim/v1/Application/{id}",
508    responses(
509        (status = 200, content_type="application/json"),
510        ApiResponseWithout200,
511    ),
512    security(("token_jwt" = [])),
513    tag = "scim",
514    operation_id = "scim_application_id_get"
515)]
516async fn scim_application_id_get(
517    State(state): State<ServerState>,
518    Path(id): Path<String>,
519    Extension(kopid): Extension<KOpId>,
520    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
521) -> Result<Json<ScimEntryKanidm>, WebError> {
522    state
523        .qe_r_ref
524        .scim_entry_id_get(
525            client_auth_info,
526            kopid.eventid,
527            id,
528            EntryClass::Application,
529            ScimEntryGetQuery::default(),
530        )
531        .await
532        .map(Json::from)
533        .map_err(WebError::from)
534}
535
536#[utoipa::path(
537    delete,
538    path = "/scim/v1/Application/{id}",
539    responses(
540        (status = 200, content_type="application/json"),
541        ApiResponseWithout200,
542    ),
543    security(("token_jwt" = [])),
544    tag = "scim",
545    operation_id = "scim_application_id_delete"
546)]
547async fn scim_application_id_delete(
548    State(state): State<ServerState>,
549    Path(id): Path<String>,
550    Extension(kopid): Extension<KOpId>,
551    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
552) -> Result<Json<()>, WebError> {
553    state
554        .qe_w_ref
555        .scim_entry_id_delete(client_auth_info, kopid.eventid, id, EntryClass::Application)
556        .await
557        .map(Json::from)
558        .map_err(WebError::from)
559}
560
561#[utoipa::path(
562    get,
563    path = "/scim/v1/Class",
564    responses(
565        (status = 200, content_type="application/json", body=ScimEntry),
566        ApiResponseWithout200,
567    ),
568    security(("token_jwt" = [])),
569    tag = "scim",
570    operation_id = "scim_schema_class_get"
571)]
572async fn scim_schema_class_get(
573    State(state): State<ServerState>,
574    Extension(kopid): Extension<KOpId>,
575    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
576    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
577) -> Result<Json<ScimListResponse>, WebError> {
578    state
579        .qe_r_ref
580        .scim_entry_search(
581            client_auth_info,
582            kopid.eventid,
583            EntryClass::ClassType.into(),
584            scim_entry_get_query,
585        )
586        .await
587        .map(Json::from)
588        .map_err(WebError::from)
589}
590
591#[utoipa::path(
592    get,
593    path = "/scim/v1/Attribute",
594    responses(
595        (status = 200, content_type="application/json", body=ScimEntry),
596        ApiResponseWithout200,
597    ),
598    security(("token_jwt" = [])),
599    tag = "scim",
600    operation_id = "scim_schema_attribute_get"
601)]
602async fn scim_schema_attribute_get(
603    State(state): State<ServerState>,
604    Extension(kopid): Extension<KOpId>,
605    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
606    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
607) -> Result<Json<ScimListResponse>, WebError> {
608    state
609        .qe_r_ref
610        .scim_entry_search(
611            client_auth_info,
612            kopid.eventid,
613            EntryClass::AttributeType.into(),
614            scim_entry_get_query,
615        )
616        .await
617        .map(Json::from)
618        .map_err(WebError::from)
619}
620
621pub fn route_setup() -> Router<ServerState> {
622    Router::new()
623        .route(
624            "/v1/sync_account",
625            get(sync_account_get).post(sync_account_post),
626        )
627        .route(
628            "/v1/sync_account/:id",
629            get(sync_account_id_get).patch(sync_account_id_patch),
630        )
631        .route(
632            "/v1/sync_account/:id/_attr/:attr",
633            get(sync_account_id_attr_get).put(sync_account_id_attr_put),
634        )
635        .route(
636            "/v1/sync_account/:id/_finalise",
637            get(sync_account_id_finalise_get),
638        )
639        .route(
640            "/v1/sync_account/:id/_terminate",
641            get(sync_account_id_terminate_get),
642        )
643        .route(
644            "/v1/sync_account/:id/_sync_token",
645            post(sync_account_token_post).delete(sync_account_token_delete),
646        )
647        // https://datatracker.ietf.org/doc/html/rfc7644#section-3.2
648        //
649        //  HTTP   SCIM Usage
650        //  Method
651        //  ------ --------------------------------------------------------------
652        //  GET    Retrieves one or more complete or partial resources.
653        //
654        //  POST   Depending on the endpoint, creates new resources, creates a
655        //         search request, or MAY be used to bulk-modify resources.
656        //
657        //  PUT    Modifies a resource by replacing existing attributes with a
658        //         specified set of replacement attributes (replace).  PUT
659        //         MUST NOT be used to create new resources.
660        //
661        //  PATCH  Modifies a resource with a set of client-specified changes
662        //         (partial update).
663        //
664        //  DELETE Deletes a resource.
665        //
666        //  Resource Endpoint         Operations             Description
667        //  -------- ---------------- ---------------------- --------------------
668        //  User     /Users           GET (Section 3.4.1),   Retrieve, add,
669        //                            POST (Section 3.3),    modify Users.
670        //                            PUT (Section 3.5.1),
671        //                            PATCH (Section 3.5.2),
672        //                            DELETE (Section 3.6)
673        //
674        //  Group    /Groups          GET (Section 3.4.1),   Retrieve, add,
675        //                            POST (Section 3.3),    modify Groups.
676        //                            PUT (Section 3.5.1),
677        //                            PATCH (Section 3.5.2),
678        //                            DELETE (Section 3.6)
679        //
680        //  Self     /Me              GET, POST, PUT, PATCH, Alias for operations
681        //                            DELETE (Section 3.11)  against a resource
682        //                                                   mapped to an
683        //                                                   authenticated
684        //                                                   subject (e.g.,
685        //                                                   User).
686        //
687        //  Service  /ServiceProvider GET (Section 4)        Retrieve service
688        //  provider Config                                  provider's
689        //  config.                                          configuration.
690        //
691        //  Resource /ResourceTypes   GET (Section 4)        Retrieve supported
692        //  type                                             resource types.
693        //
694        //  Schema   /Schemas         GET (Section 4)        Retrieve one or more
695        //                                                   supported schemas.
696        //
697        //  Bulk     /Bulk            POST (Section 3.7)     Bulk updates to one
698        //                                                   or more resources.
699        //
700        //  Search   [prefix]/.search POST (Section 3.4.3)   Search from system
701        //                                                   root or within a
702        //                                                   resource endpoint
703        //                                                   for one or more
704        //                                                   resource types using
705        //                                                   POST.
706        //  -- Kanidm Resources
707        //
708        //  Entry    /Entry/{id}      GET                    Retrieve a generic entry
709        //                                                   of any kind from the database.
710        //                                                   {id} is any unique id.
711        .route("/scim/v1/Entry/:id", get(scim_entry_id_get))
712        //  Person   /Person/{id}     GET                    Retrieve a a person from the
713        //                                                   database.
714        //                                                   {id} is any unique id.
715        .route("/scim/v1/Person/:id", get(scim_person_id_get))
716        .route(
717            "/scim/v1/Person/:id/Application/_create_password",
718            post(scim_person_id_application_create_password),
719        )
720        .route(
721            "/scim/v1/Person/:id/Application/:apppwd_id",
722            delete(scim_person_id_application_delete_password),
723        )
724        //
725        //  Sync     /Sync            GET                    Retrieve the current
726        //                                                   sync state associated
727        //                                                   with the authenticated
728        //                                                   session
729        //
730        //                            POST                   Send a sync update
731        //
732        //
733        //  Application   /Application     Post              Create a new application
734        //
735        .route(
736            "/scim/v1/Application",
737            get(scim_application_get).post(scim_application_post),
738        )
739        //  Application   /Application/{id}     Delete      Delete the application identified by id
740        //
741        .route(
742            "/scim/v1/Application/:id",
743            get(scim_application_id_get).delete(scim_application_id_delete),
744        )
745        //  Class      /Class          GET                  List or query Schema Classes
746        //
747        .route("/scim/v1/Class", get(scim_schema_class_get))
748        //  Attribute /Attribute          GET               List or query Schema Attributes
749        //
750        .route("/scim/v1/Attribute", get(scim_schema_attribute_get))
751        // Synchronisation routes.
752        .route(
753            "/scim/v1/Sync",
754            post(scim_sync_post)
755                .layer(DefaultBodyLimit::max(DEFAULT_SCIM_SYNC_BYTES))
756                .get(scim_sync_get),
757        )
758        .route("/scim/v1/Sink", get(scim_sink_get)) // skip_route_check
759}