mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 18:41:21 +00:00
Added authz logic to MI
This commit is contained in:
@@ -393,6 +393,13 @@ export const getOrganizationMachineMemberships = async (req: Request, res: Respo
|
||||
const {
|
||||
params: { organizationId }
|
||||
} = await validateRequest(reqValidator.GetOrgServiceMembersV2, req);
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const machineMemberships = await MachineMembershipOrg.find({
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
|
||||
@@ -24,10 +24,12 @@ import * as reqValidator from "../../validation";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getAuthDataProjectPermissions
|
||||
getAuthDataProjectPermissions,
|
||||
getRolePermissions,
|
||||
isAtLeastAsPrivilegedWorkspace
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||
import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||
import { ADMIN, MEMBER, VIEWER } from "../../variables";
|
||||
|
||||
interface V2PushSecret {
|
||||
@@ -523,7 +525,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
let machineMembership = await MachineMembership.findOne({
|
||||
@@ -532,7 +534,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
if (machineMembership) throw BadRequestError({
|
||||
message: "Machine identity already exists in workspace"
|
||||
message: `Machine identity with id ${machineId} already exists in workspace with id ${workspaceId}`
|
||||
});
|
||||
|
||||
const machineIdentity = await MachineIdentity.findById(machineId);
|
||||
@@ -545,6 +547,13 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
message: "Failed to add machine identity to workspace in another organization"
|
||||
});
|
||||
|
||||
const rolePermission = await getRolePermissions(role, workspaceId);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to add a more privileged MI to workspace"
|
||||
});
|
||||
|
||||
let customRole;
|
||||
if (role) {
|
||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
||||
@@ -591,8 +600,8 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
let machineMembership = await MachineMembership.findOne({
|
||||
@@ -601,7 +610,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
if (!machineMembership) throw BadRequestError({
|
||||
message: "Machine identity does not exist in workspace"
|
||||
message: `Machine identity with id ${machineId} does not exist in workspace with id ${workspaceId}`
|
||||
});
|
||||
|
||||
const machineIdentity = await MachineIdentity.findById(machineId);
|
||||
@@ -611,7 +620,14 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
if (!workspace) throw ResourceNotFoundError();
|
||||
|
||||
if (!machineIdentity.organization.equals(workspace.organization)) throw BadRequestError({
|
||||
message: "Failed to add machine identity to workspace in another organization"
|
||||
message: "Failed to update machine identity in workspace in another organization"
|
||||
});
|
||||
|
||||
const rolePermission = await getRolePermissions(role, workspaceId);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to update MI to a more privileged role"
|
||||
});
|
||||
|
||||
let customRole;
|
||||
@@ -665,7 +681,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
const machineMembership = await MachineMembership.findOneAndDelete({
|
||||
@@ -698,7 +714,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
const machineMemberships = await MachineMembership.find({
|
||||
|
||||
@@ -16,13 +16,21 @@ import {
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import * as reqValidator from "../../../validation/machineIdentity";
|
||||
import { createToken } from "../../../helpers/auth";
|
||||
|
||||
|
||||
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||
import {
|
||||
getOrgRolePermissions,
|
||||
getUserOrgPermissions,
|
||||
isAtLeastAsPrivilegedOrg
|
||||
} from "../../services/RoleService";
|
||||
import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||
import { getAuthSecret } from "../../../config";
|
||||
import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "../../services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
/**
|
||||
* Return machine identity access and refresh token as per refresh operation
|
||||
@@ -32,12 +40,12 @@ import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
export const refreshToken = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: {
|
||||
refresh_token
|
||||
refreshToken
|
||||
}
|
||||
} = await validateRequest(reqValidator.RefreshTokenV3, req);
|
||||
|
||||
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
||||
jwt.verify(refresh_token, await getAuthSecret())
|
||||
jwt.verify(refreshToken, await getAuthSecret())
|
||||
);
|
||||
|
||||
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_REFRESH_TOKEN) throw UnauthorizedRequestError();
|
||||
@@ -55,15 +63,15 @@ import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
}
|
||||
|
||||
const response: {
|
||||
refresh_token?: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
token_type: string;
|
||||
refreshToken?: string;
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
tokenType: string;
|
||||
} = {
|
||||
refresh_token,
|
||||
access_token: "",
|
||||
expires_in: 0,
|
||||
token_type: "Bearer"
|
||||
refreshToken,
|
||||
accessToken: "",
|
||||
expiresIn: 0,
|
||||
tokenType: "Bearer"
|
||||
};
|
||||
|
||||
if (machineIdentity.isRefreshTokenRotationEnabled) {
|
||||
@@ -81,7 +89,7 @@ import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
|
||||
if (!machineIdentity) throw BadRequestError();
|
||||
|
||||
response.refresh_token = createToken({
|
||||
response.refreshToken = createToken({
|
||||
payload: {
|
||||
serviceTokenDataId: machineIdentity._id.toString(),
|
||||
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
||||
@@ -91,9 +99,9 @@ import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
});
|
||||
}
|
||||
|
||||
response.access_token = createToken({
|
||||
response.accessToken = createToken({
|
||||
payload: {
|
||||
serviceTokenDataId: machineIdentity._id.toString(),
|
||||
serviceTokenDataId: machineIdentity._id.toString(), // TODO: fix this
|
||||
authTokenType: AuthTokenType.SERVICE_ACCESS_TOKEN,
|
||||
tokenVersion: machineIdentity.tokenVersion
|
||||
},
|
||||
@@ -101,7 +109,7 @@ import { ADMIN, AuthTokenType, CUSTOM, MEMBER } from "../../../variables";
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
response.expires_in = machineIdentity.accessTokenTTL;
|
||||
response.expiresIn = machineIdentity.accessTokenTTL;
|
||||
|
||||
await MachineIdentity.findByIdAndUpdate(
|
||||
machineIdentity._id,
|
||||
@@ -135,22 +143,23 @@ export const createMachineIdentity = async (req: Request, res: Response) => {
|
||||
isRefreshTokenRotationEnabled
|
||||
}
|
||||
} = await validateRequest(reqValidator.CreateMachineIdentityV3, req);
|
||||
|
||||
// const { permission } = await getAuthDataProjectPermissions({
|
||||
// authData: req.authData,
|
||||
// workspaceId: new Types.ObjectId(workspaceId)
|
||||
// });
|
||||
|
||||
// ForbiddenError.from(permission).throwUnlessCan(
|
||||
// ProjectPermissionActions.Create,
|
||||
// ProjectPermissionSub.ServiceTokens
|
||||
// );
|
||||
|
||||
// const workspace = await Workspace.findById(workspaceId);
|
||||
// if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const rolePermission = await getOrgRolePermissions(role, organizationId);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to create a more privileged MI"
|
||||
});
|
||||
|
||||
const organization = await Organization.findById(organizationId);
|
||||
if (!organization) throw BadRequestError({ message: "Organization not found" });
|
||||
if (!organization) throw BadRequestError({ message: `Organization with id ${organizationId} not found` });
|
||||
|
||||
const isCustomRole = ![ADMIN, MEMBER].includes(role);
|
||||
|
||||
@@ -217,7 +226,7 @@ export const createMachineIdentity = async (req: Request, res: Response) => {
|
||||
|
||||
const refreshToken = createToken({
|
||||
payload: {
|
||||
serviceTokenDataId: machineIdentity._id.toString(),
|
||||
serviceTokenDataId: machineIdentity._id.toString(), // TODO: update
|
||||
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
||||
tokenVersion: machineIdentity.tokenVersion
|
||||
},
|
||||
@@ -248,7 +257,7 @@ export const createMachineIdentity = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update service token V3 data with id [serviceTokenDataId]
|
||||
* Update machine identity with id [machineId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
@@ -258,7 +267,6 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
params: { machineId },
|
||||
body: {
|
||||
name,
|
||||
isActive,
|
||||
role,
|
||||
trustedIps,
|
||||
expiresIn,
|
||||
@@ -269,21 +277,24 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
|
||||
let machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: "Service token not found"
|
||||
message: `Machine identity with id ${machineId} not found`
|
||||
});
|
||||
|
||||
// const { permission } = await getAuthDataProjectPermissions({
|
||||
// authData: req.authData,
|
||||
// workspaceId: serviceTokenData.workspace
|
||||
// });
|
||||
|
||||
// ForbiddenError.from(permission).throwUnlessCan(
|
||||
// ProjectPermissionActions.Edit,
|
||||
// ProjectPermissionSub.ServiceTokens
|
||||
// );
|
||||
|
||||
// const workspace = await Workspace.findById(serviceTokenData.workspace);
|
||||
// if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
if (role) {
|
||||
const rolePermission = await getOrgRolePermissions(role, machineIdentity.organization.toString());
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to update MI to a more privileged role"
|
||||
});
|
||||
}
|
||||
|
||||
let customRole;
|
||||
if (role) {
|
||||
@@ -329,7 +340,6 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
machineId,
|
||||
{
|
||||
name,
|
||||
isActive,
|
||||
trustedIps: reformattedTrustedIps,
|
||||
expiresAt,
|
||||
accessTokenTTL,
|
||||
@@ -341,7 +351,7 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
);
|
||||
|
||||
if (!machineIdentity) throw BadRequestError({
|
||||
message: "Failed to update service token"
|
||||
message: `Failed to update machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
await MachineMembershipOrg.findOneAndUpdate(
|
||||
@@ -370,7 +380,6 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
type: EventType.UPDATE_MACHINE_IDENTITY,
|
||||
metadata: {
|
||||
name: machineIdentity.name,
|
||||
isActive,
|
||||
role,
|
||||
trustedIps: reformattedTrustedIps as Array<IMachineIdentityTrustedIp>,
|
||||
expiresAt
|
||||
@@ -387,7 +396,7 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete service token data with id [serviceTokenDataId]
|
||||
* Delete machine identity with id [machineId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
@@ -399,18 +408,15 @@ export const deleteMachineIdentity = async (req: Request, res: Response) => {
|
||||
|
||||
let machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: "Service token not found"
|
||||
message: `Machine identity with id ${machineId} not found`
|
||||
});
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
||||
|
||||
// const { permission } = await getAuthDataProjectPermissions({
|
||||
// authData: req.authData,
|
||||
// workspaceId: serviceTokenData.workspace
|
||||
// });
|
||||
|
||||
// ForbiddenError.from(permission).throwUnlessCan(
|
||||
// ProjectPermissionActions.Delete,
|
||||
// ProjectPermissionSub.ServiceTokens
|
||||
// );
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
machineIdentity = await MachineIdentity.findByIdAndDelete(machineId);
|
||||
|
||||
|
||||
@@ -235,7 +235,6 @@ interface UpdateMachineIdentityEvent {
|
||||
type: EventType.UPDATE_MACHINE_IDENTITY;
|
||||
metadata: {
|
||||
name?: string;
|
||||
isActive?: boolean;
|
||||
role?: string;
|
||||
trustedIps?: Array<IMachineIdentityTrustedIp>;
|
||||
expiresAt?: Date;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||
import picomatch from "picomatch";
|
||||
import { AuthData } from "../../interfaces/middleware";
|
||||
import { ActorType, IRole } from "../models";
|
||||
import { ActorType, IRole, Role } from "../models";
|
||||
import {
|
||||
IMachineIdentity,
|
||||
MachineMembership,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "../../models";
|
||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
|
||||
import { checkIPAgainstBlocklist } from "../../utils/ip";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
|
||||
const $glob: FieldInstruction<string> = {
|
||||
type: "field",
|
||||
@@ -60,7 +61,8 @@ export enum ProjectPermissionSub {
|
||||
Secrets = "secrets",
|
||||
SecretRollback = "secret-rollback",
|
||||
SecretApproval = "secret-approval",
|
||||
SecretRotation = "secret-rotation"
|
||||
SecretRotation = "secret-rotation",
|
||||
MachineIdentity = "machine-identity"
|
||||
}
|
||||
|
||||
type SubjectFields = {
|
||||
@@ -85,6 +87,7 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.MachineIdentity]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
|
||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
|
||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||
@@ -131,6 +134,11 @@ const buildAdminPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.MachineIdentity);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||
@@ -196,6 +204,11 @@ const buildMemberPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.MachineIdentity);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||
@@ -236,6 +249,7 @@ const buildViewerPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.MachineIdentity);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||
@@ -337,3 +351,59 @@ export const getAuthDataProjectPermissions = async ({
|
||||
throw UnauthorizedRequestError();
|
||||
}
|
||||
}
|
||||
|
||||
export const getRolePermissions = async (role: string, workspaceId: string) => {
|
||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
||||
if (isCustomRole) {
|
||||
const workspaceRole = await Role.findOne({
|
||||
slug: role,
|
||||
isOrgRole: false,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!workspaceRole) throw BadRequestError({ message: "Role not found" });
|
||||
|
||||
return createMongoAbility<ProjectPermissionSet>(workspaceRole.permissions as RawRuleOf<MongoAbility<ProjectPermissionSet>>[], {
|
||||
conditionsMatcher
|
||||
});
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case ADMIN:
|
||||
return adminProjectPermissions;
|
||||
case MEMBER:
|
||||
return memberProjectPermissions;
|
||||
case VIEWER:
|
||||
return viewerProjectPermission;
|
||||
default:
|
||||
throw BadRequestError({ message: "Role not found" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||
* @param ability
|
||||
* @returns
|
||||
*/
|
||||
const extractPermissions = (ability: MongoAbility<ProjectPermissionSet> | ProjectPermissionSet) => {
|
||||
return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||
* The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions.
|
||||
*
|
||||
*/
|
||||
export const isAtLeastAsPrivilegedWorkspace = (permissions1: MongoAbility<ProjectPermissionSet> | ProjectPermissionSet, permissions2: MongoAbility<ProjectPermissionSet> | ProjectPermissionSet) => {
|
||||
|
||||
const set1 = new Set(extractPermissions(permissions1));
|
||||
const set2 = new Set(extractPermissions(permissions2));
|
||||
|
||||
for (const perm of set2) {
|
||||
if (!set1.has(perm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return set1.size >= set2.size;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Types } from "mongoose";
|
||||
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
|
||||
import { MembershipOrg } from "../../models";
|
||||
import { IRole } from "../models";
|
||||
import { IRole, Role } from "../models";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ACCEPTED } from "../../variables";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, MEMBER } from "../../variables";
|
||||
import { conditionsMatcher } from "./ProjectRoleService";
|
||||
|
||||
export enum OrgPermissionActions {
|
||||
@@ -21,7 +22,7 @@ export enum OrgPermissionSubjects {
|
||||
Sso = "sso",
|
||||
Billing = "billing",
|
||||
SecretScanning = "secret-scanning",
|
||||
ServiceTokens = "service-tokens" // TODO: consider renaming
|
||||
MachineIdentity = "machine-identity"
|
||||
}
|
||||
|
||||
export type OrgPermissionSet =
|
||||
@@ -34,7 +35,7 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ServiceTokens];
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.MachineIdentity];
|
||||
|
||||
const buildAdminPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||
@@ -77,10 +78,10 @@ const buildAdminPermission = () => {
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
||||
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.MachineIdentity);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
@@ -105,10 +106,10 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ServiceTokens);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.MachineIdentity);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.MachineIdentity);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
@@ -132,11 +133,11 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||
}
|
||||
|
||||
if (membership.role === "admin") return { permission: adminPermissions, membership };
|
||||
if (membership.role === ADMIN) return { permission: adminPermissions, membership };
|
||||
|
||||
if (membership.role === "member") return { permission: memberPermissions, membership };
|
||||
if (membership.role === MEMBER) return { permission: memberPermissions, membership };
|
||||
|
||||
if (membership.role === "custom") {
|
||||
if (membership.role === CUSTOM) {
|
||||
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions, {
|
||||
conditionsMatcher
|
||||
});
|
||||
@@ -144,4 +145,58 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
}
|
||||
|
||||
throw BadRequestError({ message: "User role not found" });
|
||||
};
|
||||
};
|
||||
|
||||
export const getOrgRolePermissions = async (role: string, orgId: string) => {
|
||||
const isCustomRole = ![ADMIN, MEMBER].includes(role);
|
||||
if (isCustomRole) {
|
||||
const orgRole = await Role.findOne({
|
||||
slug: role,
|
||||
isOrgRole: true,
|
||||
organization: new Types.ObjectId(orgId)
|
||||
});
|
||||
|
||||
if (!orgRole) throw BadRequestError({ message: "Org Role not found" });
|
||||
|
||||
return createMongoAbility<OrgPermissionSet>(orgRole.permissions as RawRuleOf<MongoAbility<OrgPermissionSet>>[], {
|
||||
conditionsMatcher
|
||||
});
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case ADMIN:
|
||||
return adminPermissions;
|
||||
case MEMBER:
|
||||
return memberPermissions;
|
||||
default:
|
||||
throw BadRequestError({ message: "User org role not found" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||
* @param ability
|
||||
* @returns
|
||||
*/
|
||||
const extractPermissions = (ability: MongoAbility<OrgPermissionSet> | OrgPermissionSet) => {
|
||||
return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||
* The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions.
|
||||
*
|
||||
*/
|
||||
export const isAtLeastAsPrivilegedOrg = (permissions1: MongoAbility<OrgPermissionSet> | OrgPermissionSet, permissions2: MongoAbility<OrgPermissionSet> | OrgPermissionSet) => {
|
||||
|
||||
const set1 = new Set(extractPermissions(permissions1));
|
||||
const set2 = new Set(extractPermissions(permissions2));
|
||||
|
||||
for (const perm of set2) {
|
||||
if (!set1.has(perm)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return set1.size >= set2.size;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { MEMBER } from "../variables";
|
||||
|
||||
export const RefreshTokenV3 = z.object({
|
||||
body: z.object({
|
||||
refresh_token: z.string().trim()
|
||||
refreshToken: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -31,7 +31,6 @@ export const UpdateMachineIdentityV3 = z.object({
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
role: z.string().trim().min(1).optional(),
|
||||
trustedIps: z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user