Add SCIM user activation/deactivation

This commit is contained in:
Tuan Dang
2024-07-16 20:19:27 +07:00
parent c90ecd336c
commit a1d00f2c41
17 changed files with 182 additions and 55 deletions
@@ -162,11 +162,26 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
}
};
const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Groups}.orgId`, orgId);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "findTest" });
}
};
return {
...userGroupMembershipOrm,
filterProjectsByUserMembership,
findUserGroupMembershipsInProject,
findGroupMembersNotInProject,
deletePendingUserGroupMembershipsByUserIds
deletePendingUserGroupMembershipsByUserIds,
findUserGroupMembershipsInOrg
};
};
@@ -449,7 +449,8 @@ export const ldapConfigServiceFactory = ({
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
@@ -534,7 +535,8 @@ export const ldapConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -193,7 +193,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -266,7 +267,8 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -370,7 +370,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -457,7 +458,8 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
+12 -1
View File
@@ -32,12 +32,19 @@ export const parseScimFilter = (filterToParse: string | undefined) => {
return { [attributeName]: parsedValue.replace(/"/g, "") };
};
export function extractScimValueFromPath(path: string): string | null {
const regex = /members\[value eq "([^"]+)"\]/;
const match = path.match(regex);
return match ? match[1] : null;
}
export const buildScimUser = ({
orgMembershipId,
username,
email,
firstName,
lastName,
groups = [],
active
}: {
orgMembershipId: string;
@@ -45,6 +52,10 @@ export const buildScimUser = ({
email?: string | null;
firstName: string;
lastName: string;
groups?: {
value: string;
display: string;
}[];
active: boolean;
}): TScimUser => {
const scimUser = {
@@ -67,7 +78,7 @@ export const buildScimUser = ({
]
: [],
active,
groups: [],
groups,
meta: {
resourceType: "User",
location: null
+53 -30
View File
@@ -30,7 +30,14 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import {
buildScimGroup,
buildScimGroupList,
buildScimUser,
buildScimUserList,
extractScimValueFromPath,
parseScimFilter
} from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
@@ -61,7 +68,7 @@ type TScimServiceFactoryDep = {
TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
@@ -71,7 +78,12 @@ type TScimServiceFactoryDep = {
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete"
| "find"
| "transaction"
| "insertMany"
| "filterProjectsByUserMembership"
| "delete"
| "findUserGroupMembershipsInOrg"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -197,14 +209,14 @@ export const scimServiceFactory = ({
findOpts
);
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email, isActive }) =>
buildScimUser({
orgMembershipId: id ?? "",
username: externalId ?? username,
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
active: true
active: isActive
})
);
@@ -240,13 +252,19 @@ export const scimServiceFactory = ({
status: 403
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
return buildScimUser({
orgMembershipId: membership.id,
username: membership.externalId ?? membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active: true
active: membership.isActive,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
});
};
@@ -296,7 +314,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -364,7 +383,8 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
tx
);
@@ -401,7 +421,7 @@ export const scimServiceFactory = ({
firstName: createdUser.firstName as string,
lastName: createdUser.lastName as string,
email: createdUser.email ?? "",
active: true
active: createdOrgMembership.isActive
});
};
@@ -445,14 +465,8 @@ export const scimServiceFactory = ({
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
await orgMembershipDAL.updateById(membership.id, {
isActive: false
});
}
@@ -491,17 +505,11 @@ export const scimServiceFactory = ({
status: 403
});
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
await orgMembershipDAL.updateById(membership.id, {
isActive: active
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
return buildScimUser({
orgMembershipId: membership.id,
@@ -509,7 +517,11 @@ export const scimServiceFactory = ({
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active
active,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.name
}))
});
};
@@ -881,7 +893,18 @@ export const scimServiceFactory = ({
break;
}
case "remove": {
// TODO
const orgMembershipId = extractScimValueFromPath(operation.path);
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
await removeUsersFromGroupByUserIds({
group,
userIds: [orgMembership.userId as string],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL
});
break;
}
default: {
+4 -1
View File
@@ -158,7 +158,10 @@ export type TScimUser = {
type: string;
}[];
active: boolean;
groups: string[];
groups: {
value: string;
display: string;
}[];
meta: {
resourceType: string;
location: null;