in controlplane/src/core/bufservices/organization/deleteOrganizationGroup.ts [18:210]
export function deleteOrganizationGroup(
opts: RouterOptions,
req: DeleteOrganizationGroupRequest,
ctx: HandlerContext,
): Promise<PlainMessage<DeleteOrganizationGroupResponse>> {
let logger = getLogger(ctx, opts.logger);
return handleError<PlainMessage<DeleteOrganizationGroupResponse>>(ctx, logger, async () => {
const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
logger = enrichLogger(ctx, logger, authContext);
return opts.db.transaction(async (tx) => {
const orgRepo = new OrganizationRepository(logger, tx);
const orgGroupRepo = new OrganizationGroupRepository(tx);
const auditLogRepo = new AuditLogRepository(tx);
const oidcRepo = new OidcRepository(tx);
if (authContext.organizationDeactivated || !authContext.rbac.isOrganizationAdmin) {
throw new UnauthorizedError();
}
const rbac = await orgRepo.getFeature({ organizationId: authContext.organizationId, featureId: 'rbac' });
if (!rbac?.enabled) {
return {
response: {
code: EnumStatusCode.ERR,
details: `RBAC feature is not enabled for this organization.`,
},
};
}
const orgGroup = await orgGroupRepo.byId({
organizationId: authContext.organizationId,
groupId: req.groupId,
});
if (!orgGroup) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: 'Group not found',
},
};
}
if (orgGroup.builtin) {
return {
response: {
code: EnumStatusCode.ERR,
details: 'Builtin groups cannot be deleted.',
},
};
}
await opts.keycloakClient.authenticateClient();
// Retrieve the OIDC mappers that have been assigned to the group
const oidc = await oidcRepo.getOidcProvider({ organizationId: authContext.organizationId });
let oidcMappersForGroup: { id: string; claims: string }[] = [];
if (oidc) {
const oidcProvider = new OidcProvider();
const oidcMappers = await oidcProvider.fetchIDPMappers({
kcClient: opts.keycloakClient,
kcRealm: opts.keycloakRealm,
alias: oidc.alias,
organizationId: authContext.organizationId,
db: opts.db,
});
oidcMappersForGroup = oidcMappers.filter((mapper) => mapper.groupId === orgGroup.groupId);
}
// If the group have one member or the organization OIDC is enabled, we need to move the members to the
// destination group and update the OIDC mappers
let moveToGroup: OrganizationGroupDTO | undefined;
if (orgGroup.membersCount > 0 || orgGroup.apiKeysCount > 0 || oidcMappersForGroup.length > 0) {
if (req.toGroupId) {
moveToGroup = await orgGroupRepo.byId({ organizationId: authContext.organizationId, groupId: req.toGroupId });
}
if (!moveToGroup) {
return {
response: {
code: EnumStatusCode.ERR,
details: 'No group to move existing members and mappers to was provided',
},
};
}
// Change the Keycloak group of all the members belonging to this group
// We don't need to delete group for the user since it will be automatically deleted when the
// group is deleted
const [usersOfGroup] = await orgGroupRepo.getGroupMembers(orgGroup.groupId);
if (usersOfGroup.length > 0 && moveToGroup.kcGroupId) {
for (const user of usersOfGroup) {
const kcUser = await opts.keycloakClient.client.users.find({
realm: opts.keycloakRealm,
email: user.email,
exact: true,
});
if (kcUser.length === 0) {
continue;
}
await opts.keycloakClient.client.users.addToGroup({
realm: opts.keycloakRealm,
id: kcUser[0].id!,
groupId: moveToGroup.kcGroupId,
});
}
}
// Change all the group members and API keys to the target group
await orgGroupRepo.changeMemberGroup({
fromGroupId: orgGroup.groupId,
toGroupId: moveToGroup.groupId,
});
await auditLogRepo.addAuditLog({
organizationId: authContext.organizationId,
organizationSlug: authContext.organizationSlug,
auditAction: 'group.members_moved',
action: 'updated',
actorId: authContext.userId,
auditableDisplayName: moveToGroup.name,
auditableType: 'group',
actorDisplayName: authContext.userDisplayName,
targetType: 'group',
targetDisplayName: orgGroup.name,
apiKeyName: authContext.apiKeyName,
actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
});
}
// When the organization have linked an OIDC provider, we need to update the mappers that were tied
// to the group we are deleting
if (oidc && oidcMappersForGroup.length > 0 && moveToGroup) {
for (const mapper of oidcMappersForGroup) {
// To update the mapper, we need to delete the existing mapper and create a new one with the same claims.
//
// NOTES:
// I tried using the `updateMapper` Keycloak method, however, it throw an exception with "internal error"
// every time, the Keycloak log said tried to update a mapper that didn't exist, even when using the
// parameters as returned by Keycloak
await opts.keycloakClient.client.identityProviders.delMapper({
realm: opts.keycloakRealm,
alias: oidc.alias,
id: mapper.id,
});
await opts.keycloakClient.createIDPMapper({
realm: opts.keycloakRealm,
alias: oidc.alias,
keycloakGroupName: `/${authContext.organizationSlug}/${moveToGroup.name}`,
claims: mapper.claims,
});
}
}
// Delete the group from Keycloak and the database
if (orgGroup.kcGroupId) {
await opts.keycloakClient.client.groups.del({
realm: opts.keycloakRealm,
id: orgGroup.kcGroupId,
});
}
await orgGroupRepo.deleteById(orgGroup.groupId);
// Finally, create a log entry for the deleted group
await auditLogRepo.addAuditLog({
organizationId: authContext.organizationId,
organizationSlug: authContext.organizationSlug,
auditAction: 'group.deleted',
action: 'deleted',
actorId: authContext.userId,
auditableDisplayName: orgGroup.name,
auditableType: 'group',
actorDisplayName: authContext.userDisplayName,
apiKeyName: authContext.apiKeyName,
actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
});
return {
response: {
code: EnumStatusCode.OK,
},
};
});
});
}