kanidmd_core/https/
v1_domain.rs
1use super::apidocs::response_schema::DefaultApiResponse;
2use super::errors::WebError;
3use super::ServerState;
4use crate::https::extractors::DomainInfo;
5use crate::https::extractors::VerifiedClientInformation;
6use axum::extract::State;
7use axum::Json;
8use axum::{
9 http::header::CONTENT_TYPE,
10 http::StatusCode,
11 response::{IntoResponse, Response},
12};
13use kanidm_proto::internal::{ImageType, ImageValue};
14use kanidmd_lib::prelude::*;
15use kanidmd_lib::valueset::image::ImageValueThings;
16use sketching::admin_error;
17
18pub(crate) async fn image_get(DomainInfo(domain_info): DomainInfo) -> Response {
19 match domain_info.image() {
20 Some(image) => (
21 StatusCode::OK,
22 [(CONTENT_TYPE, image.filetype.as_content_type_str())],
23 image.contents.clone(),
24 )
25 .into_response(),
26 None => {
27 warn!("No image set for domain");
28 (StatusCode::NOT_FOUND, "").into_response()
29 }
30 }
31}
32
33#[utoipa::path(
34 delete,
35 path = "/v1/domain/_image",
36 responses(
37 DefaultApiResponse,
38 ),
39 security(("token_jwt" = [])),
40 tag = "v1/domain",
41 operation_id = "domain_image_delete"
42)]
43pub(crate) async fn image_delete(
44 State(state): State<ServerState>,
45 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
46) -> Result<Json<()>, WebError> {
47 let f_uuid = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO)));
48
49 state
50 .qe_w_ref
51 .handle_image_update(client_auth_info, f_uuid, None)
52 .await
53 .map(Json::from)
54 .map_err(WebError::from)
55}
56
57#[utoipa::path(
58 post,
59 path = "/v1/domain/_image",
60 responses(
61 DefaultApiResponse,
62 ),
63 security(("token_jwt" = [])),
64 tag = "v1/domain",
65 operation_id = "domain_image_post"
66)]
67pub(crate) async fn image_post(
72 State(state): State<ServerState>,
73 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
74 mut multipart: axum::extract::Multipart,
75) -> Result<Json<()>, WebError> {
76 let mut image: Option<ImageValue> = None;
78
79 while let Some(field) = multipart.next_field().await.unwrap_or(None) {
80 let filename = field.file_name().map(|f| f.to_string()).clone();
81 if let Some(filename) = filename {
82 let content_type = field.content_type().map(|f| f.to_string()).clone();
83
84 let content_type = match content_type {
85 Some(val) => {
86 if VALID_IMAGE_UPLOAD_CONTENT_TYPES.contains(&val.as_str()) {
87 val
88 } else {
89 debug!("Invalid content type: {}", val);
90 return Err(OperationError::InvalidRequestState.into());
91 }
92 }
93 None => {
94 debug!("No content type header provided");
95 return Err(OperationError::InvalidRequestState.into());
96 }
97 };
98 let data = match field.bytes().await {
99 Ok(val) => val,
100 Err(_e) => return Err(OperationError::InvalidRequestState.into()),
101 };
102
103 let filetype = match ImageType::try_from_content_type(&content_type) {
104 Ok(val) => val,
105 Err(_err) => return Err(OperationError::InvalidRequestState.into()),
106 };
107
108 image = Some(ImageValue {
109 filetype,
110 filename: filename.to_string(),
111 contents: data.to_vec(),
112 });
113 };
114 }
115
116 match image {
117 Some(image) => {
118 let image_validation_result = image.validate_image();
119 match image_validation_result {
120 Err(err) => {
121 admin_error!("Invalid image uploaded: {:?}", err);
122 Err(WebError::from(OperationError::InvalidRequestState))
123 }
124 Ok(_) => {
125 let f_uuid =
126 filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO)));
127 state
128 .qe_w_ref
129 .handle_image_update(client_auth_info, f_uuid, Some(image))
130 .await
131 .map(Json::from)
132 .map_err(WebError::from)
133 }
134 }
135 }
136 None => Err(WebError::from(OperationError::InvalidAttribute(
137 "No image included, did you mean to use the DELETE method?".to_string(),
138 ))),
139 }
140}