diff --git a/src/lib/db/access-store.ts b/src/lib/db/access-store.ts index c44c5859a3..5dffb31e3e 100644 --- a/src/lib/db/access-store.ts +++ b/src/lib/db/access-store.ts @@ -11,6 +11,10 @@ import { import { IPermission } from 'lib/types/model'; import { roundToNearestMinutesWithOptions } from 'date-fns/fp'; import NotFoundError from '../error/notfound-error'; +import { + ENVIRONMENT_PERMISSION_TYPE, + ROOT_PERMISSION_TYPE, +} from 'lib/util/constants'; const T = { ROLE_USER: 'role_user', @@ -126,14 +130,17 @@ export class AccessStore implements IAccessStore { // Since the editor should have access to the default project, // we map the project to the project and environment specific // permissions that are connected to the editor role. - if (row.role_id === EDITOR_ID && row.type !== 'root') { + if (row.role_id === EDITOR_ID && row.type !== ROOT_PERMISSION_TYPE) { project = 'default'; - } else if (row.type !== 'root') { + } else if (row.type !== ROOT_PERMISSION_TYPE) { project = row.project ? row.project : undefined; } const environment = - row.type === 'environment' ? row.environment : undefined; + row.type === ENVIRONMENT_PERMISSION_TYPE + ? row.environment + : undefined; + return { project, environment, @@ -170,11 +177,11 @@ export class AccessStore implements IAccessStore { role_id: number, permissions: IPermission[], ): Promise { - const rows = permissions.map((x) => { + const rows = permissions.map((permission) => { return { role_id, - permission_id: x.id, - environment: x.environment, + permission_id: permission.id, + environment: permission.environment, }; }); this.db.batchInsert(T.ROLE_PERMISSION, rows); @@ -217,7 +224,7 @@ export class AccessStore implements IAccessStore { return rows.map((r) => r.user_id); } - async addUserToRole( + async addUserToProjectRole( userId: number, roleId: number, projecId: string, @@ -229,7 +236,7 @@ export class AccessStore implements IAccessStore { }); } - async removeUserFromRole( + async removeUserFromProjectRole( userId: number, roleId: number, projectId: string, diff --git a/src/lib/db/role-store.ts b/src/lib/db/role-store.ts index 2e46372121..a93023fe4b 100644 --- a/src/lib/db/role-store.ts +++ b/src/lib/db/role-store.ts @@ -101,15 +101,6 @@ export default class RoleStore implements IRoleStore { return result.length > 0; } - async roleExists(name: string): Promise { - const result = await this.db.raw( - `SELECT EXISTS (SELECT 1 FROM ${T.ROLES} WHERE name = ?) AS present`, - [name], - ); - const { present } = result.rows[0]; - return present; - } - async deleteAll(): Promise { return this.db(T.ROLES).del(); } @@ -181,8 +172,8 @@ export default class RoleStore implements IRoleStore { .where('r.type', '=', 'root'); return rows.map((row) => ({ - roleId: +row.id, - userId: +row.user_id, + roleId: Number(row.id), + userId: Number(row.user_id), })); } diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts index 4dd7ddbde7..e865b34528 100644 --- a/src/lib/services/access-service.ts +++ b/src/lib/services/access-service.ts @@ -158,8 +158,8 @@ export class AccessService { const allEnvironmentPermissions = environments.map((env) => { return { name: env.name, - permissions: environmentPermissions.map((p) => { - return { environment: env.name, ...p }; + permissions: environmentPermissions.map((permission) => { + return { environment: env.name, ...permission }; }), }; }); @@ -170,12 +170,12 @@ export class AccessService { }; } - async addUserToRole( + async addUserToProjectRole( userId: number, roleId: number, projectId: string, ): Promise { - return this.store.addUserToRole(userId, roleId, projectId); + return this.store.addUserToProjectRole(userId, roleId, projectId); } async getRoleByName(roleName: string): Promise { @@ -194,7 +194,7 @@ export class AccessService { RoleType.ROOT, ); - await this.store.addUserToRole( + await this.store.addUserToProjectRole( userId, newRootRole.id, ALL_PROJECTS, @@ -214,12 +214,12 @@ export class AccessService { return userRoles.filter((r) => r.type === RoleType.ROOT); } - async removeUserFromRole( + async removeUserFromProjectRole( userId: number, roleId: number, projectId: string, ): Promise { - return this.store.removeUserFromRole(userId, roleId, projectId); + return this.store.removeUserFromProjectRole(userId, roleId, projectId); } async addPermissionToRole( @@ -242,9 +242,9 @@ export class AccessService { async removePermissionFromRole( roleId: number, permission: string, - projectId?: string, + environment?: string, ): Promise { - if (isProjectPermission(permission) && !projectId) { + if (isProjectPermission(permission) && !environment) { throw new Error( `ProjectId cannot be empty for permission=${permission}`, ); @@ -252,7 +252,7 @@ export class AccessService { return this.store.removePermissionFromRole( roleId, permission, - projectId, + environment, ); } @@ -349,7 +349,11 @@ export class AccessService { this.logger.info( `Making ${owner.id} admin of ${projectId} via roleId=${ownerRole.id}`, ); - await this.store.addUserToRole(owner.id, ownerRole.id, projectId); + await this.store.addUserToProjectRole( + owner.id, + ownerRole.id, + projectId, + ); } } diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index c5ccf03512..a71c644a79 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -300,7 +300,11 @@ export default class ProjectService { throw new Error(`User already has access to project=${projectId}`); } - await this.accessService.addUserToRole(userId, role.id, projectId); + await this.accessService.addUserToProjectRole( + userId, + role.id, + projectId, + ); } // TODO: should be an event too @@ -324,7 +328,11 @@ export default class ProjectService { } } - await this.accessService.removeUserFromRole(userId, role.id, projectId); + await this.accessService.removeUserFromProjectRole( + userId, + role.id, + projectId, + ); } async getMembers(projectId: string): Promise { diff --git a/src/lib/types/stores/access-store.ts b/src/lib/types/stores/access-store.ts index 7dc10f530e..aa0b891d15 100644 --- a/src/lib/types/stores/access-store.ts +++ b/src/lib/types/stores/access-store.ts @@ -41,12 +41,12 @@ export interface IAccessStore extends Store { role_id: number, permissions: IPermission[], ): Promise; - addUserToRole( + addUserToProjectRole( userId: number, roleId: number, projectId: string, ): Promise; - removeUserFromRole( + removeUserFromProjectRole( userId: number, roleId: number, projectId: string, diff --git a/src/lib/util/constants.ts b/src/lib/util/constants.ts index c46e2644b8..ea7fffc2c3 100644 --- a/src/lib/util/constants.ts +++ b/src/lib/util/constants.ts @@ -1 +1,5 @@ export const DEFAULT_ENV = 'default'; + +export const ROOT_PERMISSION_TYPE = 'root'; +export const ENVIRONMENT_PERMISSION_TYPE = 'environment'; +export const PROJECT_PERMISSION_TYPE = 'project'; diff --git a/src/test/e2e/services/access-service.e2e.test.ts b/src/test/e2e/services/access-service.e2e.test.ts index aade13bc5b..eb13bca915 100644 --- a/src/test/e2e/services/access-service.e2e.test.ts +++ b/src/test/e2e/services/access-service.e2e.test.ts @@ -28,14 +28,18 @@ let readRole; const createUserEditorAccess = async (name, email) => { const { userStore } = stores; const user = await userStore.insert({ name, email }); - await accessService.addUserToRole(user.id, editorRole.id, 'default'); + await accessService.addUserToProjectRole(user.id, editorRole.id, 'default'); return user; }; const createUserViewerAccess = async (name, email) => { const { userStore } = stores; const user = await userStore.insert({ name, email }); - await accessService.addUserToRole(user.id, readRole.id, ALL_PROJECTS); + await accessService.addUserToProjectRole( + user.id, + readRole.id, + ALL_PROJECTS, + ); return user; }; @@ -178,7 +182,11 @@ const createSuperUser = async () => { name: 'Alice Admin', email: 'admin@getunleash.io', }); - await accessService.addUserToRole(user.id, adminRole.id, ALL_PROJECTS); + await accessService.addUserToProjectRole( + user.id, + adminRole.id, + ALL_PROJECTS, + ); return user; }; @@ -372,7 +380,7 @@ test('should grant user access to project', async () => { await accessService.createDefaultProjectRoles(user, project); const projectRole = await accessService.getRoleByName(RoleName.MEMBER); - await accessService.addUserToRole(sUser.id, projectRole.id, project); + await accessService.addUserToProjectRole(sUser.id, projectRole.id, project); // // Should be able to update feature toggles inside the project hasCommonProjectAccess(sUser, project, true); @@ -397,7 +405,7 @@ test('should not get access if not specifying project', async () => { const projectRole = await accessService.getRoleByName(RoleName.MEMBER); - await accessService.addUserToRole(sUser.id, projectRole.id, project); + await accessService.addUserToProjectRole(sUser.id, projectRole.id, project); // Should not be able to update feature toggles outside project hasCommonProjectAccess(sUser, undefined, false); @@ -410,14 +418,18 @@ test('should remove user from role', async () => { email: 'random123@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id, 'default'); + await accessService.addUserToProjectRole(user.id, editorRole.id, 'default'); // check user has one role const userRoles = await accessService.getRolesForUser(user.id); expect(userRoles.length).toBe(1); expect(userRoles[0].name).toBe(RoleName.EDITOR); - await accessService.removeUserFromRole(user.id, editorRole.id, 'default'); + await accessService.removeUserFromProjectRole( + user.id, + editorRole.id, + 'default', + ); const userRolesAfterRemove = await accessService.getRolesForUser(user.id); expect(userRolesAfterRemove.length).toBe(0); }); @@ -429,7 +441,7 @@ test('should return role with users', async () => { email: 'random2223@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id, 'default'); + await accessService.addUserToProjectRole(user.id, editorRole.id, 'default'); const roleWithUsers = await accessService.getRoleData(editorRole.id); expect(roleWithUsers.role.name).toBe(RoleName.EDITOR); @@ -447,7 +459,7 @@ test('should return role with permissions and users', async () => { email: 'random2244@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id, 'default'); + await accessService.addUserToProjectRole(user.id, editorRole.id, 'default'); const roleWithPermission = await accessService.getRoleData(editorRole.id); @@ -536,7 +548,11 @@ test('should support permission with "ALL" environment requirement', async () => [CREATE_FEATURE_STRATEGY], 'production', ); - await accessStore.addUserToRole(user.id, customRole.id, ALL_PROJECTS); + await accessStore.addUserToProjectRole( + user.id, + customRole.id, + ALL_PROJECTS, + ); const hasAccess = await accessService.hasPermission( user, @@ -667,3 +683,17 @@ test('Should be denied access to delete a role that is in use', async () => { ); } }); + +test('Should be given full access to project created by user', async () => { + const user = editorUser; + const newProjectName = 'AWholeNewProject'; + + const project = { + id: newProjectName, + name: newProjectName, + description: 'Blah', + }; + await projectService.createProject(project, user.id); + + hasFullProjectAccess(user, newProjectName, true); +}); diff --git a/src/test/fixtures/access-service-mock.ts b/src/test/fixtures/access-service-mock.ts index 908adc9afa..e2b313b3fb 100644 --- a/src/test/fixtures/access-service-mock.ts +++ b/src/test/fixtures/access-service-mock.ts @@ -35,7 +35,7 @@ class AccessServiceMock extends AccessService { throw new Error('Method not implemented.'); } - addUserToRole(userId: number, roleId: number): Promise { + addUserToProjectRole(userId: number, roleId: number): Promise { throw new Error('Method not implemented.'); } @@ -43,10 +43,6 @@ class AccessServiceMock extends AccessService { return Promise.resolve(); } - removeUserFromRole(userId: number, roleId: number): Promise { - throw new Error('Method not implemented.'); - } - addPermissionToRole( roleId: number, permission: string, diff --git a/src/test/fixtures/fake-access-store.ts b/src/test/fixtures/fake-access-store.ts index 2767e082e3..8603683d9c 100644 --- a/src/test/fixtures/fake-access-store.ts +++ b/src/test/fixtures/fake-access-store.ts @@ -9,6 +9,14 @@ import { import { IAvailablePermissions, IPermission } from 'lib/types/model'; class AccessStoreMock implements IAccessStore { + removeUserFromProjectRole( + userId: number, + roleId: number, + projectId: string, + ): Promise { + throw new Error('Method not implemented.'); + } + wipePermissionsFromRole(role_id: number): Promise { throw new Error('Method not implemented.'); } @@ -79,11 +87,7 @@ class AccessStoreMock implements IAccessStore { throw new Error('Method not implemented.'); } - addUserToRole(userId: number, roleId: number): Promise { - throw new Error('Method not implemented.'); - } - - removeUserFromRole(userId: number, roleId: number): Promise { + addUserToProjectRole(userId: number, roleId: number): Promise { throw new Error('Method not implemented.'); }