kanidmd_core/https/views/admin/
persons.rs1use 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 askama_web::WebTemplate;
10use axum::extract::{Path, State};
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::authentication::ClientAuthInfo;
23use uuid::Uuid;
24
25pub const 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, WebTemplate)]
38#[template(path = "admin/admin_panel_template.html")]
39pub(crate) struct PersonsView {
40    navbar_ctx: NavbarCtx,
41    partial: PersonsPartialView,
42}
43
44#[derive(Template, WebTemplate)]
45#[template(path = "admin/admin_persons_partial.html")]
46struct PersonsPartialView {
47    persons: Vec<(ScimPerson, ScimEffectiveAccess)>,
48}
49
50#[derive(Template, WebTemplate)]
51#[template(path = "admin/admin_panel_template.html")]
52struct PersonView {
53    partial: PersonViewPartial,
54    navbar_ctx: NavbarCtx,
55}
56
57#[derive(Template, WebTemplate)]
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.clone()).await?;
74    let person_partial = PersonViewPartial {
75        person,
76        scim_effective_access,
77    };
78    let uat: &UserAuthToken = client_auth_info
79        .pre_validated_uat()
80        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
81    let push_url = HxPushUrl(format!("/ui/admin/person/{uuid}/view"));
82    Ok(if is_htmx {
83        (push_url, person_partial).into_response()
84    } else {
85        (
86            push_url,
87            PersonView {
88                partial: person_partial,
89                navbar_ctx: NavbarCtx::new(domain_info, &uat.ui_hints),
90            },
91        )
92            .into_response()
93    })
94}
95
96pub(crate) async fn view_persons_get(
97    State(state): State<ServerState>,
98    HxRequest(is_htmx): HxRequest,
99    Extension(kopid): Extension<KOpId>,
100    DomainInfo(domain_info): DomainInfo,
101    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
102) -> axum::response::Result<Response> {
103    let persons = get_persons_info(state, &kopid, client_auth_info.clone()).await?;
104    let persons_partial = PersonsPartialView { persons };
105    let uat: &UserAuthToken = client_auth_info
106        .pre_validated_uat()
107        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
108    let push_url = HxPushUrl("/ui/admin/persons".to_string());
109    Ok(if is_htmx {
110        (push_url, persons_partial).into_response()
111    } else {
112        (
113            push_url,
114            PersonsView {
115                navbar_ctx: NavbarCtx::new(domain_info, &uat.ui_hints),
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}