From 666a09b86b165f36451fb850548f1b87978662f6 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 23 Oct 2025 12:52:17 -0300 Subject: [PATCH 1/3] Fix missing membership from SAML mapped groups --- ...023121055_fix-missing-group-memberships.ts | 45 +++++++++++++++++++ .../saml-config/saml-config-service.ts | 10 ++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts diff --git a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts new file mode 100644 index 000000000..e4af23c46 --- /dev/null +++ b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts @@ -0,0 +1,45 @@ +import { Knex } from "knex"; + +import { AccessScope, TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasGroupsTable = await knex.schema.hasTable(TableName.Groups); + const hasMembershipTable = await knex.schema.hasTable(TableName.Membership); + + if (!hasGroupsTable || !hasMembershipTable) { + return; + } + + const groupsWithoutMembership = await knex + .select(`${TableName.Groups}.id`, `${TableName.Groups}.orgId`) + .from(TableName.Groups) + .leftJoin(TableName.Membership, function joinGroupMembership() { + this.on(`${TableName.Groups}.id`, "=", `${TableName.Membership}.actorGroupId`); + }) + .whereNull(`${TableName.Membership}.actorGroupId`); + + if (groupsWithoutMembership.length > 0) { + const membershipInserts = groupsWithoutMembership.map((group) => ({ + actorGroupId: group.id, + scope: AccessScope.Organization, + scopeOrgId: group.orgId, + isActive: true + })); + + await knex(TableName.Membership).insert(membershipInserts); + } + + await knex.schema.alterTable(TableName.Membership, (t) => { + t.check( + `("actorUserId" IS NOT NULL OR "actorIdentityId" IS NOT NULL OR "actorGroupId" IS NOT NULL)`, + undefined, + "at_least_one_actor" + ); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Membership, (t) => { + t.dropChecks("at_least_one_actor"); + }); +} diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 13b862343..f9c97fc6f 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -84,7 +84,7 @@ type TSamlConfigServiceFactoryDep = { projectDAL: Pick; projectBotDAL: Pick; projectKeyDAL: Pick; - membershipGroupDAL: Pick; + membershipGroupDAL: Pick; }; export const samlConfigServiceFactory = ({ @@ -183,6 +183,14 @@ export const samlConfigServiceFactory = ({ transaction ); orgGroupsMap.set(groupName, newGroup); + await membershipGroupDAL.create( + { + actorGroupId: newGroup.id, + scope: AccessScope.Organization, + scopeOrgId: orgId + }, + transaction + ); } } From 5e316d9d6ec19c8265fc59ea70f7cb23df994a1b Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 23 Oct 2025 15:38:37 -0300 Subject: [PATCH 2/3] Add missing membership role to SAML mapped group --- ...023121055_fix-missing-group-memberships.ts | 19 +++++++++++++------ .../saml-config/saml-config-service.ts | 10 +++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts index e4af23c46..117ac12e9 100644 --- a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts +++ b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts @@ -1,21 +1,20 @@ import { Knex } from "knex"; -import { AccessScope, TableName } from "../schemas"; +import { AccessScope, OrgMembershipRole, TableName } from "../schemas"; export async function up(knex: Knex): Promise { const hasGroupsTable = await knex.schema.hasTable(TableName.Groups); const hasMembershipTable = await knex.schema.hasTable(TableName.Membership); + const hasMembershipRoleTable = await knex.schema.hasTable(TableName.MembershipRole); - if (!hasGroupsTable || !hasMembershipTable) { + if (!hasGroupsTable || !hasMembershipTable || !hasMembershipRoleTable) { return; } const groupsWithoutMembership = await knex .select(`${TableName.Groups}.id`, `${TableName.Groups}.orgId`) .from(TableName.Groups) - .leftJoin(TableName.Membership, function joinGroupMembership() { - this.on(`${TableName.Groups}.id`, "=", `${TableName.Membership}.actorGroupId`); - }) + .leftJoin(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) .whereNull(`${TableName.Membership}.actorGroupId`); if (groupsWithoutMembership.length > 0) { @@ -26,7 +25,15 @@ export async function up(knex: Knex): Promise { isActive: true })); - await knex(TableName.Membership).insert(membershipInserts); + const insertedMemberships = await knex(TableName.Membership).insert(membershipInserts).returning("*"); + + const membershipRoleInserts = insertedMemberships.map((membership) => ({ + membershipId: membership.id, + role: OrgMembershipRole.NoAccess, + customRoleId: null + })); + + await knex(TableName.MembershipRole).insert(membershipRoleInserts); } await knex.schema.alterTable(TableName.Membership, (t) => { diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index f9c97fc6f..c99ae8b28 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -183,7 +183,7 @@ export const samlConfigServiceFactory = ({ transaction ); orgGroupsMap.set(groupName, newGroup); - await membershipGroupDAL.create( + const orgMembership = await membershipGroupDAL.create( { actorGroupId: newGroup.id, scope: AccessScope.Organization, @@ -191,6 +191,14 @@ export const samlConfigServiceFactory = ({ }, transaction ); + await membershipRoleDAL.create( + { + membershipId: orgMembership.id, + role: OrgMembershipRole.NoAccess, + customRoleId: null + }, + transaction + ); } } From 3196e24c275ae0e347ef86ba5fd6e622b844315e Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 23 Oct 2025 15:55:15 -0300 Subject: [PATCH 3/3] Small improvement on custom roles for groups memerships migration --- ...023121055_fix-missing-group-memberships.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts index 117ac12e9..56fe82b2c 100644 --- a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts +++ b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts @@ -1,6 +1,6 @@ import { Knex } from "knex"; -import { AccessScope, OrgMembershipRole, TableName } from "../schemas"; +import { AccessScope, TableName } from "../schemas"; export async function up(knex: Knex): Promise { const hasGroupsTable = await knex.schema.hasTable(TableName.Groups); @@ -12,7 +12,12 @@ export async function up(knex: Knex): Promise { } const groupsWithoutMembership = await knex - .select(`${TableName.Groups}.id`, `${TableName.Groups}.orgId`) + .select( + `${TableName.Groups}.id`, + `${TableName.Groups}.orgId`, + `${TableName.Groups}.role`, + `${TableName.Groups}.roleId` + ) .from(TableName.Groups) .leftJoin(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) .whereNull(`${TableName.Membership}.actorGroupId`); @@ -27,11 +32,14 @@ export async function up(knex: Knex): Promise { const insertedMemberships = await knex(TableName.Membership).insert(membershipInserts).returning("*"); - const membershipRoleInserts = insertedMemberships.map((membership) => ({ - membershipId: membership.id, - role: OrgMembershipRole.NoAccess, - customRoleId: null - })); + const membershipRoleInserts = insertedMemberships.map((membership, index) => { + const group = groupsWithoutMembership[index]; + return { + membershipId: membership.id, + role: group.role, + customRoleId: group.roleId + }; + }); await knex(TableName.MembershipRole).insert(membershipRoleInserts); }