Skip to content

Commit

Permalink
expose membership permissions via GraphQL API
Browse files Browse the repository at this point in the history
  • Loading branch information
n1ru4l committed Jan 3, 2025
1 parent 10cd6cc commit d410863
Show file tree
Hide file tree
Showing 7 changed files with 413 additions and 6 deletions.
20 changes: 14 additions & 6 deletions packages/services/api/src/modules/auth/lib/authz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,14 @@ const permissionsByLevel = {
],
} as const;

export const allPermissions = [
...permissionsByLevel.organization.map(v => v.value),
...permissionsByLevel.project.map(v => v.value),
...permissionsByLevel.target.map(v => v.value),
...permissionsByLevel.service.map(v => v.value),
...permissionsByLevel.appDeployment.map(v => v.value),
] as const;

export const PermissionsPerResourceLevelAssignmentModel = z.object({
organization: z.set(z.union(permissionsByLevel.organization)),
project: z.set(z.union(permissionsByLevel.project)),
Expand All @@ -400,7 +408,7 @@ export type PermissionsPerResourceLevelAssignment = z.TypeOf<
typeof PermissionsPerResourceLevelAssignmentModel
>;

type ResourceLevels = keyof PermissionsPerResourceLevelAssignment;
export type ResourceLevel = keyof PermissionsPerResourceLevelAssignment;

export const PermissionsModel = z.union([
...permissionsByLevel.organization,
Expand All @@ -410,11 +418,11 @@ export const PermissionsModel = z.union([
...permissionsByLevel.appDeployment,
]);

type Permissions = z.TypeOf<typeof PermissionsModel>;
export type Permission = z.TypeOf<typeof PermissionsModel>;

const permissionResourceLevelLookupMap = new Map<
z.TypeOf<typeof PermissionsModel>,
ResourceLevels
ResourceLevel
>();

for (const [key, permissions] of objectEntries(permissionsByLevel)) {
Expand All @@ -424,7 +432,7 @@ for (const [key, permissions] of objectEntries(permissionsByLevel)) {
}

/** Get the permission group for a specific permissions */
function getPermissionGroup(permission: Permissions): ResourceLevels {
export function getPermissionGroup(permission: Permission): ResourceLevel {
const group = permissionResourceLevelLookupMap.get(permission);

if (group === undefined) {
Expand All @@ -438,7 +446,7 @@ function getPermissionGroup(permission: Permissions): ResourceLevels {
* Transforms a flat permission array into an object that groups the permissions per resource level.
*/
export function permissionsToPermissionsPerResourceLevelAssignment(
permissions: Array<Permissions>,
permissions: Array<Permission>,
): PermissionsPerResourceLevelAssignment {
const assignment: PermissionsPerResourceLevelAssignment = {
organization: new Set(),
Expand All @@ -450,7 +458,7 @@ export function permissionsToPermissionsPerResourceLevelAssignment(

for (const permission of permissions) {
const group = getPermissionGroup(permission);
(assignment[group] as Set<Permissions>).add(permission);
(assignment[group] as Set<Permission>).add(permission);
}

return assignment;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
import { allPermissions, Permission } from './authz';

export type PermissionRecord = {
id: Permission;
title: string;
description: string;
dependsOn?: Permission;
readOnly?: true;
};

export type PermissionGroup = {
id: string;
title: string;
permissions: Array<PermissionRecord>;
};

export const allPermissionGroups: Array<PermissionGroup> = [
{
id: 'organization',
title: 'Organization',
permissions: [
{
id: 'organization:describe',
title: 'View organization',
description: 'Member can see the organization. Permission can not be modified.',
readOnly: true,
},
{
id: 'support:manageTickets',
title: 'Access support tickets',
description: 'Member can access, create and reply to support tickets.',
},
{
id: 'organization:modifySlug',
title: 'Update organization slug',
description: 'Member can modify the organization slug.',
},
{
id: 'auditLog:export',
title: 'Export audit log',
description: 'Member can access and export the audit log.',
},
{
id: 'organization:delete',
title: 'Delete organization',
description: 'Member can delete the Organization.',
},
],
},
{
id: 'members',
title: 'Members',
permissions: [
{
id: 'member:describe',
title: 'View members',
description: 'Member can access the organization member overview.',
},
{
id: 'member:assignRole',
title: 'Assign member role',
description: 'Member can assign roles to users.',
dependsOn: 'member:describe',
},
{
id: 'member:modifyRole',
title: 'Modify member role',
description: 'Member can modify, create and delete roles.',
dependsOn: 'member:describe',
},
{
id: 'member:removeMember',
title: 'Remove member',
description: 'Member can remove users from the organization.',
dependsOn: 'member:describe',
},
{
id: 'member:manageInvites',
title: 'Manage invites',
description: 'Member can invite users via email and modify or delete pending invites.',
dependsOn: 'member:describe',
},
],
},
{
id: 'billing',
title: 'Billing',
permissions: [
{
id: 'billing:describe',
title: 'View billing',
description: 'Member can view the billing information.',
},
{
id: 'billing:update',
title: 'Update billing',
description: 'Member can change the organization plan.',
dependsOn: 'billing:describe',
},
],
},
{
id: 'oidc',
title: 'OpenID Connect',
permissions: [
{
id: 'oidc:modify',
title: 'Manage OpenID Connect integration',
description: 'Member can connect, modify, and remove an OIDC provider to the connection.',
},
],
},
{
id: 'github',
title: 'GitHub Integration',
permissions: [
{
id: 'gitHubIntegration:modify',
title: 'Manage GitHub integration',
description:
'Member can connect, modify, and remove access for the GitHub integration and repository access.',
},
],
},
{
id: 'slack',
title: 'Slack Integration',
permissions: [
{
id: 'slackIntegration:modify',
title: 'Manage Slack integration',
description:
'Member can connect, modify, and remove access for the Slack integration and repository access.',
},
],
},
{
id: 'project',
title: 'Project',
permissions: [
{
id: 'project:create',
title: 'Create project',
description: 'Member can create new projects.',
},
{
id: 'project:describe',
title: 'View project',
description: 'Member can access the specified projects.',
},
{
id: 'project:delete',
title: 'Delete project',
description: 'Member can access the specified projects.',
dependsOn: 'project:describe',
},
{
id: 'project:modifySettings',
title: 'Modify Settings',
description: 'Member can access the specified projects.',
dependsOn: 'project:describe',
},
],
},
{
id: 'schema-linting',
title: 'Schema Linting',
permissions: [
{
id: 'schemaLinting:modifyOrganizationRules',
title: 'Manage organization level schema linting',
description: 'Member can view and modify the organization schema linting rules.',
},
{
id: 'schemaLinting:modifyProjectRules',
title: 'Manage project level schema linting',
description: 'Member can view and modify the projects schema linting rules.',
dependsOn: 'project:describe',
},
],
},
{
id: 'target',
title: 'Target',
permissions: [
{
id: 'target:create',
title: 'Create target',
description: 'Member can create new projects.',
dependsOn: 'project:describe',
},
{
id: 'target:delete',
title: 'Delete target',
description: 'Member can access the specified projects.',
dependsOn: 'project:describe',
},
{
id: 'target:modifySettings',
title: 'Modify settings',
description: 'Member can access the specified projects.',
dependsOn: 'project:describe',
},
{
id: 'alert:modify',
title: 'Modify alerts',
description: 'Can create alerts for schema versions.',
dependsOn: 'project:describe',
},
{
id: 'schemaVersion:approve',
title: 'Approve schema version (legacy)',
description: 'Can approve schema versions on projects using the legacy registry model.',
},
{
id: 'targetAccessToken:modify',
title: 'Manage registry access tokens',
description: 'Allow managing access tokens for CLI and Usage Reporting.',
dependsOn: 'project:describe',
},
{
id: 'cdnAccessToken:modify',
title: 'Manage CDN access tokens',
description: 'Allow managing access tokens for the CDN.',
dependsOn: 'project:describe',
},
],
},
{
id: 'laboratory',
title: 'Laboratory',
permissions: [
{
id: 'laboratory:describe',
title: 'View laboratory',
description: 'Member can access the laboratory, view and execute GraphQL documents.',
dependsOn: 'project:describe',
},
{
id: 'laboratory:modify',
title: 'Modify laboratory',
description:
'Member can create, delete and update collections and documents in the laboratory.',
dependsOn: 'laboratory:describe',
},
{
id: 'laboratory:modifyPreflightScript',
title: 'Modify the laboratory preflight script',
description: 'Member can update the laboratory preflight script.',
dependsOn: 'laboratory:describe',
},
],
},
{
id: 'app-deployments',
title: 'App Deployments',
permissions: [
{
id: 'appDeployment:describe',
title: 'View app deployments',
description: 'Member can view app deployments.',
dependsOn: 'project:describe',
},
],
},
{
id: 'schema-checks',
title: 'Schema Checks',
permissions: [
{
id: 'schemaCheck:approve',
title: 'Approve schema check',
description: 'Member can approve failed schema checks.',
dependsOn: 'project:describe',
},
],
},
] as const;

function assertAllRulesAreAssigned(excluded: Array<Permission>) {
const p = new Set(allPermissions);
for (const item of excluded) {
p.delete(item);
}

for (const group of allPermissionGroups) {
for (const per of group.permissions) {
p.delete(per.id);
}
}

if (p.size) {
throw new Error('The following permissions are not assigned: \n' + Array.from(p).join(`\n`));
}
}

/**
* This seems like the easiest way to make sure that all the permissions we have are
* assignable and exposed via our API.
*/
assertAllRulesAreAssigned([
/** These are CLI only actions for now. */
'schema:loadFromRegistry',
'schema:compose',
'schemaCheck:create',
'schemaVersion:publish',
'schemaVersion:deleteService',
'appDeployment:create',
'appDeployment:publish',
'appDeployment:retire',
]);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Member, User } from '../../shared/entities';
import { PermissionGroup, PermissionRecord } from './lib/organization-member-permissions';
import type { OrganizationAccessScope } from './providers/organization-access';
import type { ProjectAccessScope } from './providers/project-access';
import type { TargetAccessScope } from './providers/target-access';
Expand All @@ -10,3 +11,5 @@ export type UserConnectionMapper = readonly User[];
export type MemberConnectionMapper = readonly Member[];
export type MemberMapper = Member;
export type UserMapper = User;
export type PermissionGroupMapper = PermissionGroup;
export type PermissionMapper = PermissionRecord;
Loading

0 comments on commit d410863

Please sign in to comment.