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)]
36pub 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 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)]
77pub 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)]
99pub 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
259async 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), 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 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 .route("/scim/v1/Entry/:id", get(scim_entry_id_get))
712 .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 .route(
736 "/scim/v1/Application",
737 get(scim_application_get).post(scim_application_post),
738 )
739 .route(
742 "/scim/v1/Application/:id",
743 get(scim_application_id_get).delete(scim_application_id_delete),
744 )
745 .route("/scim/v1/Class", get(scim_schema_class_get))
748 .route("/scim/v1/Attribute", get(scim_schema_attribute_get))
751 .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)) }