kanidmd_core/https/views/admin/
persons.rs
1use crate::https::errors::WebError;
2use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
3use crate::https::middleware::KOpId;
4use crate::https::views::errors::HtmxError;
5use crate::https::views::navbar::NavbarCtx;
6use crate::https::views::Urls;
7use crate::https::ServerState;
8use askama::Template;
9use axum::extract::{Path, State};
10use axum::http::Uri;
11use axum::response::{IntoResponse, Response};
12use axum::Extension;
13use axum_htmx::{HxPushUrl, HxRequest};
14use kanidm_proto::attribute::Attribute;
15use kanidm_proto::internal::OperationError;
16use kanidm_proto::scim_v1::server::{
17 ScimEffectiveAccess, ScimEntryKanidm, ScimListResponse, ScimPerson,
18};
19use kanidm_proto::scim_v1::ScimEntryGetQuery;
20use kanidm_proto::scim_v1::ScimFilter;
21use kanidmd_lib::constants::EntryClass;
22use kanidmd_lib::idm::ClientAuthInfo;
23use std::str::FromStr;
24use uuid::Uuid;
25
26pub const PERSON_ATTRIBUTES: [Attribute; 9] = [
27 Attribute::Uuid,
28 Attribute::Description,
29 Attribute::Name,
30 Attribute::DisplayName,
31 Attribute::Spn,
32 Attribute::Mail,
33 Attribute::Class,
34 Attribute::EntryManagedBy,
35 Attribute::DirectMemberOf,
36];
37
38#[derive(Template)]
39#[template(path = "admin/admin_panel_template.html")]
40pub(crate) struct PersonsView {
41 navbar_ctx: NavbarCtx,
42 partial: PersonsPartialView,
43}
44
45#[derive(Template)]
46#[template(path = "admin/admin_persons_partial.html")]
47struct PersonsPartialView {
48 persons: Vec<(ScimPerson, ScimEffectiveAccess)>,
49}
50
51#[derive(Template)]
52#[template(path = "admin/admin_panel_template.html")]
53struct PersonView {
54 partial: PersonViewPartial,
55 navbar_ctx: NavbarCtx,
56}
57
58#[derive(Template)]
59#[template(path = "admin/admin_person_view_partial.html")]
60struct PersonViewPartial {
61 person: ScimPerson,
62 scim_effective_access: ScimEffectiveAccess,
63}
64
65pub(crate) async fn view_person_view_get(
66 State(state): State<ServerState>,
67 HxRequest(is_htmx): HxRequest,
68 Extension(kopid): Extension<KOpId>,
69 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
70 Path(uuid): Path<Uuid>,
71 DomainInfo(domain_info): DomainInfo,
72) -> axum::response::Result<Response> {
73 let (person, scim_effective_access) =
74 get_person_info(uuid, state, &kopid, client_auth_info).await?;
75 let person_partial = PersonViewPartial {
76 person,
77 scim_effective_access,
78 };
79
80 let path_string = format!("/ui/admin/person/{uuid}/view");
81 let uri = Uri::from_str(path_string.as_str())
82 .map_err(|_| HtmxError::new(&kopid, OperationError::Backend, domain_info.clone()))?;
83 let push_url = HxPushUrl(uri);
84 Ok(if is_htmx {
85 (push_url, person_partial).into_response()
86 } else {
87 (
88 push_url,
89 PersonView {
90 partial: person_partial,
91 navbar_ctx: NavbarCtx { domain_info },
92 },
93 )
94 .into_response()
95 })
96}
97
98pub(crate) async fn view_persons_get(
99 State(state): State<ServerState>,
100 HxRequest(is_htmx): HxRequest,
101 Extension(kopid): Extension<KOpId>,
102 DomainInfo(domain_info): DomainInfo,
103 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
104) -> axum::response::Result<Response> {
105 let persons = get_persons_info(state, &kopid, client_auth_info).await?;
106 let persons_partial = PersonsPartialView { persons };
107
108 let push_url = HxPushUrl(Uri::from_static("/ui/admin/persons"));
109 Ok(if is_htmx {
110 (push_url, persons_partial).into_response()
111 } else {
112 (
113 push_url,
114 PersonsView {
115 navbar_ctx: NavbarCtx { domain_info },
116 partial: persons_partial,
117 },
118 )
119 .into_response()
120 })
121}
122
123pub async fn get_person_info(
124 uuid: Uuid,
125 state: ServerState,
126 kopid: &KOpId,
127 client_auth_info: ClientAuthInfo,
128) -> Result<(ScimPerson, ScimEffectiveAccess), WebError> {
129 let scim_entry: ScimEntryKanidm = state
130 .qe_r_ref
131 .scim_entry_id_get(
132 client_auth_info.clone(),
133 kopid.eventid,
134 uuid.to_string(),
135 EntryClass::Person,
136 ScimEntryGetQuery {
137 attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
138 ext_access_check: true,
139 ..Default::default()
140 },
141 )
142 .await?;
143
144 if let Some(personinfo_info) = scimentry_into_personinfo(scim_entry) {
145 Ok(personinfo_info)
146 } else {
147 Err(WebError::from(OperationError::InvalidState))
148 }
149}
150
151async fn get_persons_info(
152 state: ServerState,
153 kopid: &KOpId,
154 client_auth_info: ClientAuthInfo,
155) -> Result<Vec<(ScimPerson, ScimEffectiveAccess)>, WebError> {
156 let filter = ScimFilter::Equal(Attribute::Class.into(), EntryClass::Person.into());
157
158 let base: ScimListResponse = state
159 .qe_r_ref
160 .scim_entry_search(
161 client_auth_info.clone(),
162 kopid.eventid,
163 filter,
164 ScimEntryGetQuery {
165 attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
166 ext_access_check: true,
167 sort_by: Some(Attribute::Name),
168 ..Default::default()
169 },
170 )
171 .await?;
172
173 let persons: Vec<_> = base
174 .resources
175 .into_iter()
176 .filter_map(scimentry_into_personinfo)
178 .collect();
179
180 Ok(persons)
181}
182
183fn scimentry_into_personinfo(
184 scim_entry: ScimEntryKanidm,
185) -> Option<(ScimPerson, ScimEffectiveAccess)> {
186 let scim_effective_access = scim_entry.ext_access_check.clone()?; let person = ScimPerson::try_from(scim_entry).ok()?;
188
189 Some((person, scim_effective_access))
190}