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::ScimEntry;
15use kanidm_proto::scim_v1::{
16    client::{ScimEntryPostGeneric, ScimEntryPutGeneric},
17    server::{ScimEntryKanidm, ScimListResponse},
18    ScimApplicationPassword, ScimApplicationPasswordCreate, ScimEntryGetQuery, ScimSyncRequest,
19    ScimSyncState,
20};
21use kanidm_proto::v1::Entry as ProtoEntry;
22use kanidmd_lib::prelude::*;
23
24const DEFAULT_SCIM_SYNC_BYTES: usize = 1024 * 1024 * 32;
25
26#[utoipa::path(
27    get,
28    path = "/v1/sync_account",
29    responses(
30        (status = 200,content_type="application/json", body=Vec<ProtoEntry>),
31        ApiResponseWithout200,
32    ),
33    security(("token_jwt" = [])),
34    tag = "v1/sync_account",
35    operation_id = "sync_account_get"
36)]
37/// Get all? the sync accounts.
38pub async fn sync_account_get(
39    State(state): State<ServerState>,
40    Extension(kopid): Extension<KOpId>,
41    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
42) -> Result<Json<Vec<ProtoEntry>>, WebError> {
43    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
44    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
45}
46
47#[utoipa::path(
48    post,
49    path = "/v1/sync_account",
50    // request_body=ProtoEntry,
51    responses(
52        DefaultApiResponse,
53    ),
54    security(("token_jwt" = [])),
55    tag = "v1/sync_account",
56    operation_id = "sync_account_post"
57)]
58pub async fn sync_account_post(
59    State(state): State<ServerState>,
60    Extension(kopid): Extension<KOpId>,
61    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
62    Json(obj): Json<ProtoEntry>,
63) -> Result<Json<()>, WebError> {
64    let classes: Vec<String> = vec![EntryClass::SyncAccount.into(), EntryClass::Object.into()];
65    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
66}
67
68#[utoipa::path(
69    get,
70    path = "/v1/sync_account/{id}",
71    responses(
72        (status = 200,content_type="application/json", body=Option<ProtoEntry>),
73        ApiResponseWithout200,
74    ),
75    security(("token_jwt" = [])),
76    tag = "v1/sync_account",
77)]
78/// Get the details of a sync account
79pub async fn sync_account_id_get(
80    State(state): State<ServerState>,
81    Path(id): Path<String>,
82    Extension(kopid): Extension<KOpId>,
83    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
84) -> Result<Json<Option<ProtoEntry>>, WebError> {
85    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
86    json_rest_event_get_id(state, id, filter, None, kopid, client_auth_info).await
87}
88
89#[utoipa::path(
90    patch,
91    path = "/v1/sync_account/{id}",
92    request_body=ProtoEntry,
93    responses(
94        DefaultApiResponse,
95    ),
96    security(("token_jwt" = [])),
97    tag = "v1/sync_account",
98    operation_id = "sync_account_id_patch"
99)]
100/// Modify a sync account in-place
101pub async fn sync_account_id_patch(
102    State(state): State<ServerState>,
103    Path(id): Path<String>,
104    Extension(kopid): Extension<KOpId>,
105    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
106    Json(obj): Json<ProtoEntry>,
107) -> Result<Json<()>, WebError> {
108    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
109    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
110
111    state
112        .qe_w_ref
113        .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
114        .await
115        .map(Json::from)
116        .map_err(WebError::from)
117}
118
119#[utoipa::path(
120    get,
121    path = "/v1/sync_account/{id}/_finalise",
122    responses(
123        DefaultApiResponse,
124    ),
125    security(("token_jwt" = [])),
126    tag = "v1/sync_account",
127    operation_id = "sync_account_id_finalise_get"
128)]
129pub async fn sync_account_id_finalise_get(
130    State(state): State<ServerState>,
131    Path(id): Path<String>,
132    Extension(kopid): Extension<KOpId>,
133    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
134) -> Result<Json<()>, WebError> {
135    state
136        .qe_w_ref
137        .handle_sync_account_finalise(client_auth_info, id, kopid.eventid)
138        .await
139        .map(Json::from)
140        .map_err(WebError::from)
141}
142
143#[utoipa::path(
144    get,
145    path = "/v1/sync_account/{id}/_terminate",
146    responses(
147        DefaultApiResponse,
148    ),
149    security(("token_jwt" = [])),
150    tag = "v1/sync_account",
151    operation_id = "sync_account_id_terminate_get"
152)]
153pub async fn sync_account_id_terminate_get(
154    State(state): State<ServerState>,
155    Path(id): Path<String>,
156    Extension(kopid): Extension<KOpId>,
157    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
158) -> Result<Json<()>, WebError> {
159    state
160        .qe_w_ref
161        .handle_sync_account_terminate(client_auth_info, id, kopid.eventid)
162        .await
163        .map(Json::from)
164        .map_err(WebError::from)
165}
166
167#[utoipa::path(
168    post,
169    path = "/v1/sync_account/{id}/_sync_token",
170    responses(
171        (status = 200, body=String, content_type="application/json"),
172        ApiResponseWithout200,
173    ),
174    security(("token_jwt" = [])),
175    tag = "v1/sync_account",
176    operation_id = "sync_account_token_post"
177)]
178pub async fn sync_account_token_post(
179    State(state): State<ServerState>,
180    Path(id): Path<String>,
181    Extension(kopid): Extension<KOpId>,
182    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
183    Json(label): Json<String>,
184) -> Result<Json<String>, WebError> {
185    state
186        .qe_w_ref
187        .handle_sync_account_token_generate(client_auth_info, id, label, kopid.eventid)
188        .await
189        .map(Json::from)
190        .map_err(WebError::from)
191}
192
193#[utoipa::path(
194    delete,
195    path = "/v1/sync_account/{id}/_sync_token",
196    responses(
197        DefaultApiResponse,
198    ),
199    security(("token_jwt" = [])),
200    tag = "v1/sync_account",
201    operation_id = "sync_account_token_delete"
202)]
203pub async fn sync_account_token_delete(
204    State(state): State<ServerState>,
205    Path(id): Path<String>,
206    Extension(kopid): Extension<KOpId>,
207    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
208) -> Result<Json<()>, WebError> {
209    state
210        .qe_w_ref
211        .handle_sync_account_token_destroy(client_auth_info, id, kopid.eventid)
212        .await
213        .map(Json::from)
214        .map_err(WebError::from)
215}
216
217#[utoipa::path(
218    get,
219    path = "/v1/sync_account/{id}/_attr/{attr}",
220    responses(
221        (status = 200, body=Option<Vec<String>>, content_type="application/json"),
222        ApiResponseWithout200,
223    ),
224    security(("token_jwt" = [])),
225    tag = "v1/sync_account",
226    operation_id = "sync_account_id_attr_get"
227)]
228pub async fn sync_account_id_attr_get(
229    State(state): State<ServerState>,
230    Extension(kopid): Extension<KOpId>,
231    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
232    Path((id, attr)): Path<(String, String)>,
233) -> Result<Json<Option<Vec<String>>>, WebError> {
234    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
235    json_rest_event_get_id_attr(state, id, attr, filter, kopid, client_auth_info).await
236}
237
238#[utoipa::path(
239    post,
240    path = "/v1/sync_account/{id}/_attr/{attr}",
241    request_body=Vec<String>,
242    responses(
243        DefaultApiResponse,
244    ),
245    security(("token_jwt" = [])),
246    tag = "v1/sync_account",
247    operation_id = "sync_account_id_attr_put"
248)]
249pub async fn sync_account_id_attr_put(
250    State(state): State<ServerState>,
251    Extension(kopid): Extension<KOpId>,
252    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
253    Path((id, attr)): Path<(String, String)>,
254    Json(values): Json<Vec<String>>,
255) -> Result<Json<()>, WebError> {
256    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SyncAccount.into()));
257    json_rest_event_put_attr(state, id, attr, filter, values, kopid, client_auth_info).await
258}
259
260/// When you want the kitchen Sink
261async fn scim_sink_get() -> Html<&'static str> {
262    Html::from(include_str!("scim/sink.html"))
263}
264
265#[utoipa::path(
266    post,
267    path = "/scim/v1/Sync",
268    request_body = ScimSyncRequest,
269    responses(
270        DefaultApiResponse,
271    ),
272    security(("token_jwt" = [])),
273    tag = "scim",
274    operation_id = "scim_sync_post"
275)]
276async fn scim_sync_post(
277    State(state): State<ServerState>,
278    Extension(kopid): Extension<KOpId>,
279    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
280    payload: Result<Json<ScimSyncRequest>, JsonRejection>,
281) -> Response {
282    match payload {
283        Ok(Json(changes)) => {
284            let res = state
285                .qe_w_ref
286                .handle_scim_sync_apply(client_auth_info, changes, kopid.eventid)
287                .await;
288
289            match res {
290                Ok(data) => Json::from(data).into_response(),
291                Err(err) => WebError::from(err).into_response(),
292            }
293        }
294        Err(rejection) => {
295            error!(?rejection, "Unable to process JSON");
296            rejection.into_response()
297        }
298    }
299}
300
301#[utoipa::path(
302    get,
303    path = "/scim/v1/Sync",
304    responses(
305        (status = 200, content_type="application/json", body=ScimSyncState), // TODO: response content
306        ApiResponseWithout200,
307    ),
308    security(("token_jwt" = [])),
309    tag = "scim",
310    operation_id = "scim_sync_get"
311)]
312async fn scim_sync_get(
313    State(state): State<ServerState>,
314    Extension(kopid): Extension<KOpId>,
315    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
316) -> Result<Json<ScimSyncState>, WebError> {
317    // Given the token, what is it's connected sync state?
318    state
319        .qe_r_ref
320        .handle_scim_sync_status(client_auth_info, kopid.eventid)
321        .await
322        .map(Json::from)
323        .map_err(WebError::from)
324}
325
326#[utoipa::path(
327    get,
328    path = "/scim/v1/Entry",
329    responses(
330        (status = 200, content_type="application/json", body=ScimEntry),
331        ApiResponseWithout200,
332    ),
333    security(("token_jwt" = [])),
334    tag = "scim",
335    operation_id = "scim_entry_get"
336)]
337async fn scim_entry_get(
338    State(state): State<ServerState>,
339    Extension(kopid): Extension<KOpId>,
340    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
341    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
342) -> Result<Json<ScimListResponse>, WebError> {
343    state
344        .qe_r_ref
345        .scim_entry_search(
346            client_auth_info,
347            kopid.eventid,
348            EntryClass::Object.into(),
349            scim_entry_get_query,
350        )
351        .await
352        .map(Json::from)
353        .map_err(WebError::from)
354}
355
356#[utoipa::path(
357    post,
358    path = "/scim/v1/Entry",
359    responses(
360        (status = 200, content_type="application/json", body=ScimEntry),
361        ApiResponseWithout200,
362    ),
363    security(("token_jwt" = [])),
364    tag = "scim",
365    operation_id = "scim_entry_post"
366)]
367async fn scim_entry_post(
368    State(state): State<ServerState>,
369    Extension(kopid): Extension<KOpId>,
370    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
371    Json(post_generic): Json<ScimEntryPostGeneric>,
372) -> Result<Json<ScimEntryKanidm>, WebError> {
373    state
374        .qe_w_ref
375        .scim_entry_create(client_auth_info, kopid.eventid, &[], post_generic)
376        .await
377        .map(Json::from)
378        .map_err(WebError::from)
379}
380
381#[utoipa::path(
382    put,
383    path = "/scim/v1/Entry",
384    responses(
385        (status = 200, content_type="application/json", body=ScimEntry),
386        ApiResponseWithout200,
387    ),
388    security(("token_jwt" = [])),
389    tag = "scim",
390    operation_id = "scim_entry_put"
391)]
392async fn scim_entry_put(
393    State(state): State<ServerState>,
394    Extension(kopid): Extension<KOpId>,
395    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
396    Json(put_generic): Json<ScimEntryPutGeneric>,
397) -> Result<Json<ScimEntryKanidm>, WebError> {
398    state
399        .qe_w_ref
400        .handle_scim_entry_put(client_auth_info, kopid.eventid, put_generic)
401        .await
402        .map(Json::from)
403        .map_err(WebError::from)
404}
405
406#[utoipa::path(
407    delete,
408    path = "/scim/v1/Entry/{id}",
409    responses(
410        (status = 200, content_type="application/json", body=ScimEntry),
411        ApiResponseWithout200,
412    ),
413    security(("token_jwt" = [])),
414    tag = "scim",
415    operation_id = "scim_entry_id_delete"
416)]
417async fn scim_entry_id_delete(
418    State(state): State<ServerState>,
419    Path(id): Path<String>,
420    Extension(kopid): Extension<KOpId>,
421    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
422) -> Result<Json<()>, WebError> {
423    state
424        .qe_w_ref
425        .scim_entry_id_delete(client_auth_info, kopid.eventid, id, EntryClass::Object)
426        .await
427        .map(Json::from)
428        .map_err(WebError::from)
429}
430
431#[utoipa::path(
432    get,
433    path = "/scim/v1/Entry/{id}",
434    responses(
435        (status = 200, content_type="application/json", body=ScimEntry),
436        ApiResponseWithout200,
437    ),
438    security(("token_jwt" = [])),
439    tag = "scim",
440    operation_id = "scim_entry_id_get"
441)]
442async fn scim_entry_id_get(
443    State(state): State<ServerState>,
444    Path(id): Path<String>,
445    Extension(kopid): Extension<KOpId>,
446    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
447    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
448) -> Result<Json<ScimEntryKanidm>, WebError> {
449    state
450        .qe_r_ref
451        .scim_entry_id_get(
452            client_auth_info,
453            kopid.eventid,
454            id,
455            EntryClass::Object,
456            scim_entry_get_query,
457        )
458        .await
459        .map(Json::from)
460        .map_err(WebError::from)
461}
462
463#[utoipa::path(
464    get,
465    path = "/scim/v1/Person/{id}",
466    responses(
467        (status = 200, content_type="application/json", body=ScimEntry),
468        ApiResponseWithout200,
469    ),
470    security(("token_jwt" = [])),
471    tag = "scim",
472    operation_id = "scim_person_id_get"
473)]
474async fn scim_person_id_get(
475    State(state): State<ServerState>,
476    Path(id): Path<String>,
477    Extension(kopid): Extension<KOpId>,
478    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
479    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
480) -> Result<Json<ScimEntryKanidm>, WebError> {
481    state
482        .qe_r_ref
483        .scim_entry_id_get(
484            client_auth_info,
485            kopid.eventid,
486            id,
487            EntryClass::Person,
488            scim_entry_get_query,
489        )
490        .await
491        .map(Json::from)
492        .map_err(WebError::from)
493}
494
495#[utoipa::path(
496    post,
497    path = "/scim/v1/Person/{id}/Application/_create_password",
498    request_body = ScimApplicationPasswordCreate,
499    responses(
500        (status = 200, content_type="application/json", body=ScimApplicationPassword),
501        ApiResponseWithout200,
502    ),
503    security(("token_jwt" = [])),
504    tag = "scim",
505    operation_id = "scim_person_id_application_create_password"
506)]
507async fn scim_person_id_application_create_password(
508    State(state): State<ServerState>,
509    Path(id): Path<String>,
510    Extension(kopid): Extension<KOpId>,
511    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
512    Json(request): Json<ScimApplicationPasswordCreate>,
513) -> Result<Json<ScimApplicationPassword>, WebError> {
514    state
515        .qe_w_ref
516        .scim_person_application_create_password(client_auth_info, kopid.eventid, id, request)
517        .await
518        .map(Json::from)
519        .map_err(WebError::from)
520}
521
522#[utoipa::path(
523    get,
524    path = "/scim/v1/Person/{id}/Application/{apppwd_uuid}",
525    responses(
526        DefaultApiResponse,
527    ),
528    security(("token_jwt" = [])),
529    tag = "scim",
530    operation_id = "scim_person_id_application_delete_password"
531)]
532async fn scim_person_id_application_delete_password(
533    State(state): State<ServerState>,
534    Path((id, apppwd_id)): Path<(String, Uuid)>,
535    Extension(kopid): Extension<KOpId>,
536    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
537) -> Result<Json<()>, WebError> {
538    state
539        .qe_w_ref
540        .scim_person_application_delete_password(client_auth_info, kopid.eventid, id, apppwd_id)
541        .await
542        .map(Json::from)
543        .map_err(WebError::from)
544}
545
546#[utoipa::path(
547    get,
548    path = "/scim/v1/Application",
549    responses(
550        (status = 200, content_type="application/json", body=ScimEntry),
551        ApiResponseWithout200,
552    ),
553    security(("token_jwt" = [])),
554    tag = "scim",
555    operation_id = "scim_application_get"
556)]
557async fn scim_application_get(
558    State(state): State<ServerState>,
559    Extension(kopid): Extension<KOpId>,
560    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
561    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
562) -> Result<Json<ScimListResponse>, WebError> {
563    state
564        .qe_r_ref
565        .scim_entry_search(
566            client_auth_info,
567            kopid.eventid,
568            EntryClass::Application.into(),
569            scim_entry_get_query,
570        )
571        .await
572        .map(Json::from)
573        .map_err(WebError::from)
574}
575
576#[utoipa::path(
577    post,
578    path = "/scim/v1/Application",
579    request_body = ScimEntryPostGeneric,
580    responses(
581        (status = 200, content_type="application/json", body=ScimEntry),
582        ApiResponseWithout200,
583    ),
584    security(("token_jwt" = [])),
585    tag = "scim",
586    operation_id = "scim_application_post"
587)]
588async fn scim_application_post(
589    State(state): State<ServerState>,
590    Extension(kopid): Extension<KOpId>,
591    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
592    Json(entry_post): Json<ScimEntryPostGeneric>,
593) -> Result<Json<ScimEntryKanidm>, WebError> {
594    state
595        .qe_w_ref
596        .scim_entry_create(
597            client_auth_info,
598            kopid.eventid,
599            &[
600                EntryClass::Account,
601                EntryClass::ServiceAccount,
602                EntryClass::Application,
603            ],
604            entry_post,
605        )
606        .await
607        .map(Json::from)
608        .map_err(WebError::from)
609}
610
611#[utoipa::path(
612    delete,
613    path = "/scim/v1/Application/{id}",
614    responses(
615        (status = 200, content_type="application/json"),
616        ApiResponseWithout200,
617    ),
618    security(("token_jwt" = [])),
619    tag = "scim",
620    operation_id = "scim_application_id_get"
621)]
622async fn scim_application_id_get(
623    State(state): State<ServerState>,
624    Path(id): Path<String>,
625    Extension(kopid): Extension<KOpId>,
626    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
627) -> Result<Json<ScimEntryKanidm>, WebError> {
628    state
629        .qe_r_ref
630        .scim_entry_id_get(
631            client_auth_info,
632            kopid.eventid,
633            id,
634            EntryClass::Application,
635            ScimEntryGetQuery::default(),
636        )
637        .await
638        .map(Json::from)
639        .map_err(WebError::from)
640}
641
642#[utoipa::path(
643    delete,
644    path = "/scim/v1/Application/{id}",
645    responses(
646        (status = 200, content_type="application/json"),
647        ApiResponseWithout200,
648    ),
649    security(("token_jwt" = [])),
650    tag = "scim",
651    operation_id = "scim_application_id_delete"
652)]
653async fn scim_application_id_delete(
654    State(state): State<ServerState>,
655    Path(id): Path<String>,
656    Extension(kopid): Extension<KOpId>,
657    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
658) -> Result<Json<()>, WebError> {
659    state
660        .qe_w_ref
661        .scim_entry_id_delete(client_auth_info, kopid.eventid, id, EntryClass::Application)
662        .await
663        .map(Json::from)
664        .map_err(WebError::from)
665}
666
667#[utoipa::path(
668    get,
669    path = "/scim/v1/Class",
670    responses(
671        (status = 200, content_type="application/json", body=ScimEntry),
672        ApiResponseWithout200,
673    ),
674    security(("token_jwt" = [])),
675    tag = "scim",
676    operation_id = "scim_schema_class_get"
677)]
678async fn scim_schema_class_get(
679    State(state): State<ServerState>,
680    Extension(kopid): Extension<KOpId>,
681    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
682    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
683) -> Result<Json<ScimListResponse>, WebError> {
684    state
685        .qe_r_ref
686        .scim_entry_search(
687            client_auth_info,
688            kopid.eventid,
689            EntryClass::ClassType.into(),
690            scim_entry_get_query,
691        )
692        .await
693        .map(Json::from)
694        .map_err(WebError::from)
695}
696
697#[utoipa::path(
698    get,
699    path = "/scim/v1/Attribute",
700    responses(
701        (status = 200, content_type="application/json", body=ScimEntry),
702        ApiResponseWithout200,
703    ),
704    security(("token_jwt" = [])),
705    tag = "scim",
706    operation_id = "scim_schema_attribute_get"
707)]
708async fn scim_schema_attribute_get(
709    State(state): State<ServerState>,
710    Extension(kopid): Extension<KOpId>,
711    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
712    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
713) -> Result<Json<ScimListResponse>, WebError> {
714    state
715        .qe_r_ref
716        .scim_entry_search(
717            client_auth_info,
718            kopid.eventid,
719            EntryClass::AttributeType.into(),
720            scim_entry_get_query,
721        )
722        .await
723        .map(Json::from)
724        .map_err(WebError::from)
725}
726
727#[utoipa::path(
728    get,
729    path = "/scim/v1/Message",
730    responses(
731        (status = 200, content_type="application/json", body=ScimEntry),
732        ApiResponseWithout200,
733    ),
734    security(("token_jwt" = [])),
735    tag = "scim",
736    operation_id = "scim_message_get"
737)]
738async fn scim_message_get(
739    State(state): State<ServerState>,
740    Extension(kopid): Extension<KOpId>,
741    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
742    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
743) -> Result<Json<ScimListResponse>, WebError> {
744    state
745        .qe_r_ref
746        .scim_entry_search(
747            client_auth_info,
748            kopid.eventid,
749            EntryClass::OutboundMessage.into(),
750            scim_entry_get_query,
751        )
752        .await
753        .map(Json::from)
754        .map_err(WebError::from)
755}
756
757#[utoipa::path(
758    get,
759    path = "/scim/v1/Message/{id}",
760    responses(
761        (status = 200, content_type="application/json", body=ScimEntry),
762        ApiResponseWithout200,
763    ),
764    security(("token_jwt" = [])),
765    tag = "scim",
766    operation_id = "scim_message_id_get"
767)]
768async fn scim_message_id_get(
769    State(state): State<ServerState>,
770    Path(id): Path<String>,
771    Extension(kopid): Extension<KOpId>,
772    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
773    Query(scim_entry_get_query): Query<ScimEntryGetQuery>,
774) -> Result<Json<ScimEntryKanidm>, WebError> {
775    state
776        .qe_r_ref
777        .scim_entry_id_get(
778            client_auth_info,
779            kopid.eventid,
780            id,
781            EntryClass::OutboundMessage,
782            scim_entry_get_query,
783        )
784        .await
785        .map(Json::from)
786        .map_err(WebError::from)
787}
788
789#[utoipa::path(
790    get,
791    path = "/scim/v1/Message/_ready",
792    responses(
793        (status = 200, content_type="application/json", body=ScimEntry),
794        ApiResponseWithout200,
795    ),
796    security(("token_jwt" = [])),
797    tag = "scim",
798    operation_id = "scim_message_ready_get"
799)]
800async fn scim_message_ready_get(
801    State(state): State<ServerState>,
802    Extension(kopid): Extension<KOpId>,
803    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
804) -> Result<Json<ScimListResponse>, WebError> {
805    //
806    state
807        .qe_r_ref
808        .scim_message_ready_search(client_auth_info, kopid.eventid)
809        .await
810        .map(Json::from)
811        .map_err(WebError::from)
812}
813
814#[utoipa::path(
815    delete,
816    path = "/scim/v1/Message/{id}/_sent",
817    responses(
818        (status = 200, content_type="application/json"),
819        ApiResponseWithout200,
820    ),
821    security(("token_jwt" = [])),
822    tag = "scim",
823    operation_id = "scim_message_id_sent_post"
824)]
825async fn scim_message_id_sent_post(
826    State(state): State<ServerState>,
827    Path(message_id): Path<Uuid>,
828    Extension(kopid): Extension<KOpId>,
829    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
830) -> Result<Json<()>, WebError> {
831    state
832        .qe_w_ref
833        .scim_message_id_sent(client_auth_info, kopid.eventid, message_id)
834        .await
835        .map(Json::from)
836        .map_err(WebError::from)
837}
838
839#[utoipa::path(
840    get,
841    path = "/scim/v1/Person/{id}/_messages/_send_test",
842    responses(
843        (status = 200, content_type="application/json", body=ScimEntry),
844        ApiResponseWithout200,
845    ),
846    security(("token_jwt" = [])),
847    tag = "scim",
848    operation_id = "scim_person_id_message_send_test_get"
849)]
850async fn scim_person_id_message_send_test_get(
851    State(state): State<ServerState>,
852    Path(id): Path<String>,
853    Extension(kopid): Extension<KOpId>,
854    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
855) -> Result<Json<()>, WebError> {
856    state
857        .qe_w_ref
858        .scim_person_message_send_test(client_auth_info, kopid.eventid, id)
859        .await
860        .map(Json::from)
861        .map_err(WebError::from)
862}
863
864#[instrument(level = "debug", skip_all, name = "https_v1_scim_route_setup")]
865pub fn route_setup() -> Router<ServerState> {
866    Router::new()
867        .route(
868            "/v1/sync_account",
869            get(sync_account_get).post(sync_account_post),
870        )
871        .route(
872            "/v1/sync_account/{id}",
873            get(sync_account_id_get).patch(sync_account_id_patch),
874        )
875        .route(
876            "/v1/sync_account/{id}/_attr/{attr}",
877            get(sync_account_id_attr_get).put(sync_account_id_attr_put),
878        )
879        .route(
880            "/v1/sync_account/{id}/_finalise",
881            get(sync_account_id_finalise_get),
882        )
883        .route(
884            "/v1/sync_account/{id}/_terminate",
885            get(sync_account_id_terminate_get),
886        )
887        .route(
888            "/v1/sync_account/{id}/_sync_token",
889            post(sync_account_token_post).delete(sync_account_token_delete),
890        )
891        // https://datatracker.ietf.org/doc/html/rfc7644#section-3.2
892        //
893        //  HTTP   SCIM Usage
894        //  Method
895        //  ------ --------------------------------------------------------------
896        //  GET    Retrieves one or more complete or partial resources.
897        //
898        //  POST   Depending on the endpoint, creates new resources, creates a
899        //         search request, or MAY be used to bulk-modify resources.
900        //
901        //  PUT    Modifies a resource by replacing existing attributes with a
902        //         specified set of replacement attributes (replace).  PUT
903        //         MUST NOT be used to create new resources.
904        //
905        //  PATCH  Modifies a resource with a set of client-specified changes
906        //         (partial update).
907        //
908        //  DELETE Deletes a resource.
909        //
910        //  Resource Endpoint         Operations             Description
911        //  -------- ---------------- ---------------------- --------------------
912        //  User     /Users           GET (Section 3.4.1),   Retrieve, add,
913        //                            POST (Section 3.3),    modify Users.
914        //                            PUT (Section 3.5.1),
915        //                            PATCH (Section 3.5.2),
916        //                            DELETE (Section 3.6)
917        //
918        //  Group    /Groups          GET (Section 3.4.1),   Retrieve, add,
919        //                            POST (Section 3.3),    modify Groups.
920        //                            PUT (Section 3.5.1),
921        //                            PATCH (Section 3.5.2),
922        //                            DELETE (Section 3.6)
923        //
924        //  Self     /Me              GET, POST, PUT, PATCH, Alias for operations
925        //                            DELETE (Section 3.11)  against a resource
926        //                                                   mapped to an
927        //                                                   authenticated
928        //                                                   subject (e.g.,
929        //                                                   User).
930        //
931        //  Service  /ServiceProvider GET (Section 4)        Retrieve service
932        //  provider Config                                  provider's
933        //  config.                                          configuration.
934        //
935        //  Resource /ResourceTypes   GET (Section 4)        Retrieve supported
936        //  type                                             resource types.
937        //
938        //  Schema   /Schemas         GET (Section 4)        Retrieve one or more
939        //                                                   supported schemas.
940        //
941        //  Bulk     /Bulk            POST (Section 3.7)     Bulk updates to one
942        //                                                   or more resources.
943        //
944        //  Search   [prefix]/.search POST (Section 3.4.3)   Search from system
945        //                                                   root or within a
946        //                                                   resource endpoint
947        //                                                   for one or more
948        //                                                   resource types using
949        //                                                   POST.
950        //  -- Kanidm Resources
951        //
952        //  Entry    /Entry/{id}      GET                    Retrieve a generic entry
953        //                                                   of any kind from the database.
954        //                                                   {id} is any unique id.
955        .route(
956            "/scim/v1/Entry",
957            get(scim_entry_get)
958                .post(scim_entry_post)
959                .put(scim_entry_put),
960        )
961        .route(
962            "/scim/v1/Entry/{id}",
963            get(scim_entry_id_get).delete(scim_entry_id_delete),
964        )
965        //  Person   /Person/{id}     GET                    Retrieve a a person from the
966        //                                                   database.
967        //                                                   {id} is any unique id.
968        .route("/scim/v1/Person/{id}", get(scim_person_id_get))
969        .route(
970            "/scim/v1/Person/{id}/Application/_create_password",
971            post(scim_person_id_application_create_password),
972        )
973        .route(
974            "/scim/v1/Person/{id}/Application/{apppwd_id}",
975            delete(scim_person_id_application_delete_password),
976        )
977        //  Person   /Person/{id}/_messages/_send_test
978        .route(
979            "/scim/v1/Person/{id}/_message/_send_test",
980            get(scim_person_id_message_send_test_get),
981        )
982        //
983        //  Sync     /Sync            GET                    Retrieve the current
984        //                                                   sync state associated
985        //                                                   with the authenticated
986        //                                                   session
987        //
988        //                            POST                   Send a sync update
989        //
990        //
991        //  Application   /Application     Post              Create a new application
992        //
993        .route(
994            "/scim/v1/Application",
995            get(scim_application_get).post(scim_application_post),
996        )
997        //  Application   /Application/{id}     Delete      Delete the application identified by id
998        //
999        .route(
1000            "/scim/v1/Application/{id}",
1001            get(scim_application_id_get).delete(scim_application_id_delete),
1002        )
1003        //  Class      /Class          GET                  List or query Schema Classes
1004        //
1005        .route("/scim/v1/Class", get(scim_schema_class_get))
1006        //  Attribute /Attribute          GET               List or query Schema Attributes
1007        //
1008        .route("/scim/v1/Attribute", get(scim_schema_attribute_get))
1009        //  Message    /Message          GET               List or query queued Messages
1010        //                               POST              Create a new message for sending.
1011        .route("/scim/v1/Message", get(scim_message_get))
1012        //  Message    /Message/_ready   GET               List Messages that are ready to be sent
1013        .route("/scim/v1/Message/_ready", get(scim_message_ready_get))
1014        //  Message    /Message/{id}    GET                Fetch message by id
1015        .route("/scim/v1/Message/{id}", get(scim_message_id_get))
1016        //  Message    /Message/{id}/_sent     POST         Mark this message as having been processed and sent
1017        .route(
1018            "/scim/v1/Message/{id}/_sent",
1019            post(scim_message_id_sent_post),
1020        )
1021        // Synchronisation routes.
1022        .route(
1023            "/scim/v1/Sync",
1024            post(scim_sync_post)
1025                .layer(DefaultBodyLimit::max(DEFAULT_SCIM_SYNC_BYTES))
1026                .get(scim_sync_get),
1027        )
1028        .route("/scim/v1/Sink", get(scim_sink_get)) // skip_route_check
1029}