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, UserAuthToken};
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.clone()).await?;
75 let person_partial = PersonViewPartial {
76 person,
77 scim_effective_access,
78 };
79 let uat: &UserAuthToken = client_auth_info
80 .pre_validated_uat()
81 .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
82
83 let path_string = format!("/ui/admin/person/{uuid}/view");
84 let uri = Uri::from_str(path_string.as_str())
85 .map_err(|_| HtmxError::new(&kopid, OperationError::Backend, domain_info.clone()))?;
86 let push_url = HxPushUrl(uri);
87 Ok(if is_htmx {
88 (push_url, person_partial).into_response()
89 } else {
90 (
91 push_url,
92 PersonView {
93 partial: person_partial,
94 navbar_ctx: NavbarCtx::new(domain_info, &uat.ui_hints),
95 },
96 )
97 .into_response()
98 })
99}
100
101pub(crate) async fn view_persons_get(
102 State(state): State<ServerState>,
103 HxRequest(is_htmx): HxRequest,
104 Extension(kopid): Extension<KOpId>,
105 DomainInfo(domain_info): DomainInfo,
106 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
107) -> axum::response::Result<Response> {
108 let persons = get_persons_info(state, &kopid, client_auth_info.clone()).await?;
109 let persons_partial = PersonsPartialView { persons };
110 let uat: &UserAuthToken = client_auth_info
111 .pre_validated_uat()
112 .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
113 let push_url = HxPushUrl(Uri::from_static("/ui/admin/persons"));
114 Ok(if is_htmx {
115 (push_url, persons_partial).into_response()
116 } else {
117 (
118 push_url,
119 PersonsView {
120 navbar_ctx: NavbarCtx::new(domain_info, &uat.ui_hints),
121 partial: persons_partial,
122 },
123 )
124 .into_response()
125 })
126}
127
128pub async fn get_person_info(
129 uuid: Uuid,
130 state: ServerState,
131 kopid: &KOpId,
132 client_auth_info: ClientAuthInfo,
133) -> Result<(ScimPerson, ScimEffectiveAccess), WebError> {
134 let scim_entry: ScimEntryKanidm = state
135 .qe_r_ref
136 .scim_entry_id_get(
137 client_auth_info.clone(),
138 kopid.eventid,
139 uuid.to_string(),
140 EntryClass::Person,
141 ScimEntryGetQuery {
142 attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
143 ext_access_check: true,
144 ..Default::default()
145 },
146 )
147 .await?;
148
149 if let Some(personinfo_info) = scimentry_into_personinfo(scim_entry) {
150 Ok(personinfo_info)
151 } else {
152 Err(WebError::from(OperationError::InvalidState))
153 }
154}
155
156async fn get_persons_info(
157 state: ServerState,
158 kopid: &KOpId,
159 client_auth_info: ClientAuthInfo,
160) -> Result<Vec<(ScimPerson, ScimEffectiveAccess)>, WebError> {
161 let filter = ScimFilter::Equal(Attribute::Class.into(), EntryClass::Person.into());
162
163 let base: ScimListResponse = state
164 .qe_r_ref
165 .scim_entry_search(
166 client_auth_info.clone(),
167 kopid.eventid,
168 filter,
169 ScimEntryGetQuery {
170 attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
171 ext_access_check: true,
172 sort_by: Some(Attribute::Name),
173 ..Default::default()
174 },
175 )
176 .await?;
177
178 let persons: Vec<_> = base
179 .resources
180 .into_iter()
181 .filter_map(scimentry_into_personinfo)
183 .collect();
184
185 Ok(persons)
186}
187
188fn scimentry_into_personinfo(
189 scim_entry: ScimEntryKanidm,
190) -> Option<(ScimPerson, ScimEffectiveAccess)> {
191 let scim_effective_access = scim_entry.ext_access_check.clone()?; let person = ScimPerson::try_from(scim_entry).ok()?;
193
194 Some((person, scim_effective_access))
195}