1use super::apidocs::response_schema::{ApiResponseWithout200, DefaultApiResponse};
2use super::errors::WebError;
3use super::middleware::KOpId;
4use super::oauth2::oauth2_id;
5use super::v1::{
6 json_rest_event_delete_id_attr, json_rest_event_get, json_rest_event_post,
7 json_rest_event_post_id_attr,
8};
9use super::ServerState;
10
11use crate::https::extractors::VerifiedClientInformation;
12use axum::extract::{Path, State};
13use axum::{Extension, Json};
14use kanidm_proto::internal::{ImageType, ImageValue, Oauth2ClaimMapJoin};
15use kanidm_proto::v1::Entry as ProtoEntry;
16use kanidmd_lib::prelude::*;
17use kanidmd_lib::valueset::image::ImageValueThings;
18use sketching::admin_error;
19
20#[utoipa::path(
21 get,
22 path = "/v1/oauth2",
23 responses(
24 (status = 200,content_type="application/json", body=Vec<ProtoEntry>),
25 ApiResponseWithout200,
26 ),
27 security(("token_jwt" = [])),
28 tag = "v1/oauth2",
29 operation_id = "oauth2_get"
30)]
31pub(crate) async fn oauth2_get(
33 State(state): State<ServerState>,
34 Extension(kopid): Extension<KOpId>,
35 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
36) -> Result<Json<Vec<ProtoEntry>>, WebError> {
37 let filter = filter_all!(f_eq(
38 Attribute::Class,
39 EntryClass::OAuth2ResourceServer.into()
40 ));
41 json_rest_event_get(state, None, filter, kopid, client_auth_info).await
42}
43
44#[utoipa::path(
45 post,
46 path = "/v1/oauth2/_basic",
47 request_body=ProtoEntry,
48 responses(
49 DefaultApiResponse,
50 ),
51 security(("token_jwt" = [])),
52 tag = "v1/oauth2",
53 operation_id = "oauth2_basic_post"
54)]
55pub(crate) async fn oauth2_basic_post(
57 State(state): State<ServerState>,
58 Extension(kopid): Extension<KOpId>,
59 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
60 Json(obj): Json<ProtoEntry>,
61) -> Result<Json<()>, WebError> {
62 let classes = vec![
63 EntryClass::OAuth2ResourceServer.to_string(),
64 EntryClass::OAuth2ResourceServerBasic.to_string(),
65 EntryClass::Account.to_string(),
66 EntryClass::Object.to_string(),
67 ];
68 json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
69}
70
71#[utoipa::path(
72 post,
73 path = "/v1/oauth2/_public",
74 request_body=ProtoEntry,
75 responses(
76 DefaultApiResponse,
77 ),
78 security(("token_jwt" = [])),
79 tag = "v1/oauth2",
80 operation_id = "oauth2_public_post"
81)]
82pub(crate) async fn oauth2_public_post(
84 State(state): State<ServerState>,
85 Extension(kopid): Extension<KOpId>,
86 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
87 Json(obj): Json<ProtoEntry>,
88) -> Result<Json<()>, WebError> {
89 let classes = vec![
90 EntryClass::OAuth2ResourceServer.to_string(),
91 EntryClass::OAuth2ResourceServerPublic.to_string(),
92 EntryClass::Account.to_string(),
93 EntryClass::Object.to_string(),
94 ];
95 json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
96}
97
98#[utoipa::path(
99 get,
100 path = "/v1/oauth2/{rs_name}",
101 responses(
102 (status = 200, body=Option<ProtoEntry>, content_type="application/json"),
103 ApiResponseWithout200,
104 ),
105 security(("token_jwt" = [])),
106 tag = "v1/oauth2",
107 operation_id = "oauth2_id_get"
108)]
109pub(crate) async fn oauth2_id_get(
111 State(state): State<ServerState>,
112 Path(rs_name): Path<String>,
113 Extension(kopid): Extension<KOpId>,
114 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
115) -> Result<Json<Option<ProtoEntry>>, WebError> {
116 let filter = oauth2_id(&rs_name);
117 state
118 .qe_r_ref
119 .handle_internalsearch(client_auth_info, filter, None, kopid.eventid)
120 .await
121 .map(|mut r| r.pop())
122 .map(Json::from)
123 .map_err(WebError::from)
124}
125
126#[utoipa::path(
127 get,
128 path = "/v1/oauth2/{rs_name}/_basic_secret",
129 responses(
130 (status = 200,content_type="application/json", body=Option<String>),
131 ApiResponseWithout200,
132 ),
133 security(("token_jwt" = [])),
134 tag = "v1/oauth2",
135 operation_id = "oauth2_id_get_basic_secret"
136)]
137#[instrument(level = "info", skip(state))]
139pub(crate) async fn oauth2_id_get_basic_secret(
140 State(state): State<ServerState>,
141 Extension(kopid): Extension<KOpId>,
142 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
143 Path(rs_name): Path<String>,
144) -> Result<Json<Option<String>>, WebError> {
145 let filter = oauth2_id(&rs_name);
146 state
147 .qe_r_ref
148 .handle_oauth2_basic_secret_read(client_auth_info, filter, kopid.eventid)
149 .await
150 .map(Json::from)
151 .map_err(WebError::from)
152}
153
154#[utoipa::path(
155 patch,
156 path = "/v1/oauth2/{rs_name}",
157 request_body=ProtoEntry,
158 responses(
159 DefaultApiResponse,
160 ),
161 security(("token_jwt" = [])),
162 tag = "v1/oauth2",
163 operation_id = "oauth2_id_patch"
164)]
165pub(crate) async fn oauth2_id_patch(
167 State(state): State<ServerState>,
168 Path(rs_name): Path<String>,
169 Extension(kopid): Extension<KOpId>,
170 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
171 Json(obj): Json<ProtoEntry>,
172) -> Result<Json<()>, WebError> {
173 let filter = oauth2_id(&rs_name);
174
175 state
176 .qe_w_ref
177 .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
178 .await
179 .map(Json::from)
180 .map_err(WebError::from)
181}
182
183#[utoipa::path(
184 post,
185 path = "/v1/oauth2/{rs_name}/_scopemap/{group}",
186 request_body=Vec<String>,
187 responses(
188 DefaultApiResponse,
189 ),
190 security(("token_jwt" = [])),
191 tag = "v1/oauth2",
192 operation_id = "oauth2_id_scopemap_post"
193)]
194pub(crate) async fn oauth2_id_scopemap_post(
196 State(state): State<ServerState>,
197 Extension(kopid): Extension<KOpId>,
198 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
199 Path((rs_name, group)): Path<(String, String)>,
200 Json(scopes): Json<Vec<String>>,
201) -> Result<Json<()>, WebError> {
202 let filter = oauth2_id(&rs_name);
203
204 state
205 .qe_w_ref
206 .handle_oauth2_scopemap_update(client_auth_info, group, scopes, filter, kopid.eventid)
207 .await
208 .map(Json::from)
209 .map_err(WebError::from)
210}
211
212#[utoipa::path(
213 post,
214 path = "/v1/oauth2/{rs_name}/_attr/{attr}",
215 request_body=Vec<String>,
216 responses(
217 DefaultApiResponse,
218 ),
219 security(("token_jwt" = [])),
220 tag = "v1/oauth2/attr",
221 operation_id = "oauth2_id_attr_post",
222)]
223pub async fn oauth2_id_attr_post(
224 Path((id, attr)): Path<(String, String)>,
225 State(state): State<ServerState>,
226 Extension(kopid): Extension<KOpId>,
227 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
228 Json(values): Json<Vec<String>>,
229) -> Result<Json<()>, WebError> {
230 let filter = filter_all!(f_eq(
231 Attribute::Class,
232 EntryClass::OAuth2ResourceServer.into()
233 ));
234 json_rest_event_post_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
235}
236
237#[utoipa::path(
238 delete,
239 path = "/v1/oauth2/{rs_name}/_attr/{attr}",
240 request_body=Option<Vec<String>>,
241 responses(
242 DefaultApiResponse,
243 ),
244 security(("token_jwt" = [])),
245 tag = "v1/oauth2/attr",
246 operation_id = "oauth2_id_attr_delete",
247)]
248pub async fn oauth2_id_attr_delete(
249 Path((id, attr)): Path<(String, String)>,
250 State(state): State<ServerState>,
251 Extension(kopid): Extension<KOpId>,
252 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
253 values: Option<Json<Vec<String>>>,
254) -> Result<Json<()>, WebError> {
255 let filter = filter_all!(f_eq(
256 Attribute::Class,
257 EntryClass::OAuth2ResourceServer.into()
258 ));
259 let values = values.map(|v| v.0);
260 json_rest_event_delete_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
261}
262
263#[utoipa::path(
264 delete,
265 path = "/v1/oauth2/{rs_name}/_scopemap/{group}",
266 responses(
267 DefaultApiResponse,
268 ),
269 security(("token_jwt" = [])),
270 tag = "v1/oauth2",
271 operation_id = "oauth2_id_scopemap_delete"
272)]
273pub(crate) async fn oauth2_id_scopemap_delete(
275 State(state): State<ServerState>,
276 Extension(kopid): Extension<KOpId>,
277 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
278 Path((rs_name, group)): Path<(String, String)>,
279) -> Result<Json<()>, WebError> {
280 let filter = oauth2_id(&rs_name);
281 state
282 .qe_w_ref
283 .handle_oauth2_scopemap_delete(client_auth_info, group, filter, kopid.eventid)
284 .await
285 .map(Json::from)
286 .map_err(WebError::from)
287}
288
289#[utoipa::path(
290 post,
291 path = "/v1/oauth2/{rs_name}/_claimmap/{claim_name}/{group}",
292 request_body=Vec<String>,
293 responses(
294 DefaultApiResponse,
295 ),
296 security(("token_jwt" = [])),
297 tag = "v1/oauth2",
298 operation_id = "oauth2_id_claimmap_post"
299)]
300pub(crate) async fn oauth2_id_claimmap_post(
302 State(state): State<ServerState>,
303 Extension(kopid): Extension<KOpId>,
304 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
305 Path((rs_name, claim_name, group)): Path<(String, String, String)>,
306 Json(claims): Json<Vec<String>>,
307) -> Result<Json<()>, WebError> {
308 let filter = oauth2_id(&rs_name);
309 state
310 .qe_w_ref
311 .handle_oauth2_claimmap_update(
312 client_auth_info,
313 claim_name,
314 group,
315 claims,
316 filter,
317 kopid.eventid,
318 )
319 .await
320 .map(Json::from)
321 .map_err(WebError::from)
322}
323
324#[utoipa::path(
325 post,
326 path = "/v1/oauth2/{rs_name}/_claimmap/{claim_name}",
327 request_body=Oauth2ClaimMapJoin,
328 responses(
329 DefaultApiResponse,
330 ),
331 security(("token_jwt" = [])),
332 tag = "v1/oauth2",
333 operation_id = "oauth2_id_claimmap_join_post"
334)]
335pub(crate) async fn oauth2_id_claimmap_join_post(
337 State(state): State<ServerState>,
338 Extension(kopid): Extension<KOpId>,
339 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
340 Path((rs_name, claim_name)): Path<(String, String)>,
341 Json(join): Json<Oauth2ClaimMapJoin>,
342) -> Result<Json<()>, WebError> {
343 let filter = oauth2_id(&rs_name);
344 state
345 .qe_w_ref
346 .handle_oauth2_claimmap_join_update(
347 client_auth_info,
348 claim_name,
349 join,
350 filter,
351 kopid.eventid,
352 )
353 .await
354 .map(Json::from)
355 .map_err(WebError::from)
356}
357
358#[utoipa::path(
359 delete,
360 path = "/v1/oauth2/{rs_name}/_claimmap/{claim_name}/{group}",
361 responses(
362 DefaultApiResponse,
363 ),
364 security(("token_jwt" = [])),
365 tag = "v1/oauth2",
366 operation_id = "oauth2_id_claimmap_delete"
367)]
368pub(crate) async fn oauth2_id_claimmap_delete(
370 State(state): State<ServerState>,
371 Extension(kopid): Extension<KOpId>,
372 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
373 Path((rs_name, claim_name, group)): Path<(String, String, String)>,
374) -> Result<Json<()>, WebError> {
375 let filter = oauth2_id(&rs_name);
376 state
377 .qe_w_ref
378 .handle_oauth2_claimmap_delete(client_auth_info, claim_name, group, filter, kopid.eventid)
379 .await
380 .map(Json::from)
381 .map_err(WebError::from)
382}
383
384#[utoipa::path(
385 post,
386 path = "/v1/oauth2/{rs_name}/_sup_scopemap/{group}",
387 responses(
388 DefaultApiResponse,
389 ),
390 security(("token_jwt" = [])),
391 tag = "v1/oauth2",
392 operation_id = "oauth2_id_sup_scopemap_post"
393)]
394pub(crate) async fn oauth2_id_sup_scopemap_post(
396 State(state): State<ServerState>,
397 Extension(kopid): Extension<KOpId>,
398 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
399 Path((rs_name, group)): Path<(String, String)>,
400 Json(scopes): Json<Vec<String>>,
401) -> Result<Json<()>, WebError> {
402 let filter = oauth2_id(&rs_name);
403 state
404 .qe_w_ref
405 .handle_oauth2_sup_scopemap_update(client_auth_info, group, scopes, filter, kopid.eventid)
406 .await
407 .map(Json::from)
408 .map_err(WebError::from)
409}
410
411#[utoipa::path(
412 delete,
413 path = "/v1/oauth2/{rs_name}/_sup_scopemap/{group}",
414 responses(
415 DefaultApiResponse,
416 ),
417 security(("token_jwt" = [])),
418 tag = "v1/oauth2",
419 operation_id = "oauth2_id_sup_scopemap_delete"
420)]
421pub(crate) async fn oauth2_id_sup_scopemap_delete(
423 State(state): State<ServerState>,
424 Extension(kopid): Extension<KOpId>,
425 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
426 Path((rs_name, group)): Path<(String, String)>,
427) -> Result<Json<()>, WebError> {
428 let filter = oauth2_id(&rs_name);
429 state
430 .qe_w_ref
431 .handle_oauth2_sup_scopemap_delete(client_auth_info, group, filter, kopid.eventid)
432 .await
433 .map(Json::from)
434 .map_err(WebError::from)
435}
436
437#[utoipa::path(
438 delete,
439 path = "/v1/oauth2/{rs_name}",
440 responses(
441 DefaultApiResponse,
442 (status = 404),
443 ),
444 security(("token_jwt" = [])),
445 tag = "v1/oauth2",
446 operation_id = "oauth2_id_delete"
447)]
448pub(crate) async fn oauth2_id_delete(
450 State(state): State<ServerState>,
451 Extension(kopid): Extension<KOpId>,
452 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
453 Path(rs_name): Path<String>,
454) -> Result<Json<()>, WebError> {
455 let filter = oauth2_id(&rs_name);
456 state
457 .qe_w_ref
458 .handle_internaldelete(client_auth_info, filter, kopid.eventid)
459 .await
460 .map(Json::from)
461 .map_err(WebError::from)
462}
463
464#[utoipa::path(
465 delete,
466 path = "/v1/oauth2/{rs_name}/_image",
467 responses(
468 DefaultApiResponse,
469 ),
470 security(("token_jwt" = [])),
471 tag = "v1/oauth2",
472 operation_id = "oauth2_id_image_delete"
473)]
474pub(crate) async fn oauth2_id_image_delete(
476 State(state): State<ServerState>,
477 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
478 Path(rs_name): Path<String>,
479) -> Result<Json<()>, WebError> {
480 state
481 .qe_w_ref
482 .handle_image_update(client_auth_info, oauth2_id(&rs_name), None)
483 .await
484 .map(Json::from)
485 .map_err(WebError::from)
486}
487
488#[utoipa::path(
489 post,
490 path = "/v1/oauth2/{rs_name}/_image",
491 responses(
492 DefaultApiResponse,
493 ),
494 security(("token_jwt" = [])),
495 tag = "v1/oauth2",
496 operation_id = "oauth2_id_image_post"
497)]
498pub(crate) async fn oauth2_id_image_post(
503 State(state): State<ServerState>,
504 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
505 Path(rs_name): Path<String>,
506 mut multipart: axum::extract::Multipart,
507) -> Result<Json<()>, WebError> {
508 let mut image: Option<ImageValue> = None;
510
511 while let Some(field) = multipart.next_field().await.unwrap_or(None) {
512 let filename = field.file_name().map(|f| f.to_string()).clone();
513 if let Some(filename) = filename {
514 let content_type = field.content_type().map(|f| f.to_string()).clone();
515
516 let content_type = match content_type {
517 Some(val) => {
518 if VALID_IMAGE_UPLOAD_CONTENT_TYPES.contains(&val.as_str()) {
519 val
520 } else {
521 debug!("Invalid content type: {}", val);
522 return Err(OperationError::InvalidRequestState.into());
523 }
524 }
525 None => {
526 debug!("No content type header provided");
527 return Err(OperationError::InvalidRequestState.into());
528 }
529 };
530 let data = match field.bytes().await {
531 Ok(val) => val,
532 Err(_e) => return Err(OperationError::InvalidRequestState.into()),
533 };
534
535 let filetype = match ImageType::try_from_content_type(&content_type) {
536 Ok(val) => val,
537 Err(_err) => return Err(OperationError::InvalidRequestState.into()),
538 };
539
540 image = Some(ImageValue {
541 filetype,
542 filename: filename.to_string(),
543 contents: data.to_vec(),
544 });
545 };
546 }
547
548 match image {
549 Some(image) => {
550 let image_validation_result = image.validate_image();
551 match image_validation_result {
552 Err(err) => {
553 admin_error!("Invalid image uploaded: {:?}", err);
554 Err(WebError::from(OperationError::InvalidRequestState))
555 }
556 Ok(_) => {
557 let rs_filter = oauth2_id(&rs_name);
558 state
559 .qe_w_ref
560 .handle_image_update(client_auth_info, rs_filter, Some(image))
561 .await
562 .map(Json::from)
563 .map_err(WebError::from)
564 }
565 }
566 }
567 None => Err(WebError::from(OperationError::InvalidAttribute(
568 "No image included, did you mean to use the DELETE method?".to_string(),
569 ))),
570 }
571}