From a486d482c30e28efd121c787d456d1cc487a8f32 Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Mon, 9 Feb 2026 14:56:46 +0100 Subject: [PATCH 1/2] fix(admin-roles): admin conflicting with existing admin group in Keycloack The new AdminRole implementation introduced by https://github.com/cloud-pi-native/console/pull/1893 changed the source of truth from Keycloak to Console, which overrides all existing data based on the state of Console. Signed-off-by: William Phetsinorath --- .../migration.sql | 2 +- .../20260206105522_dso/migration.sql | 28 +++++++++++++++++++ packages/test-utils/src/imports/data.ts | 10 ++++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/prisma/migrations/20260206105522_dso/migration.sql diff --git a/apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql b/apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql index adb8b6f25..92c200d26 100644 --- a/apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql +++ b/apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql @@ -1,6 +1,6 @@ -- Update existing Admin role to be system role 'Administrateur Plateforme' UPDATE "AdminRole" -SET +SET "name" = 'Administrateur Plateforme', "type" = 'system', "permissions" = 3, -- Assuming 3n means bit 0 and 1 (1 | 2 = 3) diff --git a/apps/server/src/prisma/migrations/20260206105522_dso/migration.sql b/apps/server/src/prisma/migrations/20260206105522_dso/migration.sql new file mode 100644 index 000000000..2a4afa897 --- /dev/null +++ b/apps/server/src/prisma/migrations/20260206105522_dso/migration.sql @@ -0,0 +1,28 @@ +-- Update existing Admin role to be system role 'Root Administrateur Plateforme' +UPDATE "AdminRole" +SET + "name" = 'Root Administrateur Plateforme' +WHERE "id" = '76229c96-4716-45bc-99da-00498ec9018c'::uuid; + +-- Insert 'Administrateur Plateforme' system role if it doesn't exist +INSERT INTO "AdminRole" ("id", "name", "permissions", "position", "oidcGroup", "type") +VALUES ( + '6bebe7b2-0f0a-456e-ab7f-b3d7640a7cbf'::uuid, + 'Administrateur Plateforme', + 3, -- Assuming 3n means bit 0 and 1 (1 | 2 = 3) + 0, + '/console/admin', + 'system' +) +ON CONFLICT ("id") DO UPDATE +SET + "name" = 'Administrateur Plateforme', + "type" = 'system', + "permissions" = 3, + "oidcGroup" = '/console/admin'; + +-- Update 'Lecture Seule Plateforme' system role +UPDATE "AdminRole" +SET + "oidcGroup" = '/console/readonly' +WHERE "id" = '35848aa2-e881-4770-9844-0c5c3693e506'::uuid; diff --git a/packages/test-utils/src/imports/data.ts b/packages/test-utils/src/imports/data.ts index 394b07959..b4d38d6b3 100644 --- a/packages/test-utils/src/imports/data.ts +++ b/packages/test-utils/src/imports/data.ts @@ -24,6 +24,14 @@ export const data = { permissions: '3n', position: 0, oidcGroup: '/admin', + name: 'Root Administrateur Plateforme', + type: 'system', + }, + { + id: '6bebe7b2-0f0a-456e-ab7f-b3d7640a7cbf', + permissions: '3n', + position: 0, + oidcGroup: '/console/admin', name: 'Administrateur Plateforme', type: 'system', }, @@ -39,7 +47,7 @@ export const data = { id: '35848aa2-e881-4770-9844-0c5c3693e506', permissions: '1n', position: 2, - oidcGroup: '/readonly', + oidcGroup: '/console/readonly', name: 'Lecture Seule Plateforme', type: 'system', }, From 96073a28ca0f5b979a888ff28164277de6500e10 Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Mon, 9 Feb 2026 14:56:54 +0100 Subject: [PATCH 2/2] fix(project-roles): remove /console prefix strip Signed-off-by: William Phetsinorath --- apps/server/src/resources/project-role/business.spec.ts | 6 +++--- apps/server/src/resources/project-role/business.ts | 6 +++--- apps/server/src/resources/project/business.ts | 8 ++++---- playwright/e2e-tests/system-roles.spec.ts | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/server/src/resources/project-role/business.spec.ts b/apps/server/src/resources/project-role/business.spec.ts index f1719ef06..da877b925 100644 --- a/apps/server/src/resources/project-role/business.spec.ts +++ b/apps/server/src/resources/project-role/business.spec.ts @@ -78,7 +78,7 @@ describe('test project-role business', () => { prisma.projectRole.findMany.mockResolvedValueOnce([dbRole]) const response = await listRoles(projectId) - expect(response[0].oidcGroup).toBe('/admin') + expect(response[0].oidcGroup).toBe('/console/admin') }) }) @@ -162,7 +162,7 @@ describe('test project-role business', () => { prisma.projectRole.create.mockResolvedValue(dbRole) prisma.projectRole.findMany.mockResolvedValueOnce([dbRole]) - await createRole(projectId, { name: 'test', permissions: '4', oidcGroup: '/admin' }) + await createRole(projectId, { name: 'test', permissions: '4', oidcGroup: '/console/admin' }) expect(prisma.projectRole.create).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ @@ -378,7 +378,7 @@ describe('test project-role business', () => { it('should update role with enforced oidcGroup prefix', async () => { const updateRoles: any[] = [ - { id: dbRoles[1].id, oidcGroup: '/admin' }, + { id: dbRoles[1].id, oidcGroup: '/console/admin' }, ] prisma.project.findUnique.mockResolvedValue(project) diff --git a/apps/server/src/resources/project-role/business.ts b/apps/server/src/resources/project-role/business.ts index 13e413bd4..fb4021194 100644 --- a/apps/server/src/resources/project-role/business.ts +++ b/apps/server/src/resources/project-role/business.ts @@ -15,7 +15,7 @@ export async function listRoles(projectId: Project['id']) { return roles.map(role => ({ ...role, permissions: role.permissions.toString(), - oidcGroup: role.oidcGroup ? role.oidcGroup.replace(/^\/[^/]+\/console/, '') : role.oidcGroup, + oidcGroup: role.oidcGroup ? role.oidcGroup.replace(/^\/[^/]+/, '') : role.oidcGroup, })) } @@ -45,7 +45,7 @@ export async function patchRoles(projectId: Project['id'], roles: typeof project name: matchingRole.name ?? dbRole.name, permissions: matchingRole.permissions ? BigInt(matchingRole.permissions) : dbRole.permissions, position: matchingRole.position ?? dbRole.position, - oidcGroup: matchingRole.oidcGroup ? `/${project.slug}/console${matchingRole.oidcGroup}` : dbRole.oidcGroup, + oidcGroup: matchingRole.oidcGroup ? `/${project.slug}${matchingRole.oidcGroup}` : dbRole.oidcGroup, type: matchingRole.type ?? dbRole.type, projectId: dbRole.projectId, }) @@ -84,7 +84,7 @@ export async function createRole(projectId: Project['id'], role: typeof projectR projectId, position: dbMaxPosRole + 1, permissions: BigInt(role.permissions), - oidcGroup: role.oidcGroup ? `/${project.slug}/console${role.oidcGroup}` : undefined, + oidcGroup: role.oidcGroup ? `/${project.slug}${role.oidcGroup}` : undefined, }, }) diff --git a/apps/server/src/resources/project/business.ts b/apps/server/src/resources/project/business.ts index 9f76e983f..58cee33f1 100644 --- a/apps/server/src/resources/project/business.ts +++ b/apps/server/src/resources/project/business.ts @@ -46,7 +46,7 @@ export async function listProjects({ status, statusIn, statusNotIn, filter = 'me }).then(projects => projects.map(({ clusters, ...project }) => ({ ...project, clusterIds: clusters.map(({ id }) => id), - roles: project.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: project.slug ? role.oidcGroup?.replace(`/${project.slug}/console`, '') : role.oidcGroup })), + roles: project.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: project.slug ? role.oidcGroup?.replace(`/${project.slug}`, '') : role.oidcGroup })), everyonePerms: project.everyonePerms.toString(), }))) } @@ -91,7 +91,7 @@ export async function createProject(dataDto: typeof projectContract.createProjec ...projectInfos, clusterIds: projectInfos.clusters.map(({ id }) => id), everyonePerms: projectInfos.everyonePerms.toString(), - roles: projectInfos.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: projectInfos.slug ? role.oidcGroup?.replace(`/${project.slug}/console`, '') : role.oidcGroup })), + roles: projectInfos.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: projectInfos.slug ? role.oidcGroup?.replace(`/${project.slug}`, '') : role.oidcGroup })), } } @@ -99,7 +99,7 @@ export async function getProject(projectId: Project['id']) { return getProjectOrThrow(projectId).then(({ clusters, ...project }) => ({ ...project, clusterIds: clusters.map(({ id }) => id), - roles: project.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: project.slug ? role.oidcGroup?.replace(`/${project.slug}/console`, '') : role.oidcGroup })), + roles: project.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: project.slug ? role.oidcGroup?.replace(`/${project.slug}`, '') : role.oidcGroup })), everyonePerms: project.everyonePerms.toString(), })) } @@ -156,7 +156,7 @@ export async function updateProject( ...projectInfos, clusterIds: projectInfos.clusters.map(({ id }) => id), everyonePerms: projectInfos.everyonePerms.toString(), - roles: projectInfos.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: projectInfos.slug ? role.oidcGroup?.replace(`/${projectInfos.slug}/console`, '') : role.oidcGroup })), + roles: projectInfos.roles.map(role => ({ ...role, permissions: role.permissions.toString(), oidcGroup: projectInfos.slug ? role.oidcGroup?.replace(`/${projectInfos.slug}`, '') : role.oidcGroup })), } } diff --git a/playwright/e2e-tests/system-roles.spec.ts b/playwright/e2e-tests/system-roles.spec.ts index 2c5947f22..f8d1647c4 100644 --- a/playwright/e2e-tests/system-roles.spec.ts +++ b/playwright/e2e-tests/system-roles.spec.ts @@ -20,10 +20,10 @@ test.describe('System Roles at Project Creation', () => { // Assert const systemRoles = [ - { name: 'Administrateur', oidcGroup: '/admin' }, - { name: 'DevOps', oidcGroup: '/devops' }, - { name: 'Développeur', oidcGroup: '/developer' }, - { name: 'Lecture seule', oidcGroup: '/readonly' }, + { name: 'Administrateur', oidcGroup: '/console/admin' }, + { name: 'DevOps', oidcGroup: '/console/devops' }, + { name: 'Développeur', oidcGroup: '/console/developer' }, + { name: 'Lecture seule', oidcGroup: '/console/readonly' }, ] for (const role of systemRoles) {