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        // TODO: Filtering away unsuccessful entries may not be desired.
177        .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()?; // TODO: This should be an error msg.
187    let person = ScimPerson::try_from(scim_entry).ok()?;
188
189    Some((person, scim_effective_access))
190}