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    // TODO: inefficient to sort here
174    let mut persons: Vec<_> = base
175        .into_iter()
176        // TODO: Filtering away unsuccessful entries may not be desired.
177        .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()?; // TODO: This should be an error msg.
190    let person = ScimPerson::try_from(scim_entry).ok()?;
191
192    Some((person, scim_effective_access))
193}