Whitelist ownership grants via allow policies
Replace the deny-based restriction on granting OWNER with role-scoped allow policies so authorization fails closed: admins may create and update memberships only when the assigned role is not OWNER, and the absence of a target role no longer implies permission. To keep console UI gating accurate without loosening the base grants, the permission field gains an optional typed options argument (PermissionOptionsInput) that forwards target_role into the dry-run authorization. Only the two role-related console calls (create user, update membership) pass it; the OWNER option stays hidden for admins via the existing assignable-roles helper. Add a non-regression test that an admin cannot promote a member to OWNER while still being able to change members between non-owner roles.
This commit is contained in:
@@ -29,7 +29,7 @@ export const peoplePageQuery = graphql`
|
||||
organization: node(id: $organizationId) @required(action: THROW) {
|
||||
__typename
|
||||
... on Organization {
|
||||
canCreateUser: permission(action: "iam:membership-profile:create")
|
||||
canCreateUser: permission(action: "iam:membership-profile:create", attributes: { target_role: "VIEWER" })
|
||||
...PeopleListFragment
|
||||
@arguments(first: 20, order: { direction: ASC, field: FULL_NAME })
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ const fragment = graphql`
|
||||
membership @required(action: THROW) {
|
||||
id
|
||||
role
|
||||
canUpdate: permission(action: "iam:membership:update")
|
||||
canUpdate: permission(action: "iam:membership:update", attributes: { target_role: "VIEWER" })
|
||||
}
|
||||
lastInvitation: pendingInvitations(first: 1, orderBy: { field: CREATED_AT, direction: DESC })
|
||||
@required(action: THROW)
|
||||
@@ -134,6 +134,10 @@ export function PeopleListItem(props: {
|
||||
const profile = useFragment<PeopleListItemFragment$key>(fragment, fKey);
|
||||
const lastInvitation = profile.lastInvitation.edges[0]?.node;
|
||||
|
||||
const roleOptions = availableRoles.includes(profile.membership.role)
|
||||
? availableRoles
|
||||
: [...availableRoles, profile.membership.role];
|
||||
|
||||
const isInactive = profile.state === "INACTIVE";
|
||||
|
||||
const canSendActivationMail = isInactive && profile.source !== "SCIM" && profile.canInvite;
|
||||
@@ -286,19 +290,19 @@ export function PeopleListItem(props: {
|
||||
value={profile.membership.role}
|
||||
onValueChange={role => void handleUpdateRole(role)}
|
||||
>
|
||||
{availableRoles.includes("OWNER") && (
|
||||
{roleOptions.includes("OWNER") && (
|
||||
<Option value="OWNER">{__("Owner")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("ADMIN") && (
|
||||
{roleOptions.includes("ADMIN") && (
|
||||
<Option value="ADMIN">{__("Admin")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("VIEWER") && (
|
||||
{roleOptions.includes("VIEWER") && (
|
||||
<Option value="VIEWER">{__("Viewer")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("AUDITOR") && (
|
||||
{roleOptions.includes("AUDITOR") && (
|
||||
<Option value="AUDITOR">{__("Auditor")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("EMPLOYEE") && (
|
||||
{roleOptions.includes("EMPLOYEE") && (
|
||||
<Option value="EMPLOYEE">{__("Employee")}</Option>
|
||||
)}
|
||||
</Select>
|
||||
|
||||
Reference in New Issue
Block a user