Skip to content

Commit 030b539

Browse files
muntaxir4rajdip-b
andauthored
feat(api,cli,api-client,schema): Enhance permissions to allow filtering by environments through project roles (keyshade-xyz#599)
Co-authored-by: rajdip-b <[email protected]>
1 parent 9896f27 commit 030b539

File tree

19 files changed

+851
-88
lines changed

19 files changed

+851
-88
lines changed

api-collection/Workspace Role Controller/Create workspace role.bru

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ docs {
3939
- `description`: (Optional) A description about the role
4040
- `colorCode`: (Optional) A hex color code for the role
4141
- `authorities`: (Optional) An array of allowed `Authorities`. Refer prisma schema.
42-
- `projectSlugs`: (Optional) An array of project slugs to associate to this role. Associating projects to a role will apply all the authorities in the role to the project aswell.
42+
- `projectEnvironments`: (Optional) An array of record containing projectSlug and environmentSlugs array to associate to this role. Associating project with particular environments to a role will allow access to only provided environments for the project.
4343
}

api-collection/Workspace Role Controller/Update workspace role.bru

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ docs {
3636
- `description`: (Optional) A description about the role
3737
- `colorCode`: (Optional) A hex color code for the role
3838
- `authorities`: (Optional) An array of allowed `Authorities`. Refer prisma schema.
39-
- `projectIds`: (Optional) An array of project IDs to associate to this role. Associating projects to a role will apply all the authorities in the role to the project aswell.
39+
- `projectEnvironments`: (Optional) An array of record containing projectSlug and environmentSlugs array to associate to this role. Associating project with particular environments to a role will allow access to only provided environments for the project.
4040
}

apps/api/src/common/authority-checker.service.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ProjectWithSecrets } from '@/project/project.types'
1616
import { SecretWithProjectAndVersion } from '@/secret/secret.types'
1717
import { CustomLoggerService } from './logger.service'
1818
import {
19+
getCollectiveEnvironmentAuthorities,
1920
getCollectiveProjectAuthorities,
2021
getCollectiveWorkspaceAuthorities
2122
} from './collective-authorities'
@@ -221,9 +222,9 @@ export class AuthorityCheckerService {
221222
throw new NotFoundException(`Environment ${entity.slug} not found`)
222223
}
223224

224-
const permittedAuthorities = await getCollectiveProjectAuthorities(
225+
const permittedAuthorities = await getCollectiveEnvironmentAuthorities(
225226
userId,
226-
environment.project,
227+
environment,
227228
prisma
228229
)
229230

apps/api/src/common/collective-authorities.ts

+67
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EnvironmentWithProject } from '@/environment/environment.types'
12
import {
23
Authority,
34
PrismaClient,
@@ -93,3 +94,69 @@ export const getCollectiveProjectAuthorities = async (
9394

9495
return authorities
9596
}
97+
98+
/**
99+
* Given the userId and environment, this function returns the set of authorities
100+
* that are formed by accumulating a set of all the authorities across all the
101+
* roles that the user has in the workspace, adding an extra layer of filtering
102+
* by the project and the environment.
103+
* @param userId The id of the user
104+
* @param environment The environment with the project
105+
* @param prisma The prisma client
106+
* @returns
107+
*/
108+
export const getCollectiveEnvironmentAuthorities = async (
109+
userId: User['id'],
110+
environment: EnvironmentWithProject,
111+
prisma: PrismaClient
112+
): Promise<Set<Authority>> => {
113+
const authorities = new Set<Authority>()
114+
115+
const roleAssociations = await prisma.workspaceMemberRoleAssociation.findMany(
116+
{
117+
where: {
118+
workspaceMember: {
119+
userId,
120+
workspaceId: environment.project.workspaceId
121+
},
122+
role: {
123+
OR: [
124+
{
125+
projects: {
126+
some: {
127+
projectId: environment.project.id,
128+
environments: {
129+
some: {
130+
id: environment.id
131+
}
132+
}
133+
}
134+
}
135+
},
136+
// Check if the user has the WORKSPACE_ADMIN authority
137+
{
138+
authorities: {
139+
has: Authority.WORKSPACE_ADMIN
140+
}
141+
}
142+
]
143+
}
144+
},
145+
select: {
146+
role: {
147+
select: {
148+
authorities: true
149+
}
150+
}
151+
}
152+
}
153+
)
154+
155+
roleAssociations.forEach((roleAssociation) => {
156+
roleAssociation.role.authorities.forEach((authority) => {
157+
authorities.add(authority)
158+
})
159+
})
160+
161+
return authorities
162+
}

apps/api/src/environment/dto/create.environment/create.environment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator'
1+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator'
22

33
export class CreateEnvironment {
44
@IsString()

apps/api/src/event/event.e2e.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ describe('Event Controller Tests', () => {
438438
description: 'Some description',
439439
colorCode: '#000000',
440440
authorities: [],
441-
projectSlugs: [project.slug]
441+
projectEnvironments: [{ projectSlug: project.slug }]
442442
}
443443
)
444444

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- CreateTable
2+
CREATE TABLE "_EnvironmentToProjectWorkspaceRoleAssociation" (
3+
"A" TEXT NOT NULL,
4+
"B" TEXT NOT NULL,
5+
6+
CONSTRAINT "_EnvironmentToProjectWorkspaceRoleAssociation_AB_pkey" PRIMARY KEY ("A","B")
7+
);
8+
9+
-- CreateIndex
10+
CREATE INDEX "_EnvironmentToProjectWorkspaceRoleAssociation_B_index" ON "_EnvironmentToProjectWorkspaceRoleAssociation"("B");
11+
12+
-- AddForeignKey
13+
ALTER TABLE "_EnvironmentToProjectWorkspaceRoleAssociation" ADD CONSTRAINT "_EnvironmentToProjectWorkspaceRoleAssociation_A_fkey" FOREIGN KEY ("A") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
14+
15+
-- AddForeignKey
16+
ALTER TABLE "_EnvironmentToProjectWorkspaceRoleAssociation" ADD CONSTRAINT "_EnvironmentToProjectWorkspaceRoleAssociation_B_fkey" FOREIGN KEY ("B") REFERENCES "ProjectWorkspaceRoleAssociation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Please do not edit this file manually
2-
# It should be added in your version-control system (i.e. Git)
2+
# It should be added in your version-control system (e.g., Git)
33
provider = "postgresql"

apps/api/src/prisma/schema.prisma

+4
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ model Environment {
253253
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
254254
projectId String
255255
256+
projectWorkspaceRoleAssociations ProjectWorkspaceRoleAssociation[]
257+
256258
@@unique([projectId, name])
257259
@@index([name])
258260
}
@@ -296,6 +298,8 @@ model ProjectWorkspaceRoleAssociation {
296298
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
297299
projectId String
298300
301+
environments Environment[]
302+
299303
@@unique([roleId, projectId])
300304
}
301305

apps/api/src/workspace-role/dto/create-workspace-role/create-workspace-role.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'reflect-metadata'
12
import { CreateWorkspaceRole } from './create-workspace-role'
23

34
describe('CreateWorkspaceRole', () => {

apps/api/src/workspace-role/dto/create-workspace-role/create-workspace-role.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
import { Authority } from '@prisma/client'
2-
import { IsArray, IsOptional, IsString } from 'class-validator'
2+
import {
3+
IsArray,
4+
IsNotEmpty,
5+
IsOptional,
6+
IsString,
7+
ValidateNested
8+
} from 'class-validator'
9+
import { Type } from 'class-transformer'
10+
11+
class ProjectEnvironments {
12+
@IsString()
13+
@IsNotEmpty()
14+
readonly projectSlug: string
15+
16+
@IsArray()
17+
@IsOptional()
18+
@IsNotEmpty({ each: true })
19+
readonly environmentSlugs?: string[]
20+
}
321

422
export class CreateWorkspaceRole {
523
@IsString()
@@ -19,5 +37,7 @@ export class CreateWorkspaceRole {
1937

2038
@IsArray()
2139
@IsOptional()
22-
readonly projectSlugs?: string[]
40+
@ValidateNested({ each: true })
41+
@Type(() => ProjectEnvironments)
42+
readonly projectEnvironments?: ProjectEnvironments[]
2343
}

0 commit comments

Comments
 (0)