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