mirror of
https://github.com/kanidm/kanidm.git
synced 2025-04-24 11:15:38 +02:00
Compare commits
3 commits
f8beef8af5
...
6e41b4663e
Author | SHA1 | Date | |
---|---|---|---|
|
6e41b4663e | ||
|
3077d86aa1 | ||
|
9b3f814b67 |
proto/src/scim_v1
server/core
|
@ -71,7 +71,7 @@ pub enum ScimSchema {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone, ToSchema)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
pub struct ScimMail {
|
pub struct ScimMail {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
@ -17,7 +17,7 @@ use axum_extra::extract::Form;
|
||||||
use axum_htmx::{HxEvent, HxPushUrl, HxResponseTrigger};
|
use axum_htmx::{HxEvent, HxPushUrl, HxResponseTrigger};
|
||||||
use futures_util::TryFutureExt;
|
use futures_util::TryFutureExt;
|
||||||
use kanidm_proto::attribute::Attribute;
|
use kanidm_proto::attribute::Attribute;
|
||||||
use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_MAIL};
|
use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_MAIL, ATTR_NAME};
|
||||||
use kanidm_proto::internal::UserAuthToken;
|
use kanidm_proto::internal::UserAuthToken;
|
||||||
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimPerson};
|
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimPerson};
|
||||||
use kanidm_proto::scim_v1::ScimMail;
|
use kanidm_proto::scim_v1::ScimMail;
|
||||||
|
@ -63,13 +63,13 @@ pub(crate) struct SaveProfileQuery {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct CommitSaveProfileQuery {
|
pub(crate) struct CommitSaveProfileQuery {
|
||||||
#[serde(rename = "account_name")]
|
#[serde(rename = "account_name")]
|
||||||
account_name: String,
|
account_name: Option<String>,
|
||||||
#[serde(rename = "display_name")]
|
#[serde(rename = "display_name")]
|
||||||
display_name: String,
|
display_name: Option<String>,
|
||||||
#[serde(rename = "emails[]")]
|
#[serde(rename = "emails[]")]
|
||||||
emails: Vec<String>,
|
emails: Vec<String>,
|
||||||
#[serde(rename = "new_primary_mail")]
|
#[serde(rename = "new_primary_mail")]
|
||||||
new_primary_mail: Option<String>
|
new_primary_mail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -88,6 +88,7 @@ struct ProfileChangesPartialView {
|
||||||
primary_mail: Option<String>,
|
primary_mail: Option<String>,
|
||||||
new_attrs: ProfileAttributes,
|
new_attrs: ProfileAttributes,
|
||||||
new_primary_mail: Option<String>,
|
new_primary_mail: Option<String>,
|
||||||
|
emails_are_same: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template, Clone)]
|
#[derive(Template, Clone)]
|
||||||
|
@ -123,7 +124,7 @@ pub(crate) async fn view_profile_get(
|
||||||
&kopid,
|
&kopid,
|
||||||
client_auth_info.clone(),
|
client_auth_info.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
|
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
|
||||||
|
|
||||||
|
@ -163,7 +164,8 @@ pub(crate) async fn view_profile_diff_start_save_post(
|
||||||
state,
|
state,
|
||||||
&kopid,
|
&kopid,
|
||||||
client_auth_info.clone(),
|
client_auth_info.clone(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let primary_index = query
|
let primary_index = query
|
||||||
.emails_indexes
|
.emails_indexes
|
||||||
|
@ -179,10 +181,13 @@ pub(crate) async fn view_profile_diff_start_save_post(
|
||||||
value: email.to_string(),
|
value: email.to_string(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let old_primary_mail = scim_person.mails.iter()
|
let old_primary_mail = scim_person
|
||||||
|
.mails
|
||||||
|
.iter()
|
||||||
.find(|sm| sm.primary)
|
.find(|sm| sm.primary)
|
||||||
.map(|sm| sm.value.clone());
|
.map(|sm| sm.value.clone());
|
||||||
|
|
||||||
|
let emails_are_same = scim_person.mails == new_mails;
|
||||||
|
|
||||||
let profile_view = ProfileChangesPartialView {
|
let profile_view = ProfileChangesPartialView {
|
||||||
menu_active_item: ProfileMenuItems::UserProfile,
|
menu_active_item: ProfileMenuItems::UserProfile,
|
||||||
|
@ -194,13 +199,15 @@ pub(crate) async fn view_profile_diff_start_save_post(
|
||||||
display_name: query.display_name,
|
display_name: query.display_name,
|
||||||
emails: new_mails,
|
emails: new_mails,
|
||||||
},
|
},
|
||||||
|
emails_are_same,
|
||||||
new_primary_mail: query.emails.get(primary_index).cloned(),
|
new_primary_mail: query.emails.get(primary_index).cloned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
HxPushUrl(Uri::from_static("/ui/profile/diff")),
|
HxPushUrl(Uri::from_static("/ui/profile/diff")),
|
||||||
profile_view,
|
profile_view,
|
||||||
).into_response())
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn view_profile_diff_confirm_save_post(
|
pub(crate) async fn view_profile_diff_confirm_save_post(
|
||||||
|
@ -216,24 +223,37 @@ pub(crate) async fn view_profile_diff_confirm_save_post(
|
||||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
dbg!(&query);
|
|
||||||
|
|
||||||
let filter = filter_all!(f_and!([f_id(uat.uuid.to_string().as_str())]));
|
let filter = filter_all!(f_and!([f_id(uat.uuid.to_string().as_str())]));
|
||||||
|
|
||||||
state
|
if let Some(account_name) = query.account_name {
|
||||||
.qe_w_ref
|
state
|
||||||
.handle_setattribute(
|
.qe_w_ref
|
||||||
client_auth_info.clone(),
|
.handle_setattribute(
|
||||||
uat.uuid.to_string(),
|
client_auth_info.clone(),
|
||||||
ATTR_DISPLAYNAME.to_string(),
|
uat.uuid.to_string(),
|
||||||
vec![query.display_name],
|
ATTR_NAME.to_string(),
|
||||||
filter.clone(),
|
vec![account_name],
|
||||||
kopid.eventid,
|
filter.clone(),
|
||||||
)
|
kopid.eventid,
|
||||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
)
|
||||||
.await?;
|
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(display_name) = query.display_name {
|
||||||
|
state
|
||||||
|
.qe_w_ref
|
||||||
|
.handle_setattribute(
|
||||||
|
client_auth_info.clone(),
|
||||||
|
uat.uuid.to_string(),
|
||||||
|
ATTR_DISPLAYNAME.to_string(),
|
||||||
|
vec![display_name],
|
||||||
|
filter.clone(),
|
||||||
|
kopid.eventid,
|
||||||
|
)
|
||||||
|
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
let mut emails = query.emails;
|
let mut emails = query.emails;
|
||||||
if let Some(primary) = query.new_primary_mail {
|
if let Some(primary) = query.new_primary_mail {
|
||||||
emails.insert(0, primary);
|
emails.insert(0, primary);
|
||||||
|
@ -286,7 +306,7 @@ pub(crate) async fn view_profile_diff_confirm_save_post(
|
||||||
VerifiedClientInformation(client_auth_info),
|
VerifiedClientInformation(client_auth_info),
|
||||||
DomainInfo(domain_info),
|
DomainInfo(domain_info),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Ok(Redirect::to(Urls::Profile.as_ref()).into_response()),
|
Ok(_) => Ok(Redirect::to(Urls::Profile.as_ref()).into_response()),
|
||||||
Err(e) => Ok(e.into_response()),
|
Err(e) => Ok(e.into_response()),
|
||||||
|
@ -351,5 +371,5 @@ pub(crate) async fn view_profile_unlock_get(
|
||||||
Urls::Profile.as_ref(),
|
Urls::Profile.as_ref(),
|
||||||
display_ctx,
|
display_ctx,
|
||||||
)
|
)
|
||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,79 +9,87 @@ Profile Difference
|
||||||
(% block settings_window %)
|
(% block settings_window %)
|
||||||
|
|
||||||
<form>
|
<form>
|
||||||
|
(% if person.name != new_attrs.account_name %)
|
||||||
<input type="hidden" name="account_name" value="(( new_attrs.account_name ))"/>
|
<input type="hidden" name="account_name" value="(( new_attrs.account_name ))"/>
|
||||||
|
(% endif %)
|
||||||
|
(% if person.displayname != new_attrs.display_name %)
|
||||||
<input type="hidden" name="display_name" value="(( new_attrs.display_name ))"/>
|
<input type="hidden" name="display_name" value="(( new_attrs.display_name ))"/>
|
||||||
<!-- <input type="hidden" name="legal_name" value=" new_attrs.legal_name "/>-->
|
(% endif %)
|
||||||
|
|
||||||
(% for email in new_attrs.emails %)
|
(% for email in new_attrs.emails %)
|
||||||
(% if !email.primary %)
|
(% if !email.primary %)
|
||||||
<input type="hidden" name="emails[]" value="(( email.value ))"/>
|
<input type="hidden" name="emails[]" value="(( email.value ))"/>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
(% endfor %)
|
(% endfor %)
|
||||||
(% if let Some(new_primary_mail) = new_primary_mail %)
|
(% if let Some(new_primary_mail) = new_primary_mail %)
|
||||||
<input type="hidden" name="new_primary_mail" value="(( new_primary_mail ))"/>
|
<input type="hidden" name="new_primary_mail" value="(( new_primary_mail ))"/>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
|
|
||||||
<table class="table table-bordered overflow-x-scroll">
|
<table class="table table-bordered overflow-x-scroll">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Attribute</th>
|
<th scope="col">Attribute</th>
|
||||||
<th scope="col">Old value</th>
|
<th scope="col">Old value</th>
|
||||||
<th scope="col">New value</th>
|
<th scope="col">New value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
(% if person.name != new_attrs.account_name %)
|
(% if person.name != new_attrs.account_name %)
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Username</th>
|
<th scope="row">Username</th>
|
||||||
<td class="text-break">(( person.name ))</td>
|
<td class="text-break">(( person.name ))</td>
|
||||||
<td class="text-break">(( new_attrs.account_name ))</td>
|
<td class="text-break">(( new_attrs.account_name ))</td>
|
||||||
</tr>
|
</tr>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
|
|
||||||
(% if person.displayname != new_attrs.display_name %)
|
(% if person.displayname != new_attrs.display_name %)
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Display name</th>
|
<th scope="row">Display name</th>
|
||||||
<td class="text-break">(( person.displayname ))</td>
|
<td class="text-break">(( person.displayname ))</td>
|
||||||
<td class="text-break">(( new_attrs.display_name ))</td>
|
<td class="text-break">(( new_attrs.display_name ))</td>
|
||||||
</tr>
|
</tr>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
<!-- TODO: List new items with +, same items with . -->
|
(% if !emails_are_same %)
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Primary Emails</th>
|
<th scope="row">Primary Emails</th>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
(( primary_mail.clone().unwrap_or("none".to_string()) ))
|
(( primary_mail.clone().unwrap_or("none".to_string()) ))
|
||||||
</td>
|
</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
(( new_primary_mail.clone().unwrap_or("none".to_string()) ))
|
(( new_primary_mail.clone().unwrap_or("none".to_string()) ))
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Secondary Emails</th>
|
<th scope="row">Secondary Emails</th>
|
||||||
<td class="text-break">
|
<td class="text-break">
|
||||||
<ul class="ps-3">
|
<ul class="ps-3">
|
||||||
(% for email in person.mails %)
|
(% for email in person.mails %)
|
||||||
(% if !email.primary %)
|
(% if !email.primary %)
|
||||||
<li class="text-nowrap">(( email.value ))</li>
|
<li class="text-nowrap">(( email.value ))</li>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
(% endfor %)
|
(% endfor %)
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-break">
|
<td class="text-break">
|
||||||
<ul class="ps-3">
|
<ul class="ps-3">
|
||||||
(% for email in new_attrs.emails %)
|
(% for email in new_attrs.emails %)
|
||||||
(% if !email.primary %)
|
(% if !email.primary %)
|
||||||
<li class="text-nowrap">(( email.value ))</li>
|
<li class="text-nowrap">(( email.value ))</li>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
(% endfor %)
|
(% endfor %)
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
(% endif %)
|
||||||
</table>
|
</table>
|
||||||
<!-- Edit button -->
|
|
||||||
<div class="pt-4" hx-target="#user_settings_container" hx-swap="outerHTML">
|
<div class="pt-4" hx-target="#user_settings_container" hx-swap="outerHTML">
|
||||||
(% if can_rw %)
|
(% if can_rw %)
|
||||||
<button class="btn btn-danger" type="button" hx-get="/ui/profile" hx-target="body" hx-swap="outerHTML">Discard Changes</button>
|
<button class="btn btn-danger" type="button" hx-get="/ui/profile" hx-target="body" hx-swap="outerHTML">Discard
|
||||||
<button class="btn btn-primary" type="button" hx-post="/ui/api/user_settings/confirm_profile" hx-target="body" hx-swap="outerHTML">Confirm Changes</button>
|
Changes
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" type="button" hx-post="/ui/api/user_settings/confirm_profile" hx-target="body"
|
||||||
|
hx-swap="outerHTML">Confirm Changes
|
||||||
|
</button>
|
||||||
(% else %)
|
(% else %)
|
||||||
<a href="/ui/profile/unlock" hx-boost="false">
|
<a href="/ui/profile/unlock" hx-boost="false">
|
||||||
<!-- TODO: at the moment, session expiring here means progress is lost. Do we just show an error screen ? We can't pass the update state through the reauth session, and we don't have profile update sessions like cred update. -->
|
<!-- TODO: at the moment, session expiring here means progress is lost. Do we just show an error screen ? We can't pass the update state through the reauth session, and we don't have profile update sessions like cred update. -->
|
||||||
|
|
Loading…
Reference in a new issue