Skip to content

Commit

Permalink
Insert user acl when creating user realms
Browse files Browse the repository at this point in the history
The current user's user role needs to be included
in the acl of a user realm as an immutable entry,
meaning that it cannot be removed.
  • Loading branch information
owi92 committed Feb 4, 2024
1 parent bf6058b commit 90a140b
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 7 deletions.
11 changes: 10 additions & 1 deletion backend/src/api/model/realm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub(crate) struct Realm {
admin_roles: Vec<String>,
flattened_moderator_roles: Vec<String>,
flattened_admin_roles: Vec<String>,
owner_role: Option<String>,
}

impl_from_db!(
Expand All @@ -114,6 +115,7 @@ impl_from_db!(
id, parent, name, name_from_block, path_segment, full_path, index,
child_order, resolved_name, owner_display_name, moderator_roles,
admin_roles, flattened_moderator_roles, flattened_admin_roles,
owner_role,
},
},
|row| {
Expand All @@ -132,6 +134,7 @@ impl_from_db!(
admin_roles: row.admin_roles(),
flattened_moderator_roles: row.flattened_moderator_roles(),
flattened_admin_roles: row.flattened_admin_roles(),
owner_role: row.owner_role(),
}
}
);
Expand All @@ -158,6 +161,7 @@ impl Realm {
admin_roles: mapping.admin_roles.of(&row),
flattened_moderator_roles: Vec::new(),
flattened_admin_roles: Vec::new(),
owner_role: None,
})
}

Expand Down Expand Up @@ -320,13 +324,18 @@ impl Realm {
if self.key.0 == 0 { "/" } else { &self.full_path }
}

/// This is only returns a value for root user realms, in which case it is
/// This only returns a value for root user realms, in which case it is
/// the display name of the user who owns this realm. For all other realms,
/// `null` is returned.
fn owner_display_name(&self) -> Option<&str> {
self.owner_display_name.as_deref()
}

// Same as `owner_display_name`, but with the owner's `user_role` instead.
fn owner_role(&self) -> Option<&str> {
self.owner_role.as_deref()
}

async fn own_acl(&self, context: &Context) -> ApiResult<Acl> {
let raw_roles_sql = "
select unnest(moderator_roles) as role, 'moderate' as action from realms where id = $1
Expand Down
10 changes: 7 additions & 3 deletions backend/src/api/model/realm/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ impl Realm {
}
let AuthContext::User(user) = &context.auth else { unreachable!() };
let db = &context.db;
let user_role: Vec<&str> = vec![&user.user_role];

let res = db.query_one(
"insert into realms (parent, name, path_segment, owner_display_name) \
values (null, $1, $2, $1) \
"insert into realms ( \
parent, name, path_segment, owner_display_name, \
moderator_roles, admin_roles, owner_role \
) \
values (null, $1, $2, $1, $3, $3, $4) \
returning id",
&[&user.display_name, &format!("@{}", user.username)],
&[&user.display_name, &format!("@{}", user.username), &user_role, &user.user_role],
).await;

let row = map_db_err!(res, {
Expand Down
6 changes: 6 additions & 0 deletions backend/src/db/migrations/30-realm-permissions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ alter table realms
alter column flattened_moderator_roles drop default,
alter column flattened_admin_roles drop default;

-- Needed for making permission of a realm's owner immutable.
alter table realms
add column owner_role text,
add constraint owner_role_set check (
(parent is null and full_path ~ '^/@') = (owner_role is not null)
);

create function update_flattened_permissions_of_children()
returns trigger as $$
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/routes/manage/Realm/RealmPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const fragment = graphql`
id
ownAcl { role actions info { label implies large } }
inheritedAcl { role actions info { label implies large } }
ownerRole
}
`;

Expand Down Expand Up @@ -81,6 +82,7 @@ export const RealmPermissions: React.FC<Props> = ({ fragRef, data }) => {
acl={selections}
onChange={setSelections}
{...{ knownRoles, inheritedAcl }}
ownerRole={realm.ownerRole ?? ""}
possibleActions={{
all: new Set([
{ label: "moderate", includes: new Set(["moderate"]) },
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,12 @@ type Realm implements Node {
"""
path: String!
"""
This is only returns a value for root user realms, in which case it is
This only returns a value for root user realms, in which case it is
the display name of the user who owns this realm. For all other realms,
`null` is returned.
"""
ownerDisplayName: String
ownerRole: String
ownAcl: [AclItem!]!
inheritedAcl: [AclItem!]!
"Returns the immediate parent of this realm."
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/ui/Access.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export type Actions = {

type AclContext = {
userIsRequired: boolean;
ownerRole?: string;
acl: Acl;
possibleActions: Actions;
change: (f: (acl: Acl) => void) => void;
Expand All @@ -96,6 +97,7 @@ const useAclContext = () => useContext(AclContext) ?? bug("Acl context is not in
type AclSelectorProps = {
acl: Acl;
inheritedAcl?: Acl;
ownerRole?: string;
/**
* If `true`, the current user is included by default with `write` access and can't be removed.
* This is necessary for the acl-selection in the uploader.
Expand All @@ -110,6 +112,7 @@ export const AclSelector: React.FC<AclSelectorProps> = (
{
acl,
inheritedAcl = new Map(),
ownerRole,
userIsRequired = false,
onChange,
knownRoles,
Expand Down Expand Up @@ -141,6 +144,7 @@ export const AclSelector: React.FC<AclSelectorProps> = (

const context = {
userIsRequired,
ownerRole,
acl,
change,
groupDag: buildDag(knownGroups),
Expand Down Expand Up @@ -487,7 +491,7 @@ type ListEntryProps = ItemProps & {
const ListEntry: React.FC<ListEntryProps> = ({ remove, item, kind, inherited = false }) => {
const user = useUser();
const { t, i18n } = useTranslation();
const { userIsRequired, acl, groupDag, possibleActions } = useAclContext();
const { userIsRequired, acl, groupDag, possibleActions, ownerRole } = useAclContext();

const userHasAccess = (actions: ActionLabel[]) =>
actions.every(action => item.actions.has(action));
Expand Down Expand Up @@ -515,7 +519,11 @@ const ListEntry: React.FC<ListEntryProps> = ({ remove, item, kind, inherited = f
.map(role => getLabel(role, acl.get(role)?.info?.label, i18n));
const isSubset = !inherited && supersets.length > 0;
const isUser = isRealUser(user) && item.role === user.userRole;
const immutable = item.role === COMMON_ROLES.ADMIN || userIsRequired && isUser || inherited;
const isOwner = item.role === ownerRole;
const immutable = item.role === COMMON_ROLES.ADMIN
|| userIsRequired && isUser
|| inherited
|| isOwner;

let label: JSX.Element;
if (isUser) {
Expand Down

0 comments on commit 90a140b

Please sign in to comment.