Merge remote-tracking branch 'origin/main' into misc/privilege-management-v2-transition

This commit is contained in:
Sheen Capadngan
2025-03-10 15:35:48 +08:00
41 changed files with 1625 additions and 458 deletions
+30 -18
View File
@@ -88,15 +88,18 @@ export const groupServiceFactory = ({
);
const isCustomRole = Boolean(customRole);
const hasRequiredPriviledges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.ManagePrivileges,
OrgPermissionSubjects.Groups,
permission,
rolePermission
);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to create a more privileged group",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const group = await groupDAL.transaction(async (tx) => {
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
@@ -163,15 +166,18 @@ export const groupServiceFactory = ({
);
const isCustomRole = Boolean(customOrgRole);
const hasRequiredNewRolePermission = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.ManagePrivileges,
OrgPermissionSubjects.Groups,
permission,
rolePermission
);
if (!hasRequiredNewRolePermission)
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update a more privileged group",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
if (isCustomRole) customRole = customOrgRole;
}
@@ -342,15 +348,18 @@ export const groupServiceFactory = ({
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
// check if user has broader or equal to privileges than group
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.AddMembers,
const permissionBoundary = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.ManagePrivileges,
OrgPermissionSubjects.Groups,
permission,
groupRolePermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to add user to more privileged group",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const user = await userDAL.findOne({ username });
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
@@ -415,15 +424,18 @@ export const groupServiceFactory = ({
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
// check if user has broader or equal to privileges than group
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.RemoveMembers,
const permissionBoundary = validatePrivilegeChangeOperation(
OrgPermissionGroupActions.ManagePrivileges,
OrgPermissionSubjects.Groups,
permission,
groupRolePermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to delete user from more privileged group",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const user = await userDAL.findOne({ username });
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
@@ -79,14 +79,18 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
@@ -166,14 +170,18 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
if (data?.slug) {
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
@@ -249,14 +257,18 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPriviledges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
identityRolePermission
);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
return {
@@ -93,14 +93,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
@@ -182,14 +186,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
@@ -283,14 +291,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionIdentityActions.ManagePrivileges,
ProjectPermissionSub.Identity,
permission,
identityRolePermission
);
if (!hasRequiredPrivileges) throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to edit more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
@@ -1,7 +1,7 @@
import { MongoAbility } from "@casl/ability";
import { TOrganizations } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
@@ -60,8 +60,15 @@ const validatePrivilegeChangeOperation = (
managedPermission: MongoAbility
) => {
// first we ensure if the actor has the permission to manage the privilege
// if not, we check if the actor is indeed more privileged than the managed permission
return actorPermission.can(action, subject) || isAtLeastAsPrivileged(actorPermission, managedPermission);
if (actorPermission.can(action, subject)) {
return {
isValid: true,
missingPermissions: []
};
}
// if not, we check if the actor is indeed more privileged than the managed permission - this is the old system
return validatePermissionBoundary(actorPermission, managedPermission);
};
export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO, validatePrivilegeChangeOperation };
@@ -5,22 +5,6 @@ import { PermissionConditionOperators } from "@app/lib/casl";
export const PermissionConditionSchema = {
[PermissionConditionOperators.$IN]: z.string().trim().min(1).array(),
[PermissionConditionOperators.$ALL]: z.string().trim().min(1).array(),
[PermissionConditionOperators.$REGEX]: z
.string()
.min(1)
.refine(
(el) => {
try {
// eslint-disable-next-line no-new
new RegExp(el);
return true;
} catch {
return false;
}
},
{ message: "Invalid regex pattern" }
),
[PermissionConditionOperators.$EQ]: z.string().min(1),
[PermissionConditionOperators.$NEQ]: z.string().min(1),
[PermissionConditionOperators.$GLOB]: z
@@ -80,16 +80,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
const hasRequiredPriviledges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionMemberActions.ManagePrivileges,
ProjectPermissionSub.Member,
permission,
targetUserPermission
);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged user",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
slug,
@@ -174,14 +176,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
const hasRequiredPrivileges = validatePrivilegeChangeOperation(
const permissionBoundary = validatePrivilegeChangeOperation(
ProjectPermissionMemberActions.ManagePrivileges,
ProjectPermissionSub.Member,
permission,
targetUserPermission
);
if (!hasRequiredPrivileges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update more privileged user",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
if (dto?.slug) {
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({