[PR 1] Add experimental Custom Roles foundation (#7847)

This commit is contained in:
Tom authored and GitHub committed 2026-11-04 22:11:52 +01:00
1 parent 82fc47e83e
commit 904b80115d
20 files changed
+621 -68

No files matched your search

+32 -6
View File
@@ -1,4 +1,4 @@
use std::{env, sync::LazyLock};
use std::{collections::HashMap, env, sync::LazyLock};
use reqwest::Method;
use rocket::{
@@ -26,6 +26,7 @@ use crate::{
models::{
Attachment, Cipher, Collection, Device, Event, EventType, Group, Invitation, Membership, MembershipId,
MembershipType, OrgPolicy, Organization, OrganizationId, SsoUser, TwoFactor, User, UserId,
custom_roles_enabled,
},
},
error::{Error, MapResult},
@@ -256,6 +257,7 @@ struct AdminTemplateData {
logged_in: bool,
urlpath: String,
sso_enabled: bool,
custom_roles_enabled: bool,
}
impl AdminTemplateData {
@@ -266,6 +268,7 @@ impl AdminTemplateData {
logged_in: true,
urlpath: CONFIG.domain_path(),
sso_enabled: CONFIG.sso_enabled(),
custom_roles_enabled: custom_roles_enabled(),
}
}
@@ -390,6 +393,18 @@ async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<Stri
usr["sso_identifier"] = json!(sso_u.map_or(String::new(), |u| u.identifier.to_string()));
// The clients are shown a Manager as a Custom member, but the admin panel tells the two roles apart
let roles: HashMap<String, i32> = Membership::find_confirmed_by_user(&u.uuid, &conn)
.await
.into_iter()
.map(|member| (member.org_uuid.to_string(), member.atype))
.collect();
for org in usr["organizations"].as_array_mut().into_iter().flatten() {
if let Some(atype) = org["id"].as_str().and_then(|org_id| roles.get(org_id)) {
org["type"] = json!(atype);
}
}
users_json.push(usr);
}
@@ -565,10 +580,14 @@ async fn update_membership_type(data: Json<MembershipTypeData>, token: AdminToke
err!("The specified user isn't member of the organization")
};
let new_type = if let Some(new_type) = MembershipType::from_str(&data.user_type.into_string()) {
new_type as i32
} else {
err!("Invalid type")
let new_type = match MembershipType::from_str(&data.user_type.into_string()) {
// Unlike the clients, the admin panel tells the Custom and the legacy Manager role apart. Unless Custom roles
// are enabled, a Custom role is a Manager here as well, but an existing Custom member is kept as one.
Some(MembershipType::Custom) if !custom_roles_enabled() && !member_to_edit.has_type(MembershipType::Custom) => {
MembershipType::Manager
}
Some(new_type) => new_type,
None => err!("Invalid type"),
};
if member_to_edit.atype == MembershipType::Owner && new_type != MembershipType::Owner {
@@ -578,7 +597,14 @@ async fn update_membership_type(data: Json<MembershipTypeData>, token: AdminToke
}
}
member_to_edit.atype = new_type;
// A Manager only becomes a Custom member if that changes nothing they may do
if new_type == MembershipType::Custom
&& let Some(loss) = member_to_edit.custom_role_conversion_loss(&conn).await
{
err!(loss)
}
member_to_edit.set_type(new_type);
// This check is also done at api::organizations::{accept_invite, _confirm_invite, _activate_member, edit_member}, update_membership_type
OrgPolicy::check_user_allowed(&member_to_edit, "modify", &conn).await?;