Merge pull request #4610 from Infisical/feat/simp-memb

feat: membership simplificato
This commit is contained in:
Scott Wilson
2025-10-09 12:29:02 -07:00
committed by GitHub
204 changed files with 11998 additions and 8445 deletions
+13 -10
View File
@@ -20,8 +20,6 @@ import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
import { TKmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
import { TKmipOperationServiceFactory } from "@app/ee/services/kmip/kmip-operation-service";
import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
@@ -35,7 +33,6 @@ import { TPamSessionServiceFactory } from "@app/ee/services/pam-session/pam-sess
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { TPitServiceFactory } from "@app/ee/services/pit/pit-service";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types";
import { TRelayServiceFactory } from "@app/ee/services/relay/relay-service";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-types";
@@ -53,6 +50,7 @@ import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-servi
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service";
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
@@ -65,6 +63,7 @@ import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-a
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
import { TConvertorServiceFactory } from "@app/services/convertor/convertor-service";
import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
import { TFolderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service";
@@ -88,10 +87,12 @@ import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-a
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { TMembershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service";
import { TMembershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service";
import { TMembershipUserServiceFactory } from "@app/services/membership-user/membership-user-service";
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
import { TNotificationServiceFactory } from "@app/services/notification/notification-service";
import { TOfflineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service";
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
import { TOrgServiceFactory } from "@app/services/org/org-service";
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
@@ -104,8 +105,8 @@ import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { TReminderServiceFactory } from "@app/services/reminder/reminder-types";
import { TRoleServiceFactory } from "@app/services/role/role-service";
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
@@ -213,7 +214,6 @@ declare module "fastify" {
authToken: TAuthTokenServiceFactory;
permission: TPermissionServiceFactory;
org: TOrgServiceFactory;
orgRole: TOrgRoleServiceFactory;
oidc: TOidcConfigServiceFactory;
superAdmin: TSuperAdminServiceFactory;
user: TUserServiceFactory;
@@ -225,7 +225,6 @@ declare module "fastify" {
projectMembership: TProjectMembershipServiceFactory;
projectEnv: TProjectEnvServiceFactory;
projectKey: TProjectKeyServiceFactory;
projectRole: TProjectRoleServiceFactory;
secret: TSecretServiceFactory;
secretReplication: TSecretReplicationServiceFactory;
secretTag: TSecretTagServiceFactory;
@@ -281,9 +280,6 @@ declare module "fastify" {
telemetry: TTelemetryServiceFactory;
dynamicSecret: TDynamicSecretServiceFactory;
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
identityProjectAdditionalPrivilegeV2: TIdentityProjectAdditionalPrivilegeV2ServiceFactory;
secretSharing: TSecretSharingServiceFactory;
rateLimit: TRateLimitServiceFactory;
userEngagement: TUserEngagementServiceFactory;
@@ -324,6 +320,13 @@ declare module "fastify" {
pamAccount: TPamAccountServiceFactory;
pamSession: TPamSessionServiceFactory;
upgradePath: TUpgradePathService;
membershipUser: TMembershipUserServiceFactory;
membershipIdentity: TMembershipIdentityServiceFactory;
membershipGroup: TMembershipGroupServiceFactory;
additionalPrivilege: TAdditionalPrivilegeServiceFactory;
role: TRoleServiceFactory;
convertor: TConvertorServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer
+29
View File
@@ -17,6 +17,9 @@ import {
TAccessApprovalRequestsReviewersInsert,
TAccessApprovalRequestsReviewersUpdate,
TAccessApprovalRequestsUpdate,
TAdditionalPrivileges,
TAdditionalPrivilegesInsert,
TAdditionalPrivilegesUpdate,
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate,
@@ -227,6 +230,15 @@ import {
TLdapGroupMaps,
TLdapGroupMapsInsert,
TLdapGroupMapsUpdate,
TMembershipRoles,
TMembershipRolesInsert,
TMembershipRolesUpdate,
TMemberships,
TMembershipsInsert,
TMembershipsUpdate,
TNamespaces,
TNamespacesInsert,
TNamespacesUpdate,
TOidcConfigs,
TOidcConfigsInsert,
TOidcConfigsUpdate,
@@ -314,6 +326,9 @@ import {
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate,
TRoles,
TRolesInsert,
TRolesUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
@@ -1316,5 +1331,19 @@ declare module "knex/types/tables" {
[TableName.PamResource]: KnexOriginal.CompositeTableType<TPamResources, TPamResourcesInsert, TPamResourcesUpdate>;
[TableName.PamAccount]: KnexOriginal.CompositeTableType<TPamAccounts, TPamAccountsInsert, TPamAccountsUpdate>;
[TableName.PamSession]: KnexOriginal.CompositeTableType<TPamSessions, TPamSessionsInsert, TPamSessionsUpdate>;
[TableName.Namespace]: KnexOriginal.CompositeTableType<TNamespaces, TNamespacesInsert, TNamespacesUpdate>;
[TableName.Membership]: KnexOriginal.CompositeTableType<TMemberships, TMembershipsInsert, TMembershipsUpdate>;
[TableName.MembershipRole]: KnexOriginal.CompositeTableType<
TMembershipRoles,
TMembershipRolesInsert,
TMembershipRolesUpdate
>;
[TableName.Role]: KnexOriginal.CompositeTableType<TRoles, TRolesInsert, TRolesUpdate>;
[TableName.AdditionalPrivilege]: KnexOriginal.CompositeTableType<
TAdditionalPrivileges,
TAdditionalPrivilegesInsert,
TAdditionalPrivilegesUpdate
>;
}
}
@@ -127,7 +127,8 @@ export async function down(knex: Knex): Promise<void> {
});
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (tb) => {
tb.dropColumn("approverUserId");
tb.uuid("approverId").notNullable().alter();
// akhilmhdh: i had to comment this out and is not possible as membership is now changed in structure
// tb.uuid("approverId").notNullable().alter();
});
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AdditionalPrivilegesSchema = z.object({
id: z.string().uuid(),
name: z.string(),
isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional(),
permissions: z.unknown(),
actorUserId: z.string().uuid().nullable().optional(),
actorIdentityId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid().nullable().optional(),
projectId: z.string().nullable().optional(),
namespaceId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAdditionalPrivileges = z.infer<typeof AdditionalPrivilegesSchema>;
export type TAdditionalPrivilegesInsert = Omit<z.input<typeof AdditionalPrivilegesSchema>, TImmutableDBKeys>;
export type TAdditionalPrivilegesUpdate = Partial<Omit<z.input<typeof AdditionalPrivilegesSchema>, TImmutableDBKeys>>;
+5
View File
@@ -3,6 +3,7 @@ export * from "./access-approval-policies-approvers";
export * from "./access-approval-policies-bypassers";
export * from "./access-approval-requests";
export * from "./access-approval-requests-reviewers";
export * from "./additional-privileges";
export * from "./api-keys";
export * from "./app-connections";
export * from "./audit-log-streams";
@@ -73,8 +74,11 @@ export * from "./kms-keys";
export * from "./kms-root-config";
export * from "./ldap-configs";
export * from "./ldap-group-maps";
export * from "./membership-roles";
export * from "./memberships";
export * from "./microsoft-teams-integrations";
export * from "./models";
export * from "./namespaces";
export * from "./oidc-configs";
export * from "./org-bots";
export * from "./org-gateway-config";
@@ -108,6 +112,7 @@ export * from "./projects";
export * from "./rate-limit";
export * from "./relays";
export * from "./resource-metadata";
export * from "./roles";
export * from "./saml-configs";
export * from "./scim-tokens";
export * from "./secret-approval-policies";
@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const MembershipRolesSchema = z.object({
id: z.string().uuid(),
role: z.string(),
isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional(),
customRoleId: z.string().uuid().nullable().optional(),
membershipId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TMembershipRoles = z.infer<typeof MembershipRolesSchema>;
export type TMembershipRolesInsert = Omit<z.input<typeof MembershipRolesSchema>, TImmutableDBKeys>;
export type TMembershipRolesUpdate = Partial<Omit<z.input<typeof MembershipRolesSchema>, TImmutableDBKeys>>;
+32
View File
@@ -0,0 +1,32 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const MembershipsSchema = z.object({
id: z.string().uuid(),
scope: z.string(),
actorUserId: z.string().uuid().nullable().optional(),
actorIdentityId: z.string().uuid().nullable().optional(),
actorGroupId: z.string().uuid().nullable().optional(),
scopeOrgId: z.string().uuid(),
scopeProjectId: z.string().nullable().optional(),
scopeNamespaceId: z.string().uuid().nullable().optional(),
isActive: z.boolean().default(true),
status: z.string().nullable().optional(),
inviteEmail: z.string().nullable().optional(),
lastInvitedAt: z.date().nullable().optional(),
lastLoginAuthMethod: z.string().nullable().optional(),
lastLoginTime: z.date().nullable().optional(),
projectFavorites: z.string().array().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TMemberships = z.infer<typeof MembershipsSchema>;
export type TMembershipsInsert = Omit<z.input<typeof MembershipsSchema>, TImmutableDBKeys>;
export type TMembershipsUpdate = Partial<Omit<z.input<typeof MembershipsSchema>, TImmutableDBKeys>>;
+40
View File
@@ -178,6 +178,14 @@ export enum TableName {
SecretScanningScan = "secret_scanning_scans",
SecretScanningFinding = "secret_scanning_findings",
SecretScanningConfig = "secret_scanning_configs",
Membership = "memberships",
MembershipRole = "membership_roles",
Role = "roles",
AdditionalPrivilege = "additional_privileges",
Namespace = "namespaces",
// reminders
Reminder = "reminders",
ReminderRecipient = "reminders_recipients",
@@ -302,7 +310,39 @@ export enum ActionProjectType {
Any = "any"
}
export enum TemporaryPermissionMode {
Relative = "relative"
}
export enum MembershipActors {
Group = "group",
User = "user",
Identity = "identity"
}
export enum SortDirection {
ASC = "asc",
DESC = "desc"
}
export enum AccessScope {
Organization = "organization",
Namespace = "namespace",
Project = "project"
}
export type AccessScopeData =
| {
scope: AccessScope.Organization;
orgId: string;
}
| {
scope: AccessScope.Namespace;
orgId: string;
namespaceId: string;
}
| {
scope: AccessScope.Project;
orgId: string;
projectId: string;
};
+21
View File
@@ -0,0 +1,21 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const NamespacesSchema = z.object({
id: z.string().uuid(),
name: z.string(),
description: z.string().nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TNamespaces = z.infer<typeof NamespacesSchema>;
export type TNamespacesInsert = Omit<z.input<typeof NamespacesSchema>, TImmutableDBKeys>;
export type TNamespacesUpdate = Partial<Omit<z.input<typeof NamespacesSchema>, TImmutableDBKeys>>;
+25
View File
@@ -0,0 +1,25 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const RolesSchema = z.object({
id: z.string().uuid(),
name: z.string(),
description: z.string().nullable().optional(),
slug: z.string(),
permissions: z.unknown(),
orgId: z.string().uuid().nullable().optional(),
projectId: z.string().nullable().optional(),
namespaceId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TRoles = z.infer<typeof RolesSchema>;
export type TRolesInsert = Omit<z.input<typeof RolesSchema>, TImmutableDBKeys>;
export type TRolesUpdate = Partial<Omit<z.input<typeof RolesSchema>, TImmutableDBKeys>>;
@@ -0,0 +1,27 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const UserNotificationsDefaultSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
orgId: z.string().uuid().nullable().optional(),
type: z.string(),
title: z.string(),
body: z.string().nullable().optional(),
link: z.string().nullable().optional(),
isRead: z.boolean().default(false),
createdAt: z.date(),
updatedAt: z.date()
});
export type TUserNotificationsDefault = z.infer<typeof UserNotificationsDefaultSchema>;
export type TUserNotificationsDefaultInsert = Omit<z.input<typeof UserNotificationsDefaultSchema>, TImmutableDBKeys>;
export type TUserNotificationsDefaultUpdate = Partial<
Omit<z.input<typeof UserNotificationsDefaultSchema>, TImmutableDBKeys>
>;
+16 -7
View File
@@ -1,6 +1,6 @@
import { Knex } from "knex";
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "../schemas";
import { AccessScope, OrgMembershipRole, OrgMembershipStatus, TableName } from "../schemas";
import { seedData1 } from "../seed-data";
export async function seed(knex: Knex): Promise<void> {
@@ -24,13 +24,22 @@ export async function seed(knex: Knex): Promise<void> {
])
.returning("*");
await knex(TableName.OrgMembership).insert([
const [membership] = await knex(TableName.Membership)
.insert([
{
scope: AccessScope.Organization,
scopeOrgId: org.id,
actorUserId: user.id,
isActive: true,
status: OrgMembershipStatus.Accepted
}
])
.returning("*");
await knex(TableName.MembershipRole).insert([
{
role: OrgMembershipRole.Admin,
orgId: org.id,
status: OrgMembershipStatus.Accepted,
userId: user.id,
isActive: true
membershipId: membership.id,
role: OrgMembershipRole.Admin
}
]);
}
+35 -23
View File
@@ -6,14 +6,15 @@ import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { initLogger, logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AuthMethod } from "@app/services/auth/auth-type";
import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns";
import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { userDALFactory } from "@app/services/user/user-dal";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipStatus,
ProjectMembershipRole,
@@ -39,8 +40,8 @@ const createUserWithGhostUser = async (
) => {
const projectKeyDAL = projectKeyDALFactory(knex);
const userDAL = userDALFactory(knex);
const projectMembershipDAL = projectMembershipDALFactory(knex);
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(knex);
const membershipDAL = membershipUserDALFactory(knex);
const membershipRoleDAL = membershipRoleDALFactory(knex);
const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
@@ -63,25 +64,36 @@ const createUserWithGhostUser = async (
.onConflict("userId")
.merge();
await knex(TableName.OrgMembership)
const [orgMembership] = await knex(TableName.Membership)
.insert({
orgId,
userId: ghostUser.id,
role: OrgMembershipRole.Admin,
scope: AccessScope.Organization,
scopeOrgId: orgId,
actorUserId: ghostUser.id,
status: OrgMembershipStatus.Accepted,
isActive: true
})
.returning("*");
const [projectMembership] = await knex(TableName.ProjectMembership)
await knex(TableName.MembershipRole).insert([
{
membershipId: orgMembership.id,
role: OrgMembershipRole.Admin
}
]);
const [projectMembership] = await knex(TableName.Membership)
.insert({
userId: ghostUser.id,
projectId
actorUserId: ghostUser.id,
scopeProjectId: projectId,
scope: AccessScope.Project,
scopeOrgId: orgId,
status: OrgMembershipStatus.Accepted,
isActive: true
})
.returning("*");
await knex(TableName.ProjectUserMembershipRole).insert({
projectMembershipId: projectMembership.id,
await knex(TableName.MembershipRole).insert({
membershipId: projectMembership.id,
role: ProjectMembershipRole.Admin
});
@@ -142,17 +154,16 @@ const createUserWithGhostUser = async (
});
// Create a membership for the user
const userProjectMembership = await projectMembershipDAL.create(
const userProjectMembership = await membershipDAL.create(
{
projectId,
userId: user.id
scopeProjectId: projectId,
scope: AccessScope.Project,
actorUserId: user.id,
scopeOrgId: orgId
},
knex
);
await projectUserMembershipRoleDAL.create(
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
knex
);
await membershipRoleDAL.create({ membershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin }, knex);
// Create a project key for the user
await projectKeyDAL.create(
@@ -195,10 +206,11 @@ export async function seed(knex: Knex): Promise<void> {
})
.returning("*");
const userOrgMembership = await knex(TableName.OrgMembership)
const userOrgMembership = await knex(TableName.Membership)
.where({
orgId: seedData1.organization.id,
userId: seedData1.id
scopeOrgId: seedData1.organization.id,
actorUserId: seedData1.id,
scope: AccessScope.Organization
})
.first();
+8 -6
View File
@@ -1,6 +1,6 @@
import { Knex } from "knex";
import { ProjectMembershipRole, ProjectType, ProjectVersion, TableName } from "../schemas";
import { AccessScope, ProjectMembershipRole, ProjectType, ProjectVersion, TableName } from "../schemas";
import { seedData1 } from "../seed-data";
export const DEFAULT_PROJECT_ENVS = [
@@ -23,15 +23,17 @@ export async function seed(knex: Knex): Promise<void> {
})
.returning("*");
const projectMembershipV3 = await knex(TableName.ProjectMembership)
const projectMembershipV3 = await knex(TableName.Membership)
.insert({
projectId: projectV2.id,
userId: seedData1.id
scopeProjectId: projectV2.id,
actorUserId: seedData1.id,
scope: AccessScope.Project,
scopeOrgId: seedData1.organization.id
})
.returning("*");
await knex(TableName.ProjectUserMembershipRole).insert({
await knex(TableName.MembershipRole).insert({
role: ProjectMembershipRole.Admin,
projectMembershipId: projectMembershipV3[0].id
membershipId: projectMembershipV3[0].id
});
// create default environments and default folders
+27 -15
View File
@@ -5,13 +5,12 @@ import { crypto } from "@app/lib/crypto/cryptography";
import { initLogger, logger } from "@app/lib/logger";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
import { AccessScope, IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
import { seedData1 } from "../seed-data";
export async function seed(knex: Knex): Promise<void> {
// Deletes ALL existing entries
await knex(TableName.Identity).del();
await knex(TableName.IdentityOrgMembership).del();
initLogger();
@@ -78,34 +77,47 @@ export async function seed(knex: Knex): Promise<void> {
isClientSecretRevoked: false
}
]);
await knex(TableName.IdentityOrgMembership).insert([
const [orgMembership] = await knex(TableName.Membership)
.insert([
{
actorIdentityId: seedData1.machineIdentity.id,
scopeOrgId: seedData1.organization.id,
scope: AccessScope.Organization
}
])
.returning("*");
await knex(TableName.MembershipRole).insert([
{
identityId: seedData1.machineIdentity.id,
orgId: seedData1.organization.id,
membershipId: orgMembership.id,
role: OrgMembershipRole.Admin
}
]);
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
const identityProjectMembership = await knex(TableName.Membership)
.insert({
identityId: seedData1.machineIdentity.id,
projectId: seedData1.project.id
actorIdentityId: seedData1.machineIdentity.id,
scopeOrgId: seedData1.organization.id,
scope: AccessScope.Project,
scopeProjectId: seedData1.project.id
})
.returning("*");
await knex(TableName.IdentityProjectMembershipRole).insert({
await knex(TableName.MembershipRole).insert({
role: ProjectMembershipRole.Admin,
projectMembershipId: identityProjectMembership[0].id
membershipId: identityProjectMembership[0].id
});
const identityProjectMembershipV3 = await knex(TableName.IdentityProjectMembership)
const identityProjectMembershipV3 = await knex(TableName.Membership)
.insert({
identityId: seedData1.machineIdentity.id,
projectId: seedData1.projectV3.id
actorIdentityId: seedData1.machineIdentity.id,
scopeOrgId: seedData1.organization.id,
scope: AccessScope.Project,
scopeProjectId: seedData1.projectV3.id
})
.returning("*");
await knex(TableName.IdentityProjectMembershipRole).insert({
await knex(TableName.MembershipRole).insert({
role: ProjectMembershipRole.Admin,
projectMembershipId: identityProjectMembershipV3[0].id
membershipId: identityProjectMembershipV3[0].id
});
}
@@ -1,7 +1,7 @@
import { packRules } from "@casl/ability/extra";
import { z } from "zod";
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { AccessScope, ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import {
backfillPermissionV1SchemaToV2Schema,
@@ -13,7 +13,6 @@ import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -55,14 +54,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))
);
const role = await server.services.projectRole.createRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.SLUG,
projectSlug: req.params.projectSlug
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.params.projectSlug,
orgId: req.permission.orgId
});
const role = await server.services.role.createRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId
},
data: {
...req.body,
@@ -73,7 +74,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId,
event: {
type: EventType.CREATE_PROJECT_ROLE,
metadata: {
@@ -86,7 +87,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -131,12 +132,21 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
: undefined;
const role = await server.services.projectRole.updateRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId,
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.params.projectSlug,
orgId: req.permission.orgId
});
const role = await server.services.role.updateRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId
},
selector: {
id: req.params.roleId
},
data: {
...req.body,
permissions: stringifiedPermissions
@@ -146,7 +156,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.UPDATE_PROJECT_ROLE,
metadata: {
@@ -159,7 +169,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -188,18 +198,27 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.deleteRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.params.projectSlug,
orgId: req.permission.orgId
});
const role = await server.services.role.deleteRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId
},
selector: {
id: req.params.roleId
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.DELETE_PROJECT_ROLE,
metadata: {
@@ -210,7 +229,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -238,17 +257,21 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectRole.listRoles({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.SLUG,
projectSlug: req.params.projectSlug
}
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.params.projectSlug,
orgId: req.permission.orgId
});
return { roles };
const { roles } = await server.services.role.listRoles({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId
},
data: {}
});
return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) };
}
});
@@ -265,78 +288,30 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}),
response: {
200: z.object({
role: SanitizedRoleSchemaV1.omit({ version: true })
role: SanitizedRoleSchemaV1
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.getRoleBySlug({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.SLUG,
projectSlug: req.params.projectSlug
},
roleSlug: req.params.slug
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.params.projectSlug,
orgId: req.permission.orgId
});
return { role };
}
});
server.route({
method: "GET",
url: "/:projectId/permissions",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
data: z.object({
membership: z.object({
id: z.string(),
roles: z
.object({
role: z.string()
})
.array()
}),
assumedPrivilegeDetails: z
.object({
actorId: z.string(),
actorType: z.string(),
actorName: z.string(),
actorEmail: z.string().optional()
})
.optional(),
permissions: z.any().array()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
req.permission.id,
req.params.projectId,
req.permission.authMethod,
req.permission.orgId
);
return {
data: {
permissions,
membership,
assumedPrivilegeDetails
const role = await server.services.role.getRoleBySlug({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId
},
selector: {
slug: req.params.slug
}
};
});
return { role: { ...role, projectId: role.projectId as string } };
}
});
};
@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { UnauthorizedError } from "@app/lib/errors";
@@ -15,7 +15,7 @@ import {
ProjectSpecificPrivilegePermissionSchema,
SanitizedIdentityPrivilegeSchema
} from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -56,6 +56,10 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
if (!permissions && !privilegePermission) {
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
}
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
const permission = privilegePermission
? privilegePermission.actions.map((action) => ({
@@ -64,19 +68,35 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
isTemporary: false,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
data: {
actorId: req.body.identityId,
actorType: ActorType.IDENTITY,
...req.body,
isTemporary: false,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}
};
}
});
@@ -106,7 +126,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.privilegePermission
).optional(),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -138,19 +158,39 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
isTemporary: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
return { privilege };
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
data: {
actorId: req.body.identityId,
actorType: ActorType.IDENTITY,
...req.body,
isTemporary: true,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: backfillPermissionV1SchemaToV2Schema(permission)
}
});
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}
};
}
});
@@ -183,7 +223,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
).optional(),
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -216,18 +256,36 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
conditions: privilegePermission.conditions
}))
: permissions!;
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
slug: req.body.privilegeSlug,
identityId: req.body.identityId,
projectSlug: req.body.projectSlug,
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeNameToDoc(
req.body.privilegeSlug,
projectId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.body.identityId,
actorType: ActorType.IDENTITY,
id: privilegeDoc.id
},
data: {
...req.body,
...updatedInfo,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
permissions: permission
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
@@ -235,7 +293,16 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
: undefined
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}
};
}
});
@@ -267,16 +334,39 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.privilegeSlug,
identityId: req.body.identityId,
projectSlug: req.body.projectSlug
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.body.projectSlug
});
return { privilege };
const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToDoc(
req.body.privilegeSlug,
projectId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.body.identityId,
actorType: ActorType.IDENTITY,
id: privilegeId
}
});
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}
};
}
});
@@ -310,15 +400,39 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.query.projectSlug
});
return { privilege };
const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToDoc(
req.params.privilegeSlug,
projectId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: ActorType.IDENTITY,
id: privilegeId
}
});
return {
privilege: {
...privilege,
identityId: req.query.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}
};
}
});
@@ -349,15 +463,32 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
orgId: req.permission.orgId,
slug: req.query.projectSlug
});
const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: ActorType.IDENTITY
}
});
return {
privileges
privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: projectId,
projectId,
slug: privilege.name
}))
};
}
});
+91 -56
View File
@@ -1,7 +1,9 @@
import { packRules } from "@casl/ability/extra";
import { z } from "zod";
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { OrgPermissionSchema } from "@app/ee/services/permission/org-permission";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -25,8 +27,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
),
name: z.string().trim(),
description: z.string().trim().nullish(),
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array()
permissions: z.any().array()
permissions: OrgPermissionSchema.array()
}),
response: {
200: z.object({
@@ -36,13 +37,18 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const role = await server.services.orgRole.createRole(
req.permission.id,
req.params.organizationId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions));
const role = await server.services.role.createRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.params.organizationId
},
data: {
...req.body,
permissions: stringifiedPermissions
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -59,7 +65,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, orgId: role.orgId as string } };
}
});
@@ -82,14 +88,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const role = await server.services.orgRole.getRole(
req.permission.id,
req.params.organizationId,
req.params.roleId,
req.permission.authMethod,
req.permission.orgId
);
return { role };
const role = await server.services.role.getRoleById({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.params.organizationId
},
selector: {
id: req.params.roleId
}
});
return { role: { ...role, orgId: role.orgId as string } };
}
});
@@ -114,8 +123,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
.optional(),
name: z.string().trim().optional(),
description: z.string().trim().nullish(),
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array().optional()
permissions: z.any().array().optional()
permissions: OrgPermissionSchema.array().optional()
}),
response: {
200: z.object({
@@ -125,14 +133,21 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const role = await server.services.orgRole.updateRole(
req.permission.id,
req.params.organizationId,
req.params.roleId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined;
const role = await server.services.role.updateRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.params.organizationId
},
selector: {
id: req.params.roleId
},
data: {
...req.body,
permissions: stringifiedPermissions
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -149,7 +164,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, orgId: role.orgId as string } };
}
});
@@ -172,13 +187,16 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const role = await server.services.orgRole.deleteRole(
req.permission.id,
req.params.organizationId,
req.params.roleId,
req.permission.authMethod,
req.permission.orgId
);
const role = await server.services.role.deleteRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.params.organizationId
},
selector: {
id: req.params.roleId
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -189,7 +207,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, orgId: role.orgId as string } };
}
});
@@ -206,22 +224,26 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
data: z.object({
roles: OrgRolesSchema.omit({ permissions: true })
.merge(z.object({ permissions: z.unknown() }))
.array()
roles: OrgRolesSchema.omit({ permissions: true }).array()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const roles = await server.services.orgRole.listRoles(
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.orgId
);
return { data: { roles } };
const { roles } = await server.services.role.listRoles({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
data: {}
});
return {
data: {
roles: roles.map((el) => ({ ...el, orgId: el.orgId as string }))
}
};
}
});
@@ -237,20 +259,33 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
membership: OrgMembershipsSchema,
memberships: z
.object({
id: z.string(),
roles: z
.object({
role: z.string()
})
.array()
})
.array(),
permissions: z.any().array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { permissions, membership } = await server.services.orgRole.getUserPermission(
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.orgId
);
return { permissions, membership };
const { permissions, memberships } = await server.services.role.getUserPermission({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
}
});
return {
permissions,
memberships
};
}
});
};
+70 -61
View File
@@ -1,7 +1,7 @@
import { packRules } from "@casl/ability/extra";
import { z } from "zod";
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { AccessScope, ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
@@ -11,7 +11,6 @@ import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -55,13 +54,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
handler: async (req) => {
const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions));
const role = await server.services.projectRole.createRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const role = await server.services.role.createRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
@@ -73,7 +70,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.CREATE_PROJECT_ROLE,
metadata: {
@@ -86,7 +83,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -133,12 +130,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined;
const role = await server.services.projectRole.updateRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId,
const role = await server.services.role.updateRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
id: req.params.roleId
},
data: {
...req.body,
permissions: stringifiedPermissions
@@ -148,7 +149,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.UPDATE_PROJECT_ROLE,
metadata: {
@@ -161,7 +162,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -192,18 +193,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.deleteRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId
const role = await server.services.role.deleteRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
id: req.params.roleId
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.DELETE_PROJECT_ROLE,
metadata: {
@@ -214,7 +219,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -244,17 +249,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectRole.listRoles({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const { roles } = await server.services.role.listRoles({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
},
data: {}
});
return { roles };
return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) };
}
});
@@ -273,24 +277,25 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
role: SanitizedRoleSchema.omit({ version: true })
role: SanitizedRoleSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.getRoleBySlug({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const role = await server.services.role.getRoleBySlug({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
roleSlug: req.params.roleSlug
selector: {
slug: req.params.roleSlug
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -307,14 +312,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
data: z.object({
membership: z.object({
id: z.string(),
roles: z
.object({
role: z.string()
})
.array()
}),
memberships: z
.object({
id: z.string(),
roles: z
.object({
role: z.string()
})
.array()
})
.array(),
assumedPrivilegeDetails: z
.object({
actorId: z.string(),
@@ -330,17 +337,19 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
req.permission.id,
req.params.projectId,
req.permission.authMethod,
req.permission.orgId
);
const { permissions, memberships, assumedPrivilegeDetails } = await server.services.role.getUserPermission({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: req.params.projectId,
orgId: req.permission.orgId
}
});
return {
data: {
permissions,
membership,
memberships,
assumedPrivilegeDetails
}
};
@@ -11,7 +11,6 @@ const AccessListEntrySchema = z
.object({
allowedActions: z.nativeEnum(ProjectPermissionSecretActions).array(),
id: z.string(),
membershipId: z.string(),
name: z.string()
})
.array();
@@ -1,17 +1,18 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/sanitizedSchema/user-additional-privilege";
import { AuthMode } from "@app/services/auth/auth-type";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -34,7 +35,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
z.object({
isTemporary: z.literal(true),
temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -55,17 +56,31 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectMembershipId: req.body.projectMembershipId,
...req.body.type,
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
req.body.projectMembershipId,
AccessScope.Project,
req.permission.orgId
);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
data: {
actorId: userId,
actorType: ActorType.USER,
...req.body.type,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
}
});
return { privilege };
return {
privilege: { ...privilege, userId, projectId: membership.scopeProjectId as string, slug: privilege.name }
};
}
});
@@ -91,7 +106,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
z.object({
isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -113,21 +128,41 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
...req.body.type,
permissions: req.body.permissions
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
req.body.permissions
: undefined,
privilegeId: req.params.privilegeId
const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId);
if (!data.privilege.actorUserId)
throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` });
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: data.privilege.projectId as string,
orgId: req.permission.orgId
},
data: {
...req.body,
...req.body.type,
permissions: req.body.permissions
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts
req.body.permissions
: undefined
},
selector: {
id: req.params.privilegeId,
actorId: data.privilege.actorUserId,
actorType: ActorType.USER
}
});
return { privilege };
return {
privilege: {
...privilege,
userId: data.privilege.actorUserId,
projectId: data.privilege.projectId as string,
slug: privilege.name
}
};
}
});
@@ -149,14 +184,32 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId
const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId);
if (!data.privilege.actorUserId)
throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` });
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: data.privilege.projectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.privilegeId,
actorId: data.privilege.actorUserId,
actorType: ActorType.USER
}
});
return { privilege };
return {
privilege: {
...privilege,
userId: data.privilege.actorUserId,
projectId: data.privilege.projectId as string,
slug: privilege.name
}
};
}
});
@@ -178,14 +231,33 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectMembershipId: req.query.projectMembershipId
const { userId, membership } = await server.services.convertor.userMembershipIdToUserId(
req.query.projectMembershipId,
AccessScope.Project,
req.permission.orgId
);
const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: membership.scopeProjectId as string,
orgId: req.permission.orgId
},
selector: {
actorId: userId,
actorType: ActorType.USER
}
});
return { privileges };
return {
privileges: privileges.map((privilege) => ({
...privilege,
userId: membership.actorUserId as string,
projectId: membership.scopeProjectId as string,
slug: privilege.name
}))
};
}
});
@@ -207,14 +279,32 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
privilegeId: req.params.privilegeId
const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId);
if (!data.privilege.actorUserId)
throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` });
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: data.privilege.projectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.privilegeId,
actorId: data.privilege.actorUserId,
actorType: ActorType.USER
}
});
return { privilege };
return {
privilege: {
...privilege,
userId: data.privilege.actorUserId,
projectId: data.privilege.projectId as string,
slug: privilege.name
}
};
}
});
};
@@ -1,7 +1,7 @@
import { packRules } from "@casl/ability/extra";
import { z } from "zod";
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { AccessScope, ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
@@ -11,7 +11,6 @@ import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -55,13 +54,11 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
handler: async (req) => {
const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions));
const role = await server.services.projectRole.createRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const role = await server.services.role.createRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
@@ -73,7 +70,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: req.params.projectId,
event: {
type: EventType.CREATE_PROJECT_ROLE,
metadata: {
@@ -86,7 +83,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -133,12 +130,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined;
const role = await server.services.projectRole.updateRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId,
const role = await server.services.role.updateRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
id: req.params.roleId
},
data: {
...req.body,
permissions: stringifiedPermissions
@@ -148,7 +149,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.UPDATE_PROJECT_ROLE,
metadata: {
@@ -161,7 +162,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -192,18 +193,22 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.deleteRole({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
roleId: req.params.roleId
const role = await server.services.role.deleteRole({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
id: req.params.roleId
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: role.projectId,
projectId: role.projectId as string,
event: {
type: EventType.DELETE_PROJECT_ROLE,
metadata: {
@@ -214,7 +219,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
@@ -244,17 +249,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectRole.listRoles({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const { roles } = await server.services.role.listRoles({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
},
data: {}
});
return { roles };
return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) };
}
});
@@ -273,24 +277,25 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv
}),
response: {
200: z.object({
role: SanitizedRoleSchema.omit({ version: true })
role: SanitizedRoleSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const role = await server.services.projectRole.getRoleBySlug({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
filter: {
type: ProjectRoleServiceIdentifierType.ID,
const role = await server.services.role.getRoleBySlug({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
roleSlug: req.params.roleSlug
selector: {
slug: req.params.roleSlug
}
});
return { role };
return { role: { ...role, projectId: role.projectId as string } };
}
});
};
@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
@@ -11,7 +11,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchema/identitiy-additional-privilege";
import { AuthMode } from "@app/services/auth/auth-type";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -43,7 +43,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
z.object({
isTemporary: z.literal(true),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryMode),
temporaryRange: z
.string()
@@ -64,18 +64,31 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.create({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
projectId: req.body.projectId,
identityId: req.body.identityId,
...req.body.type,
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.createAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: req.body.projectId,
orgId: req.permission.orgId
},
data: {
actorId: req.body.identityId,
actorType: ActorType.IDENTITY,
...req.body.type,
name: req.body.slug || slugify(alphaNumericNanoId(8)),
permissions: req.body.permissions
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: req.body.identityId,
projectMembershipId: req.body.projectId,
projectId: req.body.projectId,
slug: privilege.name
}
};
}
});
@@ -108,7 +121,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
z.object({
isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary),
temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryMode),
temporaryRange: z
.string()
@@ -129,19 +142,36 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.updateById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.id,
const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: privilegeDoc.projectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: privilegeDoc.actorIdentityId as string,
actorType: ActorType.IDENTITY
},
data: {
...req.body,
...req.body.type,
permissions: req.body.permissions || undefined
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: privilegeDoc.actorIdentityId as string,
projectMembershipId: privilegeDoc.projectId as string,
projectId: privilegeDoc.projectId as string,
slug: privilege.name
}
};
}
});
@@ -171,14 +201,31 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.deleteById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: privilegeDoc.projectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: privilegeDoc.actorIdentityId as string,
actorType: ActorType.IDENTITY
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: privilegeDoc.actorIdentityId as string,
projectMembershipId: privilegeDoc.projectId as string,
projectId: privilegeDoc.projectId as string,
slug: privilege.name
}
};
}
});
@@ -208,14 +255,31 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsById({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
id: req.params.id
const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id);
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: privilegeDoc.projectId as string,
orgId: req.permission.orgId
},
selector: {
id: req.params.id,
actorId: privilegeDoc.actorIdentityId as string,
actorType: ActorType.IDENTITY
}
});
return { privilege };
return {
privilege: {
...privilege,
identityId: privilegeDoc.actorIdentityId as string,
projectMembershipId: privilegeDoc.projectId as string,
projectId: privilegeDoc.projectId as string,
slug: privilege.name
}
};
}
});
@@ -249,15 +313,36 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsBySlug({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
slug: req.params.privilegeSlug,
...req.query
const { id: projectId } = await server.services.convertor.projectSlugToId({
slug: req.query.projectSlug,
orgId: req.permission.orgId
});
return { privilege };
const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeByName(
{
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId,
orgId: req.permission.orgId
},
selector: {
name: req.params.privilegeSlug,
actorId: req.query.identityId,
actorType: ActorType.IDENTITY
}
}
);
return {
privilege: {
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.projectId as string,
projectId,
slug: privilege.name
}
};
}
});
@@ -288,15 +373,27 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const privileges = await server.services.identityProjectAdditionalPrivilegeV2.listIdentityProjectPrivileges({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
const { additionalPrivileges: privileges } = await server.services.additionalPrivilege.listAdditionalPrivileges({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
projectId: req.query.projectId,
orgId: req.permission.orgId
},
selector: {
actorId: req.query.identityId,
actorType: ActorType.IDENTITY
}
});
return {
privileges
privileges: privileges.map((privilege) => ({
...privilege,
identityId: req.query.identityId,
projectMembershipId: privilege.projectId as string,
projectId: req.query.projectId,
slug: privilege.name
}))
};
}
});
@@ -1,21 +1,20 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { AccessScope, ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal";
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { TAccessApprovalRequestReviewerDALFactory } from "../access-approval-request/access-approval-request-reviewer-dal";
import { ApprovalStatus } from "../access-approval-request/access-approval-request-types";
import { TGroupDALFactory } from "../group/group-dal";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import {
TAccessApprovalPolicyApproverDALFactory,
TAccessApprovalPolicyBypasserDALFactory
@@ -39,14 +38,13 @@ type TAccessApprovalPolicyServiceFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
accessApprovalPolicyBypasserDAL: TAccessApprovalPolicyBypasserDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
groupDAL: TGroupDALFactory;
userDAL: Pick<TUserDALFactory, "find">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find" | "resetReviewByPolicyId">;
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">;
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory;
membershipUserDAL: TMembershipUserDALFactory;
};
export const accessApprovalPolicyServiceFactory = ({
@@ -62,7 +60,7 @@ export const accessApprovalPolicyServiceFactory = ({
accessApprovalRequestDAL,
additionalPrivilegeDAL,
accessApprovalRequestReviewerDAL,
orgMembershipDAL
membershipUserDAL
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
const $policyExists = async ({
envId,
@@ -424,13 +422,14 @@ export const accessApprovalPolicyServiceFactory = ({
// Validate user bypassers
if (bypasserUserIds.length > 0) {
const orgMemberships = await orgMembershipDAL.find({
$in: { userId: bypasserUserIds },
orgId: actorOrgId
const orgMemberships = await membershipUserDAL.find({
$in: { actorUserId: bypasserUserIds },
scopeOrgId: actorOrgId,
scope: AccessScope.Organization
});
if (orgMemberships.length !== bypasserUserIds.length) {
const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.userId));
const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.actorUserId as string));
const missingUserIds = bypasserUserIds.filter((id) => !foundUserIdsInOrg.has(id));
throw new BadRequestError({
message: `One or more specified bypasser users are not part of the organization or do not exist. Invalid or non-member user IDs: ${missingUserIds.join(", ")}`
@@ -633,7 +632,7 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission({
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
@@ -641,9 +640,6 @@ export const accessApprovalPolicyServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
@@ -3,9 +3,10 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import {
AccessApprovalRequestsSchema,
AccessScope,
TableName,
TAccessApprovalRequests,
TOrgMemberships,
TMemberships,
TUserGroupMembership,
TUsers
} from "@app/db/schemas";
@@ -244,11 +245,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
const docs = await db
.replicaNode()(TableName.AccessApprovalRequest)
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
TableName.AdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
`${TableName.AdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalPolicy,
@@ -276,7 +276,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
`${TableName.UserGroupMembership}.groupId`
)
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
@@ -294,24 +293,24 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
`requestedByUser.id`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("approverOrgMembership"),
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
`approverOrgMembership.userId`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
`${TableName.Users}.id`,
`approverGroupOrgMembership.userId`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("reviewerOrgMembership"),
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
`reviewerOrgMembership.userId`
)
.leftJoin<TMemberships>(db(TableName.Membership).as("approverOrgMembership"), (qb) => {
qb.on(
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
`approverOrgMembership.actorUserId`
).andOn(`approverOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin<TMemberships>(db(TableName.Membership).as("approverGroupOrgMembership"), (qb) => {
qb.on(`${TableName.Users}.id`, `approverGroupOrgMembership.actorUserId`).andOn(
`approverGroupOrgMembership.scope`,
db.raw("?", [AccessScope.Organization])
);
})
.leftJoin<TMemberships>(db(TableName.Membership).as("reviewerOrgMembership"), (qb) => {
qb.on(
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
`reviewerOrgMembership.actorUserId`
).andOn(`reviewerOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
@@ -360,22 +359,22 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
db.ref("actorUserId").withSchema(TableName.AdditionalPrivilege).as("privilegeUserId"),
db.ref("projectId").withSchema(TableName.AdditionalPrivilege).as("privilegeMembershipId"),
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
db.ref("isTemporary").withSchema(TableName.AdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.AdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.AdditionalPrivilege).as("privilegeTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.withSchema(TableName.AdditionalPrivilege)
.as("privilegeTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.withSchema(TableName.AdditionalPrivilege)
.as("privilegeTemporaryAccessEndTime"),
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
db.ref("permissions").withSchema(TableName.AdditionalPrivilege).as("privilegePermissions")
)
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
@@ -408,7 +407,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
privilege: doc.privilegeId
? {
membershipId: doc.privilegeMembershipId,
userId: doc.privilegeUserId,
userId: doc.privilegeUserId || "",
projectId: doc.projectId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
@@ -773,9 +772,9 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
TableName.AdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
`${TableName.AdditionalPrivilege}.id`
)
.leftJoin(
@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
import msFn from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, TemporaryPermissionMode } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@@ -10,12 +10,12 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { EnforcementLevel } from "@app/lib/types";
import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification";
import { TriggerFeature } from "@app/lib/workflow-integrations/types";
import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
import { TProjectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
@@ -26,15 +26,13 @@ import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-poli
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
import { TGroupDALFactory } from "../group/group-dal";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
import { verifyRequestedPermissions } from "./access-approval-request-fns";
import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal";
import { ApprovalStatus, TAccessApprovalRequestServiceFactory } from "./access-approval-request-types";
type TSecretApprovalRequestServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "create" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
@@ -59,7 +57,6 @@ type TSecretApprovalRequestServiceFactoryDep = {
"create" | "find" | "findOne" | "transaction" | "delete"
>;
groupDAL: Pick<TGroupDALFactory, "findAllGroupPossibleMembers">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<
TUserDALFactory,
@@ -125,7 +122,7 @@ export const accessApprovalRequestServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone can create an access approval request.
const { membership } = await permissionService.getProjectPermission({
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
@@ -133,9 +130,6 @@ export const accessApprovalRequestServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const requestedByUser = await userDAL.findById(actorId);
if (!requestedByUser) throw new ForbiddenRequestError({ message: "User not found" });
@@ -340,7 +334,7 @@ export const accessApprovalRequestServiceFactory = ({
});
}
const { membership, hasRole } = await permissionService.getProjectPermission({
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: accessApprovalRequest.projectId,
@@ -349,10 +343,6 @@ export const accessApprovalRequestServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
if (!hasRole(ProjectMembershipRole.Admin) && !isApprover) {
@@ -496,7 +486,7 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission({
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
@@ -504,9 +494,6 @@ export const accessApprovalRequestServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
@@ -566,7 +553,7 @@ export const accessApprovalRequestServiceFactory = ({
slug: permissionEnvironment
});
const { membership, hasRole } = await permissionService.getProjectPermission({
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: accessApprovalRequest.projectId,
@@ -575,10 +562,6 @@ export const accessApprovalRequestServiceFactory = ({
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const isSelfApproval = actorId === accessApprovalRequest.requestedByUserId;
const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft;
const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId);
@@ -724,9 +707,9 @@ export const accessApprovalRequestServiceFactory = ({
// Permanent access
const privilege = await additionalPrivilegeDAL.create(
{
userId: accessApprovalRequest.requestedByUserId,
actorUserId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
name: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
tx
@@ -739,12 +722,12 @@ export const accessApprovalRequestServiceFactory = ({
const privilege = await additionalPrivilegeDAL.create(
{
userId: accessApprovalRequest.requestedByUserId,
actorUserId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
name: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true, // Explicitly set to true for the privilege
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryMode: TemporaryPermissionMode.Relative,
temporaryRange: accessApprovalRequest.temporaryRange!,
temporaryAccessStartTime: startTime,
temporaryAccessEndTime: new Date(startTime.getTime() + relativeTempAllocatedTimeInMs)
@@ -830,7 +813,7 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission({
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
@@ -838,9 +821,6 @@ export const accessApprovalRequestServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id, policyId });
@@ -16,11 +16,6 @@ export const gatewayV2DalFactory = (db: TDbClient) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter, TableName.GatewayV2))
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.GatewayV2}.identityId`)
.join(
TableName.IdentityOrgMembership,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.GatewayV2}.identityId`
)
.select(selectAllTableCols(TableName.GatewayV2))
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
+7 -10
View File
@@ -1,5 +1,5 @@
import { TDbClient } from "@app/db";
import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
import { AccessScope, GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
@@ -17,17 +17,14 @@ export const gatewayDALFactory = (db: TDbClient) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter, TableName.Gateway, ["orgId"]))
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
.join(
TableName.IdentityOrgMembership,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.Gateway}.identityId`
)
.join(TableName.Membership, `${TableName.Membership}.actorIdentityId`, `${TableName.Gateway}.identityId`)
.select(selectAllTableCols(TableName.Gateway))
.select(db.ref("orgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId"))
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
.select(db.ref("scopeOrgId").withSchema(TableName.Membership).as("identityOrgId"))
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"))
.where(`${TableName.Membership}.scope`, AccessScope.Organization);
if (filter.orgId) {
void query.where(`${TableName.IdentityOrgMembership}.orgId`, filter.orgId);
void query.where(`${TableName.Membership}.scopeOrgId`, filter.orgId);
}
if (limit) void query.limit(limit);
if (offset) void query.offset(offset);
@@ -39,7 +36,7 @@ export const gatewayDALFactory = (db: TDbClient) => {
return docs.map((el) => ({
...GatewaysSchema.parse(el),
orgId: el.identityOrgId as string, // todo(daniel): figure out why typescript is not inferring this as a string
orgId: el.identityOrgId,
identity: { id: el.identityId, name: el.identityName }
}));
} catch (error) {
@@ -6,13 +6,15 @@ import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
import { Octokit as OctokitRest } from "@octokit/rest";
import RE2 from "re2";
import { OrgMembershipRole } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { retryWithBackoff } from "@app/lib/retry";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TGroupDALFactory } from "../group/group-dal";
@@ -77,11 +79,10 @@ type TGithubOrgSyncServiceFactoryDep = {
"findGroupMembershipsByUserIdInOrg" | "findGroupMembershipsByGroupIdInOrg" | "insertMany" | "delete"
>;
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "insertMany">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgMembershipDAL: Pick<
TOrgMembershipDALFactory,
"find" | "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId">;
};
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
@@ -93,7 +94,9 @@ export const githubOrgSyncServiceFactory = ({
userGroupMembershipDAL,
groupDAL,
licenseService,
orgMembershipDAL
orgMembershipDAL,
membershipRoleDAL,
membershipGroupDAL
}: TGithubOrgSyncServiceFactoryDep) => {
const createGithubOrgSync = async ({
githubOrgName,
@@ -368,6 +371,23 @@ export const githubOrgSyncServiceFactory = ({
})),
tx
);
const memberships = await membershipGroupDAL.insertMany(
newGroups.map((el) => ({
actorGroupId: el.id,
scope: AccessScope.Organization,
scopeOrgId: orgId
})),
tx
);
await membershipRoleDAL.insertMany(
memberships.map((el) => ({
membershipId: el.id,
role: OrgMembershipRole.Member
})),
tx
);
await userGroupMembershipDAL.insertMany(
newGroups.map((el) => ({
groupId: el.id,
@@ -694,6 +714,23 @@ export const githubOrgSyncServiceFactory = ({
tx
);
const memberships = await membershipGroupDAL.insertMany(
newGroups.map((el) => ({
actorGroupId: el.id,
scope: AccessScope.Organization,
scopeOrgId: orgPermission.orgId
})),
tx
);
await membershipRoleDAL.insertMany(
memberships.map((el) => ({
membershipId: el.id,
role: OrgMembershipRole.Member
})),
tx
);
newGroups.forEach((group) => {
if (!existingTeamsMap[group.name]) {
existingTeamsMap[group.name] = [];
+56 -16
View File
@@ -1,7 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TGroups } from "@app/db/schemas";
import { AccessScope, TableName, TGroups } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
@@ -36,14 +36,21 @@ export const groupDALFactory = (db: TDbClient) => {
try {
const docs = await (tx || db.replicaNode())(TableName.Groups)
.where(`${TableName.Groups}.orgId`, orgId)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.join(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`)
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
.select(selectAllTableCols(TableName.Groups))
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
.select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles))
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles));
.select(db.ref("id").as("crId").withSchema(TableName.Role))
.select(db.ref("name").as("crName").withSchema(TableName.Role))
.select(db.ref("role").withSchema(TableName.MembershipRole))
.select(db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole))
.select(db.ref("slug").as("crSlug").withSchema(TableName.Role))
.select(db.ref("description").as("crDescription").withSchema(TableName.Role))
.select(db.ref("permissions").as("crPermission").withSchema(TableName.Role));
return docs.map(({ crId, crDescription, crSlug, crPermission, crName, ...el }) => ({
...el,
customRole: el.roleId
@@ -81,9 +88,11 @@ export const groupDALFactory = (db: TDbClient) => {
}) => {
try {
const query = db
.replicaNode()(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.replicaNode()(TableName.Membership)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.leftJoin(TableName.UserGroupMembership, (bd) => {
bd.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
`${TableName.UserGroupMembership}.groupId`,
@@ -92,7 +101,7 @@ export const groupDALFactory = (db: TDbClient) => {
);
})
.select(
db.ref("id").withSchema(TableName.OrgMembership),
db.ref("id").withSchema(TableName.Membership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("createdAt").withSchema(TableName.UserGroupMembership).as("joinedGroupAt"),
db.ref("email").withSchema(TableName.Users),
@@ -160,8 +169,10 @@ export const groupDALFactory = (db: TDbClient) => {
const findGroupsByProjectId = async (projectId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.Groups)
.join(TableName.GroupProjectMembership, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.join(TableName.Membership, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
.where(`${TableName.Membership}.scopeProjectId`, projectId)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.whereNotNull(`${TableName.Membership}.actorGroupId`)
.select(selectAllTableCols(TableName.Groups));
return docs;
} catch (error) {
@@ -172,11 +183,16 @@ export const groupDALFactory = (db: TDbClient) => {
const findById = async (id: string, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.Groups)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.join(TableName.Membership, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
.where(`${TableName.Groups}.id`, id)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.select(
selectAllTableCols(TableName.Groups),
db.ref("slug").as("customRoleSlug").withSchema(TableName.OrgRoles)
db.ref("slug").as("customRoleSlug").withSchema(TableName.Role),
db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole),
db.ref("role").withSchema(TableName.MembershipRole)
)
.first();
@@ -186,12 +202,36 @@ export const groupDALFactory = (db: TDbClient) => {
}
};
const findOne = async (filter: Partial<TGroups>, tx?: Knex): Promise<TGroups | undefined> => {
try {
const doc = await (tx || db.replicaNode())(TableName.Groups)
.join(TableName.Membership, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where((queryBuilder) => {
Object.entries(filter).forEach(([key, value]) => {
void queryBuilder.where(`${TableName.Groups}.${key}`, value);
});
})
.select(
selectAllTableCols(TableName.Groups),
db.ref("role").withSchema(TableName.MembershipRole),
db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole)
)
.first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "Find one" });
}
};
return {
...groupOrm,
findGroups,
findByOrgId,
findAllGroupPossibleMembers,
findGroupsByProjectId,
findById
findById,
findOne
};
};
+26 -19
View File
@@ -1,6 +1,6 @@
import { Knex } from "knex";
import { ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { AccessScope, ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
@@ -16,7 +16,7 @@ const addAcceptedUsersToGroup = async ({
group,
userGroupMembershipDAL,
userDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@@ -42,13 +42,15 @@ const addAcceptedUsersToGroup = async ({
const projectIds = Array.from(
new Set(
(
await groupProjectDAL.find(
await membershipGroupDAL.find(
{
groupId: group.id
actorGroupId: group.id,
scopeOrgId: group.orgId,
scope: AccessScope.Project
},
{ tx }
)
).map((gp) => gp.projectId)
).map((gp) => gp.scopeProjectId as string)
)
);
@@ -167,11 +169,11 @@ export const addUsersToGroupByUserIds = async ({
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
tx: outerTx
tx: outerTx,
membershipGroupDAL
}: TAddUsersToGroupByUserIds) => {
const processAddition = async (tx: Knex) => {
const foundMembers = await userDAL.find(
@@ -214,15 +216,18 @@ export const addUsersToGroupByUserIds = async ({
// check if all user(s) are part of the organization
const existingUserOrgMemberships = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: group.orgId,
scope: AccessScope.Organization,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: userIds
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userIds
}
},
{ tx }
);
const existingUserOrgMembershipsUserIdsSet = new Set(existingUserOrgMemberships.map((u) => u.userId));
const existingUserOrgMembershipsUserIdsSet = new Set(
existingUserOrgMemberships.map((u) => u.actorUserId as string)
);
userIds.forEach((userId) => {
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
@@ -250,7 +255,7 @@ export const addUsersToGroupByUserIds = async ({
group,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@@ -292,9 +297,9 @@ export const removeUsersFromGroupByUserIds = async ({
userIds,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
tx: outerTx
tx: outerTx,
membershipGroupDAL
}: TRemoveUsersFromGroupByUserIds) => {
const processRemoval = async (tx: Knex) => {
const foundMembers = await userDAL.find({
@@ -352,13 +357,15 @@ export const removeUsersFromGroupByUserIds = async ({
const projectIds = Array.from(
new Set(
(
await groupProjectDAL.find(
await membershipGroupDAL.find(
{
groupId: group.id
scope: AccessScope.Project,
actorGroupId: group.id,
scopeOrgId: group.orgId
},
{ tx }
)
).map((gp) => gp.projectId)
).map((gp) => gp.scopeProjectId as string)
)
);
@@ -422,11 +429,11 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
userIds,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
tx: outerTx
tx: outerTx,
membershipGroupDAL
}: TConvertPendingGroupAdditionsToGroupMemberships) => {
const processConversion = async (tx: Knex) => {
const users = await userDAL.find(
@@ -463,7 +470,7 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
group: pendingGroupAddition.group,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
+94 -81
View File
@@ -1,11 +1,12 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole, TRoles } from "@app/db/schemas";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
@@ -35,8 +36,9 @@ type TGroupServiceFactoryDep = {
TGroupDALFactory,
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find" | "findOne" | "create">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "delete">;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers" | "findById">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
@@ -46,7 +48,7 @@ type TGroupServiceFactoryDep = {
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
permissionService: Pick<
TPermissionServiceFactory,
"getOrgPermission" | "getOrgPermissionByRole" | "invalidateProjectPermissionCache"
"getOrgPermission" | "getOrgPermissionByRoles" | "invalidateProjectPermissionCache"
>;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
@@ -57,7 +59,6 @@ export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
export const groupServiceFactory = ({
userDAL,
groupDAL,
groupProjectDAL,
orgDAL,
userGroupMembershipDAL,
projectDAL,
@@ -65,12 +66,14 @@ export const groupServiceFactory = ({
projectKeyDAL,
permissionService,
licenseService,
oidcConfigDAL
oidcConfigDAL,
membershipGroupDAL,
membershipRoleDAL
}: TGroupServiceFactoryDep) => {
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission, membership } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
@@ -85,25 +88,23 @@ export const groupServiceFactory = ({
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
});
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
role,
actorOrgId
);
const isCustomRole = Boolean(customRole);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], actorOrgId);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
const isCustomRole = Boolean(rolePermissionDetails?.role);
if (role !== OrgMembershipRole.NoAccess) {
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.GrantPrivileges,
OrgPermissionSubjects.Groups,
permission,
rolePermission
rolePermissionDetails.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create group",
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.GrantPrivileges,
OrgPermissionSubjects.Groups
),
@@ -125,7 +126,25 @@ export const groupServiceFactory = ({
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
orgId: actorOrgId,
role: isCustomRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id
roleId: null
},
tx
);
const membership = await membershipGroupDAL.create(
{
actorGroupId: newGroup.id,
scope: AccessScope.Organization,
scopeOrgId: actorOrgId
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role: isCustomRole ? OrgMembershipRole.Custom : role,
customRoleId: rolePermissionDetails?.role?.id
},
tx
);
@@ -148,7 +167,7 @@ export const groupServiceFactory = ({
}: TUpdateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission, membership } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
@@ -169,32 +188,31 @@ export const groupServiceFactory = ({
throw new NotFoundError({ message: `Failed to find group with ID ${id}` });
}
let customRole: TOrgRoles | undefined;
let customRole: TRoles | undefined;
if (role) {
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
role,
group.orgId
);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], group.orgId);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
const isCustomRole = Boolean(rolePermissionDetails?.role);
const isCustomRole = Boolean(customOrgRole);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.GrantPrivileges,
OrgPermissionSubjects.Groups,
permission,
rolePermission
rolePermissionDetails.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update group",
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.GrantPrivileges,
OrgPermissionSubjects.Groups
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
if (isCustomRole) customRole = customOrgRole;
if (isCustomRole) customRole = rolePermissionDetails?.role;
}
const updatedGroup = await groupDAL.transaction(async (tx) => {
@@ -208,35 +226,44 @@ export const groupServiceFactory = ({
}
}
const [updated] = await groupDAL.update(
{
id: group.id
},
{
name,
slug: slug ? slugify(slug) : undefined,
...(role
? {
role: customRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id ?? null
}
: {})
},
tx
);
let updated = group;
if (name || slug) {
[updated] = await groupDAL.update(
{
id: group.id
},
{
name,
slug: slug ? slugify(slug) : undefined
},
tx
);
}
if (role) {
const membership = await membershipGroupDAL.findOne(
{
scope: AccessScope.Organization,
actorGroupId: updated.id,
scopeOrgId: updated.orgId
},
tx
);
await membershipRoleDAL.delete({ membershipId: membership.id }, tx);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role: customRole ? OrgMembershipRole.Custom : role,
customRoleId: customRole?.id ?? null
},
tx
);
}
return updated;
});
if (role) {
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
await Promise.allSettled([
...groupProjects.map((groupProject) =>
permissionService.invalidateProjectPermissionCache(groupProject.projectId)
)
]);
}
return updatedGroup;
};
@@ -259,17 +286,11 @@ export const groupServiceFactory = ({
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
});
const groupProjects = await groupProjectDAL.find({ groupId: id });
const [group] = await groupDAL.delete({
id,
orgId: actorOrgId
});
await Promise.allSettled([
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
]);
return group;
};
@@ -344,7 +365,7 @@ export const groupServiceFactory = ({
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission, membership } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
@@ -376,22 +397,23 @@ export const groupServiceFactory = ({
});
}
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
// check if user has broader or equal to privileges than group
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.AddMembers,
OrgPermissionSubjects.Groups,
permission,
groupRolePermission
rolePermissionDetails.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to add user to more privileged group",
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.AddMembers,
OrgPermissionSubjects.Groups
),
@@ -410,17 +432,12 @@ export const groupServiceFactory = ({
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
projectDAL,
projectBotDAL
});
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
await Promise.allSettled([
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
]);
return users[0];
};
@@ -434,7 +451,7 @@ export const groupServiceFactory = ({
}: TRemoveUserFromGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission, membership } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
@@ -466,21 +483,22 @@ export const groupServiceFactory = ({
});
}
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
// check if user has broader or equal to privileges than group
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.RemoveMembers,
OrgPermissionSubjects.Groups,
permission,
groupRolePermission
rolePermissionDetails.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to delete user from more privileged group",
membership.shouldUseNewPrivilegeSystem,
shouldUseNewPrivilegeSystem,
OrgPermissionGroupActions.RemoveMembers,
OrgPermissionSubjects.Groups
),
@@ -498,15 +516,10 @@ export const groupServiceFactory = ({
userIds: [user.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL
});
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
await Promise.allSettled([
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
]);
return users[0];
};
+5 -5
View File
@@ -3,7 +3,7 @@ import { Knex } from "knex";
import { TGroups } from "@app/db/schemas";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TGenericPermission } from "@app/lib/types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
@@ -63,7 +63,7 @@ export type TAddUsersToGroup = {
group: TGroups;
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserIdsBatch">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -76,7 +76,7 @@ export type TAddUsersToGroupByUserIds = {
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
orgDAL: Pick<TOrgDALFactory, "findMembership">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -88,7 +88,7 @@ export type TRemoveUsersFromGroupByUserIds = {
userIds: string[];
userDAL: Pick<TUserDALFactory, "find" | "transaction">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "filterProjectsByUserMembership" | "delete">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "delete">;
tx?: Knex;
};
@@ -100,7 +100,7 @@ export type TConvertPendingGroupAdditionsToGroupMemberships = {
TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "deletePendingUserGroupMembershipsByUserIds"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -1,7 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { AccessScope, TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
@@ -18,21 +18,19 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
*/
const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[], tx?: Knex) => {
try {
const userProjectMemberships: string[] = await (tx || db.replicaNode())(TableName.ProjectMembership)
.where(`${TableName.ProjectMembership}.userId`, userId)
.whereIn(`${TableName.ProjectMembership}.projectId`, projectIds)
.pluck(`${TableName.ProjectMembership}.projectId`);
const userProjectMemberships: string[] = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.actorUserId`, userId)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.whereIn(`${TableName.Membership}.scopeProjectId`, projectIds)
.pluck(`${TableName.Membership}.scopeProjectId`);
const userGroupMemberships: string[] = await (tx || db.replicaNode())(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.whereNot(`${TableName.UserGroupMembership}.groupId`, groupId)
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId`
)
.whereIn(`${TableName.GroupProjectMembership}.projectId`, projectIds)
.pluck(`${TableName.GroupProjectMembership}.projectId`);
.join(TableName.Membership, `${TableName.UserGroupMembership}.groupId`, `${TableName.Membership}.actorGroupId`)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.whereIn(`${TableName.Membership}.scopeProjectId`, projectIds)
.pluck(`${TableName.Membership}.scopeProjectId`);
return new Set(userProjectMemberships.concat(userGroupMemberships));
} catch (error) {
@@ -44,13 +42,10 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string, tx?: Knex) => {
try {
const usernameDocs: string[] = await (tx || db.replicaNode())(TableName.UserGroupMembership)
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId`
)
.join(TableName.Membership, `${TableName.UserGroupMembership}.groupId`, `${TableName.Membership}.actorGroupId`)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.where(`${TableName.Membership}.scopeProjectId`, projectId)
.whereIn(`${TableName.Users}.username`, usernames)
.pluck(`${TableName.Users}.id`);
@@ -73,24 +68,25 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
try {
// get list of groups in the project with id [projectId]
// that that are not the group with id [groupId]
const groups: string[] = await (tx || db.replicaNode())(TableName.GroupProjectMembership)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.whereNot(`${TableName.GroupProjectMembership}.groupId`, groupId)
.pluck(`${TableName.GroupProjectMembership}.groupId`);
const groups: string[] = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.scopeProjectId`, projectId)
.whereNot(`${TableName.Membership}.actorGroupId`, groupId)
.pluck(`${TableName.Membership}.actorGroupId`);
// main query
const members = await (tx || db.replicaNode())(TableName.UserGroupMembership)
.where(`${TableName.UserGroupMembership}.groupId`, groupId)
.where(`${TableName.UserGroupMembership}.isPending`, false)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.ProjectMembership, (bd) => {
bd.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn(
`${TableName.ProjectMembership}.projectId`,
.leftJoin(TableName.Membership, (bd) => {
bd.on(`${TableName.Users}.id`, "=", `${TableName.Membership}.actorUserId`).andOn(
`${TableName.Membership}.scopeProjectId`,
"=",
db.raw("?", [projectId])
);
})
.whereNull(`${TableName.ProjectMembership}.userId`)
.whereNull(`${TableName.Membership}.actorUserId`)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.leftJoin<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
@@ -166,15 +162,17 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Membership, `${TableName.UserGroupMembership}.userId`, `${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("id").withSchema(TableName.Membership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName"),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
@@ -191,15 +189,17 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Membership, `${TableName.UserGroupMembership}.userId`, `${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Groups}.id`, groupId)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("id").withSchema(TableName.Membership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName")
);
@@ -1,12 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityProjectAdditionalPrivilegeV2DALFactory = ReturnType<
typeof identityProjectAdditionalPrivilegeV2DALFactory
>;
export const identityProjectAdditionalPrivilegeV2DALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
return orm;
};
@@ -1,434 +0,0 @@
import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "../permission/project-permission";
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
import {
IdentityProjectAdditionalPrivilegeTemporaryMode,
TCreateIdentityPrivilegeDTO,
TDeleteIdentityPrivilegeByIdDTO,
TGetIdentityPrivilegeDetailsByIdDTO,
TGetIdentityPrivilegeDetailsBySlugDTO,
TListIdentityPrivilegesDTO,
TUpdateIdentityPrivilegeByIdDTO
} from "./identity-project-additional-privilege-v2-types";
type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
};
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
typeof identityProjectAdditionalPrivilegeV2ServiceFactory
>;
export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
identityProjectAdditionalPrivilegeDAL,
identityProjectDAL,
projectDAL,
permissionService
}: TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep) => {
const create = async ({
slug,
actor,
actorId,
projectId,
actorOrgId,
identityId,
permissions: customPermission,
actorAuthMethod,
...dto
}: TCreateIdentityPrivilegeDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission, membership } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
const packedPermission = JSON.stringify(packRules(customPermission));
if (!dto.isTemporary) {
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
projectMembershipId: identityProjectMembership.id,
slug,
permissions: packedPermission
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
projectMembershipId: identityProjectMembership.id,
slug,
permissions: packedPermission,
isTemporary: true,
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: dto.temporaryRange,
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const updateById = async ({
id,
data,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TUpdateIdentityPrivilegeByIdDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
if (!identityProjectMembership)
throw new NotFoundError({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: targetIdentityPermission, membership } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
if (data?.slug) {
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug: data.slug,
projectMembershipId: identityProjectMembership.id
});
if (existingSlug && existingSlug.id !== identityPrivilege.id)
throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
}
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
if (isTemporary) {
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
slug: data.slug,
permissions: packedPermission,
isTemporary: data.isTemporary,
temporaryRange: data.temporaryRange,
temporaryMode: data.temporaryMode,
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
slug: data.slug,
permissions: packedPermission,
isTemporary: false,
temporaryAccessStartTime: null,
temporaryAccessEndTime: null,
temporaryRange: null,
temporaryMode: null
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const deleteById = async ({ actorId, id, actor, actorOrgId, actorAuthMethod }: TDeleteIdentityPrivilegeByIdDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
if (!identityProjectMembership)
throw new NotFoundError({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
identityRolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...deletedPrivilege,
permissions: unpackPermissions(deletedPrivilege.permissions)
};
};
const getPrivilegeDetailsById = async ({
id,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetIdentityPrivilegeDetailsByIdDTO) => {
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
if (!identityProjectMembership)
throw new NotFoundError({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
return {
...identityPrivilege,
permissions: unpackPermissions(identityPrivilege.permissions)
};
};
const getPrivilegeDetailsBySlug = async ({
identityId,
slug,
projectSlug,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetIdentityPrivilegeDetailsBySlugDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
return {
...identityPrivilege,
permissions: unpackPermissions(identityPrivilege.permissions)
};
};
const listIdentityProjectPrivileges = async ({
identityId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
projectId
}: TListIdentityPrivilegesDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
{
projectMembershipId: identityProjectMembership.id
},
{ sort: [[`${TableName.IdentityProjectAdditionalPrivilege}.slug` as "slug", "asc"]] }
);
return identityPrivileges;
};
return {
getPrivilegeDetailsById,
getPrivilegeDetailsBySlug,
listIdentityProjectPrivileges,
create,
updateById,
deleteById
};
};
@@ -1,55 +0,0 @@
import { TProjectPermission } from "@app/lib/types";
import { TProjectPermissionV2Schema } from "../permission/project-permission";
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
Relative = "relative"
}
export type TCreateIdentityPrivilegeDTO = {
permissions: TProjectPermissionV2Schema[];
identityId: string;
projectId: string;
slug: string;
} & (
| {
isTemporary: false;
}
| {
isTemporary: true;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}
) &
Omit<TProjectPermission, "projectId">;
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
data: Partial<{
permissions: TProjectPermissionV2Schema[];
slug: string;
isTemporary: boolean;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}>;
};
export type TDeleteIdentityPrivilegeByIdDTO = Omit<TProjectPermission, "projectId"> & {
id: string;
};
export type TGetIdentityPrivilegeDetailsByIdDTO = Omit<TProjectPermission, "projectId"> & {
id: string;
};
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
identityId: string;
projectId: string;
};
export type TGetIdentityPrivilegeDetailsBySlugDTO = Omit<TProjectPermission, "projectId"> & {
slug: string;
identityId: string;
projectSlug: string;
};
@@ -1,12 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
typeof identityProjectAdditionalPrivilegeDALFactory
>;
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
return orm;
};
@@ -1,451 +0,0 @@
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import {
ProjectPermissionIdentityActions,
ProjectPermissionSet,
ProjectPermissionSub
} from "../permission/project-permission";
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
import {
IdentityProjectAdditionalPrivilegeTemporaryMode,
TCreateIdentityPrivilegeDTO,
TDeleteIdentityPrivilegeDTO,
TGetIdentityPrivilegeDetailsDTO,
TListIdentityPrivilegesDTO,
TUpdateIdentityPrivilegeDTO
} from "./identity-project-additional-privilege-types";
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
};
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
typeof identityProjectAdditionalPrivilegeServiceFactory
>;
const unpackPermissions = (permissions: unknown) =>
UnpackedPermissionSchema.array().parse(
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
);
export const identityProjectAdditionalPrivilegeServiceFactory = ({
identityProjectAdditionalPrivilegeDAL,
identityProjectDAL,
permissionService,
projectDAL
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({
slug,
actor,
actorId,
identityId,
projectSlug,
permissions: customPermission,
actorOrgId,
actorAuthMethod,
...dto
}: TCreateIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const packedPermission = JSON.stringify(packRules(customPermission));
if (!dto.isTemporary) {
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
projectMembershipId: identityProjectMembership.id,
slug,
permissions: packedPermission
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
projectMembershipId: identityProjectMembership.id,
slug,
permissions: packedPermission,
isTemporary: true,
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: dto.temporaryRange,
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const updateBySlug = async ({
projectSlug,
slug,
identityId,
data,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TUpdateIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
targetIdentityPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
if (data?.slug) {
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug: data.slug,
projectMembershipId: identityProjectMembership.id
});
if (existingSlug && existingSlug.id !== identityPrivilege.id)
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
}
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
if (isTemporary) {
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
slug: data.slug,
permissions: packedPermission,
isTemporary: data.isTemporary,
temporaryRange: data.temporaryRange,
temporaryMode: data.temporaryMode,
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
slug: data.slug,
permissions: packedPermission,
isTemporary: false,
temporaryAccessStartTime: null,
temporaryAccessEndTime: null,
temporaryRange: null,
temporaryMode: null
});
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const deleteBySlug = async ({
actorId,
slug,
identityId,
projectSlug,
actor,
actorOrgId,
actorAuthMethod
}: TDeleteIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
identityRolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to edit more privileged identity",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
return {
...deletedPrivilege,
permissions: unpackPermissions(deletedPrivilege.permissions)
};
};
const getPrivilegeDetailsBySlug = async ({
projectSlug,
identityId,
slug,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetIdentityPrivilegeDetailsDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
);
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
return {
...identityPrivilege,
permissions: unpackPermissions(identityPrivilege.permissions)
};
};
const listIdentityProjectPrivileges = async ({
identityId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
projectSlug
}: TListIdentityPrivilegesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
);
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
projectMembershipId: identityProjectMembership.id
});
return identityPrivileges.map((el) => ({
...el,
permissions: unpackPermissions(el.permissions)
}));
};
return {
create,
updateBySlug,
deleteBySlug,
getPrivilegeDetailsBySlug,
listIdentityProjectPrivileges
};
};
@@ -1,56 +0,0 @@
import { TProjectPermission } from "@app/lib/types";
import { TProjectPermissionV2Schema } from "../permission/project-permission";
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
Relative = "relative"
}
export type TCreateIdentityPrivilegeDTO = {
permissions: TProjectPermissionV2Schema[];
identityId: string;
projectSlug: string;
slug: string;
} & (
| {
isTemporary: false;
}
| {
isTemporary: true;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}
) &
Omit<TProjectPermission, "projectId">;
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
TProjectPermission,
"projectId"
> & {
data: Partial<{
permissions: TProjectPermissionV2Schema[];
slug: string;
isTemporary: boolean;
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}>;
};
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
slug: string;
identityId: string;
projectSlug: string;
};
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
slug: string;
identityId: string;
projectSlug: string;
};
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
identityId: string;
projectSlug: string;
};
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { Knex } from "knex";
import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
import { AccessScope, OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@@ -12,12 +12,12 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
@@ -49,13 +49,13 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
type TLdapConfigServiceFactoryDep = {
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne" | "transaction">;
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -87,9 +87,9 @@ export const ldapConfigServiceFactory = ({
ldapConfigDAL,
ldapGroupMapDAL,
orgDAL,
orgMembershipDAL,
groupDAL,
groupProjectDAL,
membershipGroupDAL,
membershipRoleDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@@ -388,25 +388,33 @@ export const ldapConfigServiceFactory = ({
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgDAL.createMembership(
const membership = await orgDAL.createMembership(
{
userId: userAlias.userId,
orgId,
role,
roleId,
actorUserId: userAlias.userId,
scopeOrgId: orgId,
scope: AccessScope.Organization,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
await orgDAL.updateMembershipById(
orgMembership.id,
@@ -459,8 +467,9 @@ export const ldapConfigServiceFactory = ({
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: newUserAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
@@ -469,16 +478,22 @@ export const ldapConfigServiceFactory = ({
await throwOnPlanSeatLimitReached(licenseService, orgId, UserAliasType.LDAP);
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
const membership = await orgDAL.createMembership(
{
userId: newUser.id,
inviteEmail: email.toLowerCase(),
orgId,
role,
roleId,
actorUserId: newUser.id,
scopeOrgId: orgId,
scope: AccessScope.Organization,
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
isActive: true,
inviteEmail: email.toLowerCase()
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
@@ -542,10 +557,10 @@ export const ldapConfigServiceFactory = ({
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx
});
}
@@ -566,7 +581,7 @@ export const ldapConfigServiceFactory = ({
userIds: [newUser.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
tx
});
+14 -11
View File
@@ -1,7 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
import { AccessScope, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
export type TLicenseDALFactory = ReturnType<typeof licenseDALFactory>;
@@ -9,14 +9,14 @@ export type TLicenseDALFactory = ReturnType<typeof licenseDALFactory>;
export const licenseDALFactory = (db: TDbClient) => {
const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.OrgMembership)
.where({ status: OrgMembershipStatus.Accepted })
const doc = await (tx || db.replicaNode())(TableName.Membership)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId);
}
})
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false)
.count();
return Number(doc?.[0]?.count ?? 0);
@@ -28,24 +28,27 @@ export const licenseDALFactory = (db: TDbClient) => {
const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => {
try {
// count org users
const userDoc = await (tx || db.replicaNode())(TableName.OrgMembership)
.where({ status: OrgMembershipStatus.Accepted })
const userDoc = await (tx || db.replicaNode())(TableName.Membership)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.whereNotNull(`${TableName.Membership}.actorUserId`)
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId);
}
})
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false)
.count();
const userCount = Number(userDoc?.[0].count);
// count org identities
const identityDoc = await (tx || db.replicaNode())(TableName.IdentityOrgMembership)
const identityDoc = await (tx || db.replicaNode())(TableName.Membership)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where((bd) => {
if (orgId) {
void bd.where({ orgId });
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId);
}
})
.count();
@@ -488,7 +488,7 @@ export const licenseServiceFactory = ({
const getUsageMetrics = async (orgId: string) => {
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
orgDAL.countAllOrgMembers(orgId),
identityOrgMembershipDAL.countAllOrgIdentities({ orgId }),
identityOrgMembershipDAL.countAllOrgIdentities({ scopeOrgId: orgId }),
projectDAL.countOfOrgProjects(orgId)
]);
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability";
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
import { AccessScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
@@ -19,12 +19,12 @@ import { OrgServiceActor } from "@app/lib/types";
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
@@ -62,11 +62,12 @@ type TOidcConfigServiceFactoryDep = {
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getUserOrgPermission">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
groupDAL: Pick<TGroupDALFactory, "findByOrgId">;
userGroupMembershipDAL: Pick<
@@ -78,7 +79,6 @@ type TOidcConfigServiceFactoryDep = {
| "delete"
| "filterProjectsByUserMembership"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@@ -90,7 +90,6 @@ export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFacto
export const oidcConfigServiceFactory = ({
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
licenseService,
@@ -100,7 +99,8 @@ export const oidcConfigServiceFactory = ({
oidcConfigDAL,
userGroupMembershipDAL,
groupDAL,
groupProjectDAL,
membershipGroupDAL,
membershipRoleDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@@ -196,26 +196,33 @@ export const oidcConfigServiceFactory = ({
const foundUser = await userDAL.findById(userAlias.userId, tx);
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
const membership = await orgDAL.createMembership(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role,
roleId,
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
actorUserId: userAlias.userId,
scopeOrgId: orgId,
scope: AccessScope.Organization,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
await orgDAL.updateMembershipById(
@@ -288,8 +295,9 @@ export const oidcConfigServiceFactory = ({
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
@@ -299,15 +307,22 @@ export const oidcConfigServiceFactory = ({
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
const membership = await orgDAL.createMembership(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role,
roleId,
actorUserId: newUser.id,
scopeOrgId: orgId,
scope: AccessScope.Organization,
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
isActive: true,
inviteEmail: email.toLowerCase()
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
@@ -341,7 +356,7 @@ export const oidcConfigServiceFactory = ({
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
projectDAL,
projectBotDAL
@@ -378,7 +393,7 @@ export const oidcConfigServiceFactory = ({
group,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL
});
}
@@ -749,7 +764,7 @@ export const oidcConfigServiceFactory = ({
};
const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => {
await permissionService.getUserOrgPermission(actor.id, orgId, actor.authMethod, actor.orgId);
await permissionService.getOrgPermission(ActorType.USER, actor.id, orgId, actor.authMethod, actor.orgId);
const oidcConfig = await oidcConfigDAL.findOne({
orgId,
@@ -219,7 +219,9 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Groups).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGroupActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.SecretScanning).describe("The entity this permission pertains to."),
@@ -227,11 +229,15 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Billing).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionBillingActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Identity).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionIdentityActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Kms).describe("The entity this permission pertains to."),
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,7 @@
import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability";
import { z } from "zod";
import { OrgMembershipRole, TOrganizations } from "@app/db/schemas";
import { TOrganizations } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
@@ -123,13 +123,13 @@ function validateOrgSSO(
isOrgSsoEnforced: TOrganizations["authEnforced"],
isOrgGoogleSsoEnforced: TOrganizations["googleSsoAuthEnforced"],
isOrgSsoBypassEnabled: TOrganizations["bypassOrgAuthEnabled"],
orgRole: OrgMembershipRole
isAdmin: boolean
) {
if (actorAuthMethod === undefined) {
throw new UnauthorizedError({ name: "No auth method defined" });
}
if ((isOrgSsoEnforced || isOrgGoogleSsoEnforced) && isOrgSsoBypassEnabled && orgRole === OrgMembershipRole.Admin) {
if ((isOrgSsoEnforced || isOrgGoogleSsoEnforced) && isOrgSsoBypassEnabled && isAdmin) {
return;
}
@@ -1,8 +1,8 @@
import { MongoAbility, RawRuleOf } from "@casl/ability";
import { MongoAbility } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js";
import { Knex } from "knex";
import { ActionProjectType } from "@app/db/schemas";
import { ActionProjectType, TMemberships } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission";
@@ -49,232 +49,90 @@ export type TGetProjectPermissionArg = {
actionProjectType: ActionProjectType;
};
export type TGetOrgPermissionArg = {
actor: ActorType;
actorId: string;
orgId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId?: string;
};
export type TPermissionServiceFactory = {
getUserOrgPermission: (
userId: string,
orgId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
status: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
role: string;
isActive: boolean;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
permissions?: unknown;
userId?: string | null | undefined;
roleId?: string | null | undefined;
inviteEmail?: string | null | undefined;
projectFavorites?: string[] | null | undefined;
customRoleSlug?: string | null | undefined;
orgAuthEnforced?: boolean | null | undefined;
} & {
groups: {
id: string;
updatedAt: Date;
createdAt: Date;
role: string;
roleId: string | null | undefined;
customRolePermission: unknown;
name: string;
slug: string;
orgId: string;
}[];
};
}>;
getOrgPermission: (
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => Promise<
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
status: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
role: string;
isActive: boolean;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
permissions?: unknown;
userId?: string | null | undefined;
roleId?: string | null | undefined;
inviteEmail?: string | null | undefined;
projectFavorites?: string[] | null | undefined;
customRoleSlug?: string | null | undefined;
orgAuthEnforced?: boolean | null | undefined;
} & {
groups: {
id: string;
updatedAt: Date;
createdAt: Date;
role: string;
roleId: string | null | undefined;
customRolePermission: unknown;
name: string;
slug: string;
orgId: string;
}[];
};
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
memberships: Array<
TMemberships & {
roles: { role: string; customRoleSlug?: string | null }[];
shouldUseNewPrivilegeSystem?: boolean | null;
}
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
id: string;
role: string;
createdAt: Date;
updatedAt: Date;
orgId: string;
roleId?: string | null | undefined;
permissions?: unknown;
identityId: string;
orgAuthEnforced: boolean | null | undefined;
shouldUseNewPrivilegeSystem: boolean;
};
}
>;
getUserProjectPermission: ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg) => Promise<{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
projectId: string;
} & {
orgAuthEnforced: boolean | null | undefined;
orgId: string;
roles: Array<{
role: string;
}>;
shouldUseNewPrivilegeSystem: boolean;
};
>;
hasRole: (role: string) => boolean;
}>;
getProjectPermission: <T extends ActorType>(
arg: TGetProjectPermissionArg
) => Promise<
T extends ActorType.SERVICE
? {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: {
shouldUseNewPrivilegeSystem: boolean;
};
hasRole: (arg: string) => boolean;
}
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: (T extends ActorType.USER
? {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
projectId: string;
}
: {
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
identityId: string;
}) & {
orgAuthEnforced: boolean | null | undefined;
orgId: string;
roles: Array<{
role: string;
}>;
shouldUseNewPrivilegeSystem: boolean;
};
hasRole: (role: string) => boolean;
}
>;
getProjectPermissions: (projectId: string) => Promise<{
getProjectPermission: (arg: TGetProjectPermissionArg) => Promise<{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
memberships: Array<TMemberships & { roles: { role: string; customRoleSlug?: string | null }[] }>;
hasRole: (role: string) => boolean;
}>;
getProjectPermissions: (
projectId: string,
orgId: string
) => Promise<{
userPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
identityPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
groupPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
}>;
getOrgPermissionByRole: (
role: string,
getOrgPermissionByRoles: (
roles: string[],
orgId: string
) => Promise<
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
role: {
name: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
role?: undefined;
}
{
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
role?: {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}[]
>;
getProjectPermissionByRole: (
role: string,
getProjectPermissionByRoles: (
roles: string[],
projectId: string
) => Promise<
| {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
role: {
name: string;
version: number;
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}
| {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
role?: undefined;
}
{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
role?: {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}[]
>;
buildOrgPermission: (orgUserRoles: TBuildOrgPermissionDTO) => MongoAbility<OrgPermissionSet, MongoQuery>;
buildProjectPermissionRules: (
projectUserRoles: TBuildProjectPermissionDTO
) => RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
checkGroupProjectPermission: ({
groupId,
projectId,
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
import { z } from "zod";
import { ProjectMembershipRole } from "@app/db/schemas";
import {
CASL_ACTION_SCHEMA_ENUM,
CASL_ACTION_SCHEMA_NATIVE_ENUM
@@ -199,6 +200,9 @@ export enum ProjectPermissionPamSessionActions {
// Terminate = "terminate"
}
export const isCustomProjectRole = (slug: string) =>
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
export enum ProjectPermissionSub {
Role = "role",
Member = "member",
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify, TOrmify } from "@app/lib/knex";
export type TProjectUserAdditionalPrivilegeDALFactory = TOrmify<TableName.ProjectUserAdditionalPrivilege>;
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient): TProjectUserAdditionalPrivilegeDALFactory => {
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
return orm;
};
@@ -1,388 +0,0 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import {
ProjectPermissionMemberActions,
ProjectPermissionSet,
ProjectPermissionSub
} from "../permission/project-permission";
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
import {
ProjectUserAdditionalPrivilegeTemporaryMode,
TProjectUserAdditionalPrivilegeServiceFactory
} from "./project-user-additional-privilege-types";
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
};
const unpackPermissions = (permissions: unknown) =>
UnpackedPermissionSchema.array().parse(
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
);
export const projectUserAdditionalPrivilegeServiceFactory = ({
projectUserAdditionalPrivilegeDAL,
projectMembershipDAL,
permissionService,
accessApprovalRequestDAL
}: TProjectUserAdditionalPrivilegeServiceFactoryDep): TProjectUserAdditionalPrivilegeServiceFactory => {
const create: TProjectUserAdditionalPrivilegeServiceFactory["create"] = async ({
slug,
actor,
actorId,
permissions: customPermission,
actorOrgId,
actorAuthMethod,
projectMembershipId,
...dto
}) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged user",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
slug,
projectId: projectMembership.projectId,
userId: projectMembership.userId
});
if (existingSlug)
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
validateHandlebarTemplate("User Additional Privilege Create", JSON.stringify(customPermission || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const packedPermission = JSON.stringify(packRules(customPermission));
if (!dto.isTemporary) {
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
userId: projectMembership.userId,
projectId: projectMembership.projectId,
slug,
permissions: packedPermission
});
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
projectId: projectMembership.projectId,
userId: projectMembership.userId,
slug,
permissions: packedPermission,
isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: dto.temporaryRange,
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
});
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const updateById: TProjectUserAdditionalPrivilegeServiceFactory["updateById"] = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
...dto
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// 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 permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged user",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
if (dto?.slug) {
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
slug: dto.slug,
userId: projectMembership.id,
projectId: projectMembership.projectId
});
if (existingSlug && existingSlug.id !== userPrivilege.id)
throw new BadRequestError({ message: `Additional privilege with provided slug ${dto.slug} already exists` });
}
validateHandlebarTemplate("User Additional Privilege Update", JSON.stringify(dto.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
const packedPermission = dto.permissions && JSON.stringify(packRules(dto.permissions));
if (isTemporary) {
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
slug: dto.slug,
permissions: packedPermission,
isTemporary: dto.isTemporary,
temporaryRange: dto.temporaryRange,
temporaryMode: dto.temporaryMode,
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
});
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
slug: dto.slug,
permissions: packedPermission,
isTemporary: false,
temporaryAccessStartTime: null,
temporaryAccessEndTime: null,
temporaryRange: null,
temporaryMode: null
});
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
};
const deleteById: TProjectUserAdditionalPrivilegeServiceFactory["deleteById"] = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
privilegeId
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
await accessApprovalRequestDAL.update(
{
privilegeId: userPrivilege.id
},
{
privilegeDeletedAt: new Date(),
status: ApprovalStatus.REJECTED
}
);
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
return {
...deletedPrivilege,
permissions: unpackPermissions(deletedPrivilege.permissions)
};
};
const getPrivilegeDetailsById: TProjectUserAdditionalPrivilegeServiceFactory["getPrivilegeDetailsById"] = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
return {
...userPrivilege,
permissions: unpackPermissions(userPrivilege.permissions)
};
};
const listPrivileges: TProjectUserAdditionalPrivilegeServiceFactory["listPrivileges"] = async ({
projectMembershipId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
{
userId: projectMembership.userId,
projectId: projectMembership.projectId
},
{ sort: [[`${TableName.ProjectUserAdditionalPrivilege}.slug` as "slug", "asc"]] }
);
return userPrivileges;
};
return {
create,
updateById,
deleteById,
getPrivilegeDetailsById,
listPrivileges
};
};
@@ -1,60 +0,0 @@
import { TProjectUserAdditionalPrivilege } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
import { TProjectPermissionV2Schema } from "../permission/project-permission";
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
Relative = "relative"
}
export type TCreateUserPrivilegeDTO = (
| {
permissions: TProjectPermissionV2Schema[];
projectMembershipId: string;
slug: string;
isTemporary: false;
}
| {
permissions: TProjectPermissionV2Schema[];
projectMembershipId: string;
slug: string;
isTemporary: true;
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}
) &
Omit<TProjectPermission, "projectId">;
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
Partial<{
permissions: TProjectPermissionV2Schema[];
slug: string;
isTemporary: boolean;
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
temporaryRange: string;
temporaryAccessStartTime: string;
}>;
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
interface TAdditionalPrivilege extends TProjectUserAdditionalPrivilege {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
}
export type TProjectUserAdditionalPrivilegeServiceFactory = {
create: (arg: TCreateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
updateById: (arg: TUpdateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
deleteById: (arg: TDeleteUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
getPrivilegeDetailsById: (arg: TGetUserPrivilegeDetailsDTO) => Promise<TAdditionalPrivilege>;
listPrivileges: (arg: TListUserPrivilegesDTO) => Promise<TProjectUserAdditionalPrivilege[]>;
};
@@ -4,6 +4,7 @@ import { Knex } from "knex";
import RE2 from "re2";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
@@ -19,13 +20,13 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
import { AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
@@ -68,32 +69,30 @@ type TSamlConfigServiceFactoryDep = {
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "find" | "transaction">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "delete" | "transaction" | "insertMany" | "filterProjectsByUserMembership"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "delete" | "transaction" | "insertMany" | "filterProjectsByUserMembership"
>;
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "find" | "transaction">;
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
};
export const samlConfigServiceFactory = ({
samlConfigDAL,
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
groupDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectDAL,
projectBotDAL,
projectKeyDAL,
@@ -102,7 +101,9 @@ export const samlConfigServiceFactory = ({
tokenService,
smtpService,
identityMetadataDAL,
kmsService
kmsService,
membershipRoleDAL,
membershipGroupDAL
}: TSamlConfigServiceFactoryDep): TSamlConfigServiceFactory => {
const parseSamlGroups = (groupsValue: string): string[] => {
let samlGroups: string[] = [];
@@ -195,10 +196,10 @@ export const samlConfigServiceFactory = ({
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx: transaction
});
} catch (error) {
@@ -218,7 +219,7 @@ export const samlConfigServiceFactory = ({
group,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
tx: transaction
});
@@ -506,26 +507,35 @@ export const samlConfigServiceFactory = ({
const foundUser = await userDAL.findById(userAlias.userId, tx);
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
const membership = await orgDAL.createMembership(
{
userId: userAlias.userId,
actorUserId: userAlias.userId,
inviteEmail: email,
orgId,
role,
roleId,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited,
scopeOrgId: orgId,
scope: AccessScope.Organization,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
await orgDAL.updateMembershipById(
@@ -606,8 +616,9 @@ export const samlConfigServiceFactory = ({
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
@@ -617,15 +628,22 @@ export const samlConfigServiceFactory = ({
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
const membership = await orgDAL.createMembership(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role,
roleId,
actorUserId: newUser.id,
scopeOrgId: orgId,
scope: AccessScope.Organization,
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
isActive: true,
inviteEmail: email.toLowerCase()
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
+125 -75
View File
@@ -2,7 +2,15 @@ import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { scimPatch } from "scim-patch";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
TGroups,
TMemberships,
TUsers
} from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@@ -11,18 +19,19 @@ import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
import { deleteOrgMembershipsFn } from "@app/services/org/org-fns";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { OrgAuthMethod } from "@app/services/org/org-types";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
@@ -33,7 +42,6 @@ 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-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import { TScimGroup, TScimServiceFactory } from "./scim-types";
@@ -55,12 +63,8 @@ type TScimServiceFactoryDep = {
| "updateMembershipById"
| "findOrgById"
>;
orgMembershipDAL: Pick<
TOrgMembershipDALFactory,
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
>;
membershipUserDAL: TMembershipUserDALFactory;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
TGroupDALFactory,
| "create"
@@ -72,7 +76,8 @@ type TScimServiceFactoryDep = {
| "updateById"
| "update"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find" | "create">;
membershipRoleDAL: TMembershipRoleDALFactory;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
| "find"
@@ -88,8 +93,8 @@ type TScimServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
smtpService: Pick<TSmtpService, "sendMail">;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
additionalPrivilegeDAL: TAdditionalPrivilegeDALFactory;
};
export const scimServiceFactory = ({
@@ -98,18 +103,18 @@ export const scimServiceFactory = ({
userDAL,
userAliasDAL,
orgDAL,
orgMembershipDAL,
projectDAL,
projectMembershipDAL,
groupDAL,
groupProjectDAL,
userGroupMembershipDAL,
projectKeyDAL,
projectBotDAL,
permissionService,
projectUserAdditionalPrivilegeDAL,
smtpService,
externalGroupOrgRoleMappingDAL
externalGroupOrgRoleMappingDAL,
membershipGroupDAL,
membershipUserDAL,
membershipRoleDAL,
additionalPrivilegeDAL
}: TScimServiceFactoryDep): TScimServiceFactory => {
const createScimToken: TScimServiceFactory["createScimToken"] = async ({
actor,
@@ -244,8 +249,9 @@ export const scimServiceFactory = ({
const getScimUser: TScimServiceFactory["getScimUser"] = async ({ orgMembershipId, orgId }) => {
const [membership] = await orgDAL
.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
})
.catch(() => {
throw new ScimRequestError({
@@ -322,13 +328,14 @@ export const scimServiceFactory = ({
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
let user: TUsers | undefined;
let orgMembership: TOrgMemberships;
let orgMembership: TMemberships;
if (userAlias) {
user = await userDAL.findById(userAlias.userId, tx);
orgMembership = await orgMembershipDAL.findOne(
orgMembership = await membershipUserDAL.findOne(
{
userId: user.id,
orgId
actorUserId: user.id,
scope: AccessScope.Organization,
scopeOrgId: orgId
},
tx
);
@@ -336,20 +343,27 @@ export const scimServiceFactory = ({
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
orgMembership = await orgMembershipDAL.create(
orgMembership = await membershipUserDAL.create(
{
userId: userAlias.userId,
actorUserId: userAlias.userId,
inviteEmail: email.toLowerCase(),
orgId,
role,
roleId,
scopeOrgId: orgId,
scope: AccessScope.Organization,
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
);
await membershipRoleDAL.create(
{
membershipId: orgMembership.id,
role,
customRoleId: roleId
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
orgMembership = await orgMembershipDAL.updateById(
orgMembership = await membershipUserDAL.updateById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
@@ -401,8 +415,9 @@ export const scimServiceFactory = ({
const [foundOrgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
},
{ tx }
);
@@ -412,18 +427,25 @@ export const scimServiceFactory = ({
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
orgMembership = await orgMembershipDAL.create(
orgMembership = await membershipUserDAL.create(
{
userId: user.id,
actorUserId: user.id,
inviteEmail: email.toLowerCase(),
orgId,
role,
roleId,
scopeOrgId: orgId,
scope: AccessScope.Organization,
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
);
await membershipRoleDAL.create(
{
membershipId: orgMembership.id,
role,
customRoleId: roleId
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
orgMembership = await orgDAL.updateMembershipById(
@@ -475,8 +497,9 @@ export const scimServiceFactory = ({
const [membership] = await orgDAL
.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
})
.catch(() => {
throw new ScimRequestError({
@@ -485,7 +508,7 @@ export const scimServiceFactory = ({
});
});
if (!membership)
if (!membership || !membership.actorUserId)
throw new ScimRequestError({
detail: "User not found",
status: 404
@@ -514,7 +537,7 @@ export const scimServiceFactory = ({
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails;
await userDAL.transaction(async (tx) => {
await orgMembershipDAL.updateById(
await membershipUserDAL.updateById(
membership.id,
{
isActive: scimUser.active
@@ -523,7 +546,7 @@ export const scimServiceFactory = ({
);
const hasEmailChanged = scimUser.emails[0].value !== membership.email;
await userDAL.updateById(
membership.userId,
membership.actorUserId as string,
{
firstName: scimUser.name.givenName,
email: scimUser.emails[0].value.toLowerCase(),
@@ -556,8 +579,9 @@ export const scimServiceFactory = ({
const [membership] = await orgDAL
.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
})
.catch(() => {
throw new ScimRequestError({
@@ -566,7 +590,7 @@ export const scimServiceFactory = ({
});
});
if (!membership)
if (!membership || !membership.actorUserId)
throw new ScimRequestError({
detail: "User not found",
status: 404
@@ -587,7 +611,7 @@ export const scimServiceFactory = ({
{
orgId,
aliasType: org.orgAuthMethod === OrgAuthMethod.OIDC ? UserAliasType.OIDC : UserAliasType.SAML,
userId: membership.userId
userId: membership.actorUserId as string
},
{
externalId
@@ -595,7 +619,7 @@ export const scimServiceFactory = ({
tx
);
await orgMembershipDAL.updateById(
await membershipUserDAL.updateById(
membership.id,
{
isActive: active
@@ -603,7 +627,7 @@ export const scimServiceFactory = ({
tx
);
await userDAL.updateById(
membership.userId,
membership.actorUserId!,
{
firstName,
email: email?.toLowerCase(),
@@ -628,8 +652,9 @@ export const scimServiceFactory = ({
const deleteScimUser: TScimServiceFactory["deleteScimUser"] = async ({ orgMembershipId, orgId }) => {
const [membership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (!membership)
@@ -645,15 +670,17 @@ export const scimServiceFactory = ({
});
}
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
await deleteOrgMembershipsFn({
orgMembershipIds: [membership.id],
orgId: membership.scopeOrgId,
orgDAL,
projectMembershipDAL,
projectUserAdditionalPrivilegeDAL,
projectKeyDAL,
userAliasDAL,
licenseService
licenseService,
membershipUserDAL,
membershipRoleDAL,
userGroupMembershipDAL,
additionalPrivilegeDAL
});
return {}; // intentionally return empty object upon success
@@ -750,8 +777,9 @@ export const scimServiceFactory = ({
if (!externalGroupMapping) return;
// only get org memberships that are new (invites)
const newOrgMemberships = await orgMembershipDAL.find({
const newOrgMemberships = await membershipUserDAL.find({
status: "invited",
scope: AccessScope.Organization,
$in: {
id: members.map((member) => member.value)
}
@@ -760,15 +788,15 @@ export const scimServiceFactory = ({
if (!newOrgMemberships.length) return;
// set new membership roles to group mapping value
await orgMembershipDAL.update(
await membershipRoleDAL.update(
{
$in: {
id: newOrgMemberships.map((membership) => membership.id)
membershipId: newOrgMemberships.map((membership) => membership.id)
}
},
{
role: externalGroupMapping.role,
roleId: externalGroupMapping.roleId
customRoleId: externalGroupMapping.roleId
}
);
};
@@ -821,8 +849,26 @@ export const scimServiceFactory = ({
tx
);
const groupMembership = await membershipGroupDAL.create(
{
scope: AccessScope.Organization,
actorGroupId: group.id,
scopeOrgId: orgId
},
tx
);
await membershipRoleDAL.create(
{
membershipId: groupMembership.id,
role: OrgMembershipRole.NoAccess
},
tx
);
if (members && members.length) {
const orgMemberships = await orgMembershipDAL.find({
const orgMemberships = await membershipUserDAL.find({
scope: AccessScope.Organization,
$in: {
id: members.map((member) => member.value)
}
@@ -830,14 +876,14 @@ export const scimServiceFactory = ({
const newMembers = await addUsersToGroupByUserIds({
group,
userIds: orgMemberships.map((membership) => membership.userId as string),
userIds: orgMemberships.map((membership) => membership.actorUserId as string),
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx
});
@@ -850,9 +896,10 @@ export const scimServiceFactory = ({
});
const orgMemberships = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: newGroup.newMembers.map((member) => member.id)
[`${TableName.Membership}.actorUserId` as "actorUserId"]: newGroup.newMembers.map((member) => member.id)
}
});
@@ -895,9 +942,10 @@ export const scimServiceFactory = ({
.then((g) => g.members);
const orgMemberships = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: users
[`${TableName.Membership}.actorUserId` as "actorUserId"]: users
.filter((user) => user.isPartOfGroup)
.map((user) => user.id)
}
@@ -933,10 +981,10 @@ export const scimServiceFactory = ({
}
const updatedGroup = await groupDAL.transaction(async (tx) => {
if (group.name !== displayName) {
if (group?.name !== displayName) {
await externalGroupOrgRoleMappingDAL.update(
{
groupName: group.name,
groupName: group?.name,
orgId
},
{
@@ -958,14 +1006,16 @@ export const scimServiceFactory = ({
}
const orgMemberships = members.length
? await orgMembershipDAL.find({
? await membershipUserDAL.find({
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
$in: {
id: members.map((member) => member.value)
}
})
: [];
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.actorUserId as string));
const userGroupMembers = await userGroupMembershipDAL.find({
groupId: group.id
});
@@ -978,20 +1028,20 @@ export const scimServiceFactory = ({
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
const allMembersUserIdsSet = new Set(allMembersUserIds);
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.actorUserId as string));
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
if (toAddUserIds.length) {
await addUsersToGroupByUserIds({
group,
userIds: toAddUserIds.map((member) => member.userId as string),
userIds: toAddUserIds.map((member) => member.actorUserId as string),
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx
});
}
@@ -1002,7 +1052,7 @@ export const scimServiceFactory = ({
userIds: toRemoveUserIds,
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
membershipGroupDAL,
projectKeyDAL,
tx
});
@@ -2,9 +2,10 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import {
AccessScope,
SecretApprovalRequestsSchema,
TableName,
TOrgMemberships,
TMemberships,
TSecretApprovalRequests,
TSecretApprovalRequestsSecrets,
TUserGroupMembership,
@@ -36,6 +37,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.where(filter)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.join(
TableName.SecretApprovalPolicy,
`${TableName.SecretApprovalRequest}.policyId`,
@@ -109,24 +111,22 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`secretApprovalReviewerUser.id`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("approverOrgMembership"),
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
`approverOrgMembership.userId`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("approverGroupOrgMembership"),
`secretApprovalPolicyGroupApproverUser.id`,
`approverGroupOrgMembership.userId`
)
.leftJoin<TOrgMemberships>(
db(TableName.OrgMembership).as("reviewerOrgMembership"),
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
`reviewerOrgMembership.userId`
)
.leftJoin<TMemberships>(db(TableName.Membership).as("approverOrgMembership"), (qb) => {
qb.on(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, `approverOrgMembership.actorUserId`)
.andOn(`approverOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`approverOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin<TMemberships>(db(TableName.Membership).as("approverGroupOrgMembership"), (qb) => {
qb.on(`secretApprovalPolicyGroupApproverUser.id`, `approverGroupOrgMembership.actorUserId`)
.andOn(`approverGroupOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`approverGroupOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin<TMemberships>(db(TableName.Membership).as("reviewerOrgMembership"), (qb) => {
qb.on(`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`, `reviewerOrgMembership.actorUserId`)
.andOn(`reviewerOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`reviewerOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
@@ -44,10 +44,7 @@ type TSshHostGroupServiceFactoryDep = {
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction" | "delete">;
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
userDAL: Pick<TUserDALFactory, "find">;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "checkGroupProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
};
@@ -2,6 +2,7 @@ import { Knex } from "knex";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
import { TCreateSshLoginMappingsDTO } from "./ssh-host-types";
@@ -59,11 +60,12 @@ export const createSshLoginMappings = async ({
for await (const user of users) {
// check that each user has access to the SSH project
await permissionService.getUserProjectPermission({
userId: user.id,
await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: user.id,
projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
}
@@ -64,10 +64,7 @@ type TSshHostServiceFactoryDep = {
>;
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "checkGroupProjectPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
@@ -66,7 +66,7 @@ type BaseCreateSshLoginMappingsDTO = {
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction">;
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
userDAL: Pick<TUserDALFactory, "find">;
permissionService: Pick<TPermissionServiceFactory, "getUserProjectPermission" | "checkGroupProjectPermission">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "checkGroupProjectPermission">;
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
projectId: string;
actorAuthMethod: ActorAuthMethod;
+204 -171
View File
@@ -50,9 +50,6 @@ import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { HsmModule } from "@app/ee/services/hsm/hsm-types";
import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal";
import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service";
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-client-certificate-dal";
import { kmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
import { kmipOperationServiceFactory } from "@app/ee/services/kmip/kmip-operation-service";
@@ -79,8 +76,6 @@ import { permissionServiceFactory } from "@app/ee/services/permission/permission
import { pitServiceFactory } from "@app/ee/services/pit/pit-service";
import { projectTemplateDALFactory } from "@app/ee/services/project-template/project-template-dal";
import { projectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
import { rateLimitDALFactory } from "@app/ee/services/rate-limit/rate-limit-dal";
import { rateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
import { instanceRelayConfigDalFactory } from "@app/ee/services/relay/instance-relay-config-dal";
@@ -147,6 +142,8 @@ import { TQueueServiceFactory } from "@app/queue";
import { readLimit } from "@app/server/config/rateLimiter";
import { registerSecretScanningV2Webhooks } from "@app/server/plugins/secret-scanner-v2";
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
import { additionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal";
import { additionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service";
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
@@ -174,6 +171,7 @@ import { certificateTemplateDALFactory } from "@app/services/certificate-templat
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
import { convertorServiceFactory } from "@app/services/convertor/convertor-service";
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
@@ -187,7 +185,6 @@ import { folderCommitChangesDALFactory } from "@app/services/folder-commit-chang
import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal";
import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal";
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { identityDALFactory } from "@app/services/identity/identity-dal";
import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
@@ -214,7 +211,6 @@ import { identityOciAuthServiceFactory } from "@app/services/identity-oci-auth/i
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { identityTlsCertAuthDALFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-dal";
import { identityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-service";
@@ -231,6 +227,14 @@ import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { membershipDALFactory } from "@app/services/membership/membership-dal";
import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service";
import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal";
import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service";
import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service";
import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal";
import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal";
@@ -242,8 +246,6 @@ import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-re
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
import { orgBotDALFactory } from "@app/services/org/org-bot-dal";
import { orgDALFactory } from "@app/services/org/org-dal";
import { orgRoleDALFactory } from "@app/services/org/org-role-dal";
import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
import { orgServiceFactory } from "@app/services/org/org-service";
import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
@@ -274,15 +276,14 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal"
import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { reminderDALFactory } from "@app/services/reminder/reminder-dal";
import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue";
import { reminderServiceFactory } from "@app/services/reminder/reminder-service";
import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { roleDALFactory } from "@app/services/role/role-dal";
import { roleServiceFactory } from "@app/services/role/role-service";
import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -382,16 +383,12 @@ export const registerRoutes = async (
const orgMembershipDAL = orgMembershipDALFactory(db);
const orgBotDAL = orgBotDALFactory(db);
const incidentContactDAL = incidentContactDALFactory(db);
const orgRoleDAL = orgRoleDALFactory(db);
const rateLimitDAL = rateLimitDALFactory(db);
const apiKeyDAL = apiKeyDALFactory(db);
const projectDAL = projectDALFactory(db);
const projectSshConfigDAL = projectSshConfigDALFactory(db);
const projectMembershipDAL = projectMembershipDALFactory(db);
const projectUserAdditionalPrivilegeDAL = projectUserAdditionalPrivilegeDALFactory(db);
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db);
const projectRoleDAL = projectRoleDALFactory(db);
const projectEnvDAL = projectEnvDALFactory(db);
const projectKeyDAL = projectKeyDALFactory(db);
const projectBotDAL = projectBotDALFactory(db);
@@ -423,8 +420,6 @@ export const registerRoutes = async (
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
const identityOrgMembershipDAL = identityOrgDALFactory(db);
const identityProjectDAL = identityProjectDALFactory(db);
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
const identityAuthTemplateDAL = identityAuthTemplateDALFactory(db);
const identityTokenAuthDAL = identityTokenAuthDALFactory(db);
@@ -482,7 +477,6 @@ export const registerRoutes = async (
const gitAppOrgDAL = gitAppDALFactory(db);
const groupDAL = groupDALFactory(db);
const groupProjectDAL = groupProjectDALFactory(db);
const groupProjectMembershipRoleDAL = groupProjectMembershipRoleDALFactory(db);
const userGroupMembershipDAL = userGroupMembershipDALFactory(db);
const secretScanningDAL = secretScanningDALFactory(db);
const secretSharingDAL = secretSharingDALFactory(db);
@@ -531,17 +525,27 @@ export const registerRoutes = async (
const secretScanningV2DAL = secretScanningV2DALFactory(db);
const keyValueStoreDAL = keyValueStoreDALFactory(db);
const membershipDAL = membershipDALFactory(db);
const membershipUserDAL = membershipUserDALFactory(db);
const membershipIdentityDAL = membershipIdentityDALFactory(db);
const membershipGroupDAL = membershipGroupDALFactory(db);
const additionalPrivilegeDAL = additionalPrivilegeDALFactory(db);
const membershipRoleDAL = membershipRoleDALFactory(db);
const roleDAL = roleDALFactory(db);
const eventBusService = eventBusFactory(server.redis);
const sseService = sseServiceFactory(eventBusService, server.redis);
const permissionService = permissionServiceFactory({
permissionDAL,
orgRoleDAL,
projectRoleDAL,
serviceTokenDAL,
projectDAL,
keyStore
keyStore,
roleDAL,
userDAL,
identityDAL
});
const assumePrivilegeService = assumePrivilegeServiceFactory({
projectDAL,
permissionService
@@ -556,6 +560,57 @@ export const registerRoutes = async (
projectDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL });
const membershipUserService = membershipUserServiceFactory({
licenseService,
membershipRoleDAL,
membershipUserDAL,
orgDAL,
permissionService,
roleDAL,
userDAL,
projectDAL,
projectKeyDAL,
smtpService,
tokenService,
userAliasDAL,
userGroupMembershipDAL,
additionalPrivilegeDAL
});
const membershipIdentityService = membershipIdentityServiceFactory({
membershipIdentityDAL,
membershipRoleDAL,
orgDAL,
permissionService,
roleDAL,
additionalPrivilegeDAL
});
const membershipGroupService = membershipGroupServiceFactory({
membershipGroupDAL,
membershipRoleDAL,
roleDAL,
permissionService,
orgDAL
});
const roleService = roleServiceFactory({
permissionService,
roleDAL,
projectDAL,
identityDAL,
userDAL,
externalGroupOrgRoleMappingDAL
});
const additionalPrivilegeService = additionalPrivilegeServiceFactory({
additionalPrivilegeDAL,
membershipDAL,
orgDAL,
permissionService
});
const hsmService = hsmServiceFactory({
hsmModule,
envConfig
@@ -621,31 +676,29 @@ export const registerRoutes = async (
userDAL,
secretApprovalRequestDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
const samlService = samlConfigServiceFactory({
identityMetadataDAL,
permissionService,
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
samlConfigDAL,
groupDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectDAL,
projectBotDAL,
projectKeyDAL,
licenseService,
tokenService,
smtpService,
kmsService
kmsService,
membershipRoleDAL,
membershipGroupDAL
});
const groupService = groupServiceFactory({
userDAL,
groupDAL,
groupProjectDAL,
orgDAL,
userGroupMembershipDAL,
projectDAL,
@@ -653,17 +706,13 @@ export const registerRoutes = async (
projectKeyDAL,
permissionService,
licenseService,
oidcConfigDAL
oidcConfigDAL,
membershipGroupDAL,
membershipRoleDAL
});
const groupProjectService = groupProjectServiceFactory({
groupDAL,
groupProjectDAL,
groupProjectMembershipRoleDAL,
userGroupMembershipDAL,
projectDAL,
projectKeyDAL,
projectBotDAL,
projectRoleDAL,
permissionService
});
@@ -708,18 +757,18 @@ export const registerRoutes = async (
userDAL,
userAliasDAL,
orgDAL,
orgMembershipDAL,
projectDAL,
projectUserAdditionalPrivilegeDAL,
projectMembershipDAL,
groupDAL,
groupProjectDAL,
userGroupMembershipDAL,
projectKeyDAL,
projectBotDAL,
permissionService,
smtpService,
externalGroupOrgRoleMappingDAL
externalGroupOrgRoleMappingDAL,
groupDAL,
membershipGroupDAL,
membershipRoleDAL,
membershipUserDAL,
additionalPrivilegeDAL
});
const githubOrgSyncConfigService = githubOrgSyncServiceFactory({
@@ -729,16 +778,16 @@ export const registerRoutes = async (
permissionService,
groupDAL,
userGroupMembershipDAL,
orgMembershipDAL
orgMembershipDAL,
membershipRoleDAL,
membershipGroupDAL
});
const ldapService = ldapConfigServiceFactory({
ldapConfigDAL,
ldapGroupMapDAL,
orgDAL,
orgMembershipDAL,
groupDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@@ -749,7 +798,9 @@ export const registerRoutes = async (
licenseService,
tokenService,
smtpService,
kmsService
kmsService,
membershipGroupDAL,
membershipRoleDAL
});
const telemetryService = telemetryServiceFactory({
@@ -771,13 +822,12 @@ export const registerRoutes = async (
const userService = userServiceFactory({
userDAL,
orgDAL,
orgMembershipDAL,
tokenService,
permissionService,
groupProjectDAL,
smtpService,
projectMembershipDAL,
userAliasDAL
userAliasDAL,
membershipUserDAL
});
const upgradePathService = upgradePathServiceFactory({ keyStore });
@@ -794,17 +844,18 @@ export const registerRoutes = async (
tokenService,
orgDAL,
totpService,
orgMembershipDAL,
auditLogService,
notificationService
notificationService,
membershipRoleDAL,
membershipUserDAL
});
const passwordService = authPaswordServiceFactory({
tokenService,
smtpService,
authDAL,
userDAL,
orgMembershipDAL,
totpConfigDAL
totpConfigDAL,
membershipUserDAL
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
@@ -826,14 +877,10 @@ export const registerRoutes = async (
folderDAL,
licenseService,
samlConfigDAL,
orgRoleDAL,
permissionService,
orgDAL,
incidentContactDAL,
tokenService,
projectUserAdditionalPrivilegeDAL,
projectUserMembershipRoleDAL,
projectRoleDAL,
projectDAL,
projectMembershipDAL,
orgMembershipDAL,
@@ -846,7 +893,12 @@ export const registerRoutes = async (
ldapConfigDAL,
loginService,
projectBotService,
reminderService
reminderService,
membershipRoleDAL,
membershipUserDAL,
roleDAL,
userGroupMembershipDAL,
additionalPrivilegeDAL
});
const signupService = authSignupServiceFactory({
tokenService,
@@ -857,18 +909,10 @@ export const registerRoutes = async (
projectKeyDAL,
projectDAL,
projectBotDAL,
groupProjectDAL,
projectMembershipDAL,
projectUserMembershipRoleDAL,
orgDAL,
orgService,
licenseService
});
const orgRoleService = orgRoleServiceFactory({
permissionService,
orgRoleDAL,
orgDAL,
externalGroupOrgRoleMappingDAL
licenseService,
membershipGroupDAL
});
const microsoftTeamsService = microsoftTeamsServiceFactory({
@@ -885,8 +929,6 @@ export const registerRoutes = async (
userAliasDAL,
identityTokenAuthDAL,
identityAccessTokenDAL,
orgMembershipDAL,
identityOrgMembershipDAL,
authService: loginService,
serverCfgDAL: superAdminDAL,
kmsRootConfigDAL,
@@ -898,7 +940,10 @@ export const registerRoutes = async (
microsoftTeamsService,
invalidateCacheQueue,
smtpService,
tokenService
tokenService,
membershipIdentityDAL,
membershipRoleDAL,
membershipUserDAL
});
const offlineUsageReportService = offlineUsageReportServiceFactory({
@@ -910,9 +955,10 @@ export const registerRoutes = async (
smtpService,
projectDAL,
permissionService,
projectUserMembershipRoleDAL,
projectMembershipDAL,
notificationService
notificationService,
membershipRoleDAL,
membershipUserDAL,
projectMembershipDAL
});
const rateLimitService = rateLimitServiceFactory({
@@ -938,32 +984,25 @@ export const registerRoutes = async (
const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL,
projectUserMembershipRoleDAL,
projectDAL,
permissionService,
projectBotDAL,
orgDAL,
userDAL,
projectUserAdditionalPrivilegeDAL,
userGroupMembershipDAL,
smtpService,
projectKeyDAL,
projectRoleDAL,
groupProjectDAL,
secretReminderRecipientsDAL,
licenseService,
notificationService
});
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
permissionService,
projectMembershipDAL,
projectUserAdditionalPrivilegeDAL,
accessApprovalRequestDAL
notificationService,
membershipUserDAL,
additionalPrivilegeDAL,
membershipRoleDAL
});
const projectKeyService = projectKeyServiceFactory({
permissionService,
projectKeyDAL,
projectMembershipDAL
membershipUserDAL
});
const projectQueueService = projectQueueFactory({
@@ -979,10 +1018,10 @@ export const registerRoutes = async (
secretVersionDAL,
projectKeyDAL,
projectBotDAL,
projectMembershipDAL,
secretApprovalRequestDAL,
secretApprovalSecretDAL: secretApprovalRequestSecretDAL,
projectUserMembershipRoleDAL
membershipRoleDAL,
membershipUserDAL
});
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
@@ -1194,14 +1233,15 @@ export const registerRoutes = async (
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService,
resourceMetadataDAL,
folderCommitService,
secretSyncQueue,
reminderService,
eventBusService,
licenseService
licenseService,
membershipRoleDAL,
membershipUserDAL
});
const projectService = projectServiceFactory({
@@ -1212,13 +1252,10 @@ export const registerRoutes = async (
secretV2BridgeDAL,
projectQueue: projectQueueService,
projectBotService,
identityProjectDAL,
identityOrgMembershipDAL,
userDAL,
projectEnvDAL,
orgDAL,
projectMembershipDAL,
projectRoleDAL,
folderDAL,
licenseService,
pkiSubscriberDAL,
@@ -1232,8 +1269,6 @@ export const registerRoutes = async (
sshCertificateTemplateDAL,
sshHostDAL,
sshHostGroupDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL,
keyStore,
kmsService,
certificateTemplateDAL,
@@ -1242,10 +1277,14 @@ export const registerRoutes = async (
projectMicrosoftTeamsConfigDAL,
microsoftTeamsIntegrationDAL,
projectTemplateService,
groupProjectDAL,
smtpService,
reminderService,
notificationService
notificationService,
membershipGroupDAL,
membershipIdentityDAL,
membershipRoleDAL,
membershipUserDAL,
roleDAL
});
const projectEnvService = projectEnvServiceFactory({
@@ -1259,16 +1298,6 @@ export const registerRoutes = async (
secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL
});
const projectRoleService = projectRoleServiceFactory({
permissionService,
projectRoleDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL,
projectDAL,
identityDAL,
userDAL
});
const snapshotService = secretSnapshotServiceFactory({
permissionService,
licenseService,
@@ -1423,21 +1452,18 @@ export const registerRoutes = async (
groupDAL,
permissionService,
projectEnvDAL,
projectMembershipDAL,
projectDAL,
userDAL,
accessApprovalRequestDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
accessApprovalRequestReviewerDAL,
orgMembershipDAL
additionalPrivilegeDAL,
membershipUserDAL
});
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
projectDAL,
permissionService,
accessApprovalRequestReviewerDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
projectMembershipDAL,
accessApprovalPolicyDAL,
accessApprovalRequestDAL,
projectEnvDAL,
@@ -1449,7 +1475,8 @@ export const registerRoutes = async (
groupDAL,
microsoftTeamsService,
projectMicrosoftTeamsConfigDAL,
notificationService
notificationService,
additionalPrivilegeDAL
});
const secretReplicationService = secretReplicationServiceFactory({
@@ -1538,7 +1565,15 @@ export const registerRoutes = async (
identityProjectDAL,
licenseService,
identityMetadataDAL,
keyStore
keyStore,
orgDAL,
membershipIdentityDAL,
membershipRoleDAL
});
const identityProjectService = identityProjectServiceFactory({
identityProjectDAL,
membershipIdentityDAL,
permissionService
});
const identityAuthTemplateService = identityAuthTemplateServiceFactory({
@@ -1557,105 +1592,91 @@ export const registerRoutes = async (
identityDAL
});
const identityProjectService = identityProjectServiceFactory({
permissionService,
projectDAL,
identityProjectDAL,
identityOrgMembershipDAL,
identityProjectMembershipRoleDAL,
projectRoleDAL
});
const identityProjectAdditionalPrivilegeService = identityProjectAdditionalPrivilegeServiceFactory({
projectDAL,
identityProjectAdditionalPrivilegeDAL,
permissionService,
identityProjectDAL
});
const identityProjectAdditionalPrivilegeV2Service = identityProjectAdditionalPrivilegeV2ServiceFactory({
projectDAL,
identityProjectAdditionalPrivilegeDAL,
permissionService,
identityProjectDAL
});
const identityTokenAuthService = identityTokenAuthServiceFactory({
identityTokenAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
permissionService,
licenseService
licenseService,
orgDAL,
membershipIdentityDAL
});
const identityUaService = identityUaServiceFactory({
identityOrgMembershipDAL,
permissionService,
identityAccessTokenDAL,
identityUaClientSecretDAL,
identityUaDAL,
licenseService,
keyStore
keyStore,
orgDAL,
membershipIdentityDAL
});
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityKubernetesAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
permissionService,
licenseService,
gatewayService,
orgDAL,
gatewayV2Service,
gatewayV2DAL,
gatewayDAL,
kmsService
kmsService,
membershipIdentityDAL
});
const identityGcpAuthService = identityGcpAuthServiceFactory({
identityGcpAuthDAL,
identityOrgMembershipDAL,
orgDAL,
identityAccessTokenDAL,
permissionService,
licenseService
licenseService,
membershipIdentityDAL
});
const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({
identityAccessTokenDAL,
orgDAL,
identityAliCloudAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService
permissionService,
membershipIdentityDAL
});
const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({
identityAccessTokenDAL,
identityTlsCertAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService,
kmsService
kmsService,
membershipIdentityDAL
});
const identityAwsAuthService = identityAwsAuthServiceFactory({
identityAccessTokenDAL,
orgDAL,
identityAwsAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService
permissionService,
membershipIdentityDAL
});
const identityAzureAuthService = identityAzureAuthServiceFactory({
identityAzureAuthDAL,
identityOrgMembershipDAL,
orgDAL,
identityAccessTokenDAL,
permissionService,
licenseService
licenseService,
membershipIdentityDAL
});
const identityOciAuthService = identityOciAuthServiceFactory({
identityAccessTokenDAL,
orgDAL,
identityOciAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService
permissionService,
membershipIdentityDAL
});
const pitService = pitServiceFactory({
@@ -1674,32 +1695,42 @@ export const registerRoutes = async (
const identityOidcAuthService = identityOidcAuthServiceFactory({
identityOidcAuthDAL,
identityOrgMembershipDAL,
orgDAL,
identityAccessTokenDAL,
permissionService,
licenseService,
kmsService
kmsService,
membershipIdentityDAL
});
const identityJwtAuthService = identityJwtAuthServiceFactory({
identityJwtAuthDAL,
orgDAL,
permissionService,
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService,
kmsService
kmsService,
membershipIdentityDAL
});
const identityLdapAuthService = identityLdapAuthServiceFactory({
identityLdapAuthDAL,
orgDAL,
permissionService,
kmsService,
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService,
identityDAL,
identityAuthTemplateDAL,
keyStore
keyStore,
membershipIdentityDAL
});
const convertorService = convertorServiceFactory({
additionalPrivilegeDAL,
membershipDAL,
projectDAL,
groupDAL
});
const dynamicSecretProviders = buildDynamicSecretProviders({
@@ -1774,7 +1805,6 @@ export const registerRoutes = async (
const oidcService = oidcConfigServiceFactory({
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
licenseService,
@@ -1787,9 +1817,10 @@ export const registerRoutes = async (
projectKeyDAL,
projectDAL,
userGroupMembershipDAL,
groupProjectDAL,
groupDAL,
auditLogService
auditLogService,
membershipGroupDAL,
membershipRoleDAL
});
const userEngagementService = userEngagementServiceFactory({
@@ -1845,8 +1876,8 @@ export const registerRoutes = async (
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
permissionService,
licenseService,
orgRoleDAL,
externalGroupOrgRoleMappingDAL
externalGroupOrgRoleMappingDAL,
roleDAL
});
const appConnectionService = appConnectionServiceFactory({
@@ -2192,7 +2223,6 @@ export const registerRoutes = async (
groupProject: groupProjectService,
permission: permissionService,
org: orgService,
orgRole: orgRoleService,
oidc: oidcService,
apiKey: apiKeyService,
authToken: tokenService,
@@ -2202,7 +2232,6 @@ export const registerRoutes = async (
projectMembership: projectMembershipService,
projectKey: projectKeyService,
projectEnv: projectEnvService,
projectRole: projectRoleService,
secret: secretService,
secretReplication: secretReplicationService,
secretTag: secretTagService,
@@ -2217,7 +2246,6 @@ export const registerRoutes = async (
identity: identityService,
identityAuthTemplate: identityAuthTemplateService,
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityTokenAuth: identityTokenAuthService,
identityUa: identityUaService,
identityKubernetesAuth: identityKubernetesAuthService,
@@ -2264,9 +2292,6 @@ export const registerRoutes = async (
scim: scimService,
secretBlindIndex: secretBlindIndexService,
telemetry: telemetryService,
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
identityProjectAdditionalPrivilegeV2: identityProjectAdditionalPrivilegeV2Service,
secretSharing: secretSharingService,
userEngagement: userEngagementService,
externalKms: externalKmsService,
@@ -2300,7 +2325,15 @@ export const registerRoutes = async (
pamResource: pamResourceService,
pamAccount: pamAccountService,
pamSession: pamSessionService,
upgradePath: upgradePathService
upgradePath: upgradePathService,
membershipUser: membershipUserService,
membershipIdentity: membershipIdentityService,
membershipGroup: membershipGroupService,
role: roleService,
additionalPrivilege: additionalPrivilegeService,
identityProject: identityProjectService,
convertor: convertorService
});
const cronJobs: CronJob[] = [];
@@ -209,11 +209,11 @@ export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivile
)
});
export const SanitizedRoleSchema = ProjectRolesSchema.extend({
export const SanitizedRoleSchema = ProjectRolesSchema.omit({ version: true }).extend({
permissions: UnpackedPermissionSchema.array()
});
export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({
export const SanitizedRoleSchemaV1 = ProjectRolesSchema.omit({ version: true }).extend({
permissions: UnpackedPermissionSchema.array().transform((caslPermission) =>
// first map and remove other actions of folder permission
caslPermission
+18 -3
View File
@@ -5,6 +5,7 @@ import {
IdentitiesSchema,
OrganizationsSchema,
OrgMembershipsSchema,
OrgMembershipStatus,
SuperAdminSchema,
UsersSchema
} from "@app/db/schemas";
@@ -279,7 +280,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
);
return {
organizationMembership
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -892,7 +896,13 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const organizationMembership = await server.services.superAdmin.resendOrgInvite(req.params, req.permission);
return { organizationMembership };
return {
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -922,7 +932,12 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
req.params.organizationId,
req.permission
);
return { organizationMembership };
return {
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -1,9 +1,13 @@
import { z } from "zod";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipsSchema,
OrgMembershipStatus,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -13,7 +17,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -66,14 +69,23 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
const { data: memberships } = await server.services.membershipUser.listMemberships({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
data: {}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.workspaceId
}))
};
}
});
@@ -124,15 +136,30 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
id: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const membership = await server.services.membershipUser.getMembershipByUserId({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.workspaceId
}
};
}
});
@@ -241,14 +268,22 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
metadata: data.map(({ actorUserId }) => ({
userId: actorUserId || "",
email: ""
}))
}
});
return { data, success: true };
return {
data: data.map((el) => ({
...el,
orgId: req.permission.orgId,
role: OrgMembershipRole.Member,
status: el.status || OrgMembershipStatus.Accepted
})),
success: true
};
}
});
@@ -282,7 +317,7 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -300,30 +335,28 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId,
roles: req.body.roles
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
},
data: {
roles: req.body.roles
}
});
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: req.params.workspaceId,
// event: {
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
// metadata: {
// userId: membership.userId,
// newRole: req.body.role,
// oldRole: membership.role,
// email: ""
// }
// }
// });
return { roles };
return { roles: membership.roles.map((el) => ({ ...el, projectMembershipId: req.params.membershipId })) };
}
});
@@ -352,13 +385,22 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
}
});
await server.services.auditLog.createAuditLog({
@@ -367,12 +409,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.workspaceId
}
};
}
});
};
@@ -1,19 +1,21 @@
import { z } from "zod";
import {
AccessScope,
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UsersSchema
} from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -54,7 +56,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -73,17 +75,32 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupIdOrName: req.params.groupIdOrName
let groupId = req.params.groupIdOrName;
if (!isUuidV4(req.params.groupIdOrName)) {
const groupDetails = await server.services.convertor.getGroupIdFromName(groupId, req.permission.orgId);
groupId = groupDetails.groupId;
}
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
permission: req.permission,
data: {
groupId,
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -115,7 +132,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -131,17 +148,22 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
groupId: req.params.groupId,
roles: req.body.roles
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
data: {
roles: req.body.roles
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { roles };
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
}
});
@@ -172,16 +194,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupId: req.params.groupId,
projectId: req.params.projectId
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -233,15 +264,17 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
permission: req.permission,
data: {},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMemberships };
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
}
});
@@ -292,15 +325,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.getGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -78,7 +78,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
metadata: {
@@ -45,7 +45,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: {
@@ -40,7 +40,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH,
metadata: {
@@ -40,7 +40,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_GCP_AUTH,
metadata: {
@@ -119,7 +119,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_JWT_AUTH,
metadata: {
@@ -64,7 +64,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH,
metadata: {
@@ -168,7 +168,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
metadata: {
@@ -57,7 +57,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_OCI_AUTH,
metadata: {
@@ -67,7 +67,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_OIDC_AUTH,
metadata: {
@@ -1,10 +1,12 @@
import { z } from "zod";
import {
AccessScope,
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
@@ -14,7 +16,6 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
@@ -56,7 +57,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
@@ -82,16 +83,22 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: roles || [{ role }]
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
identityId: req.params.identityId,
roles: roles || [{ role, isTemporary: false }]
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -130,7 +137,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
@@ -153,16 +160,24 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const roles = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: req.body.roles
const { membership } = await server.services.membershipIdentity.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
},
data: {
roles: req.body.roles
}
});
return { roles };
return {
roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id }))
};
}
});
@@ -193,15 +208,21 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -72,7 +72,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH,
metadata: {
@@ -332,7 +332,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.CREATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -393,7 +393,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.GET_TOKENS_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -447,7 +447,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.UPDATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -59,7 +59,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: {
@@ -1,6 +1,6 @@
import { z } from "zod";
import { OrgMembershipRole, ProjectMembershipRole, UsersSchema } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
import { inviteUserRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -23,13 +23,6 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
.array()
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
organizationId: z.string().trim(),
projects: z
.object({
id: z.string(),
projectRoleSlug: z.string().array().default([ProjectMembershipRole.Member])
})
.array()
.optional(),
organizationRoleSlug: z.string().default(OrgMembershipRole.Member)
}),
response: {
@@ -50,15 +43,16 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const { signupTokens: completeInviteLinks } = await server.services.org.inviteUserToOrganization({
orgId: req.body.organizationId,
actor: req.permission.type,
actorId: req.permission.id,
inviteeEmails: req.body.inviteeEmails,
projects: req.body.projects,
organizationRoleSlug: req.body.organizationRoleSlug,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
const { signUpTokens: completeInviteLinks } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
data: {
usernames: req.body.inviteeEmails,
roles: [{ isTemporary: false, role: req.body.organizationRoleSlug }]
}
});
await server.services.telemetry.sendPostHogEvents({
@@ -86,7 +86,7 @@ export const registerOrgAdminRouter = async (server: FastifyZodProvider) => {
});
}
return { membership };
return { membership: { ...membership, userId: req.permission.id, projectId: req.params.projectId } };
}
});
};
@@ -6,6 +6,7 @@ import {
GroupsSchema,
IncidentContactsSchema,
OrgMembershipsSchema,
OrgMembershipStatus,
OrgRolesSchema,
UsersSchema
} from "@app/db/schemas";
@@ -263,7 +264,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.authMethod,
req.permission.orgId
);
return { users };
return { users: users.map((el) => ({ ...el, status: el.status || OrgMembershipStatus.Accepted })) };
}
});
@@ -1,10 +1,11 @@
import { z } from "zod";
import {
OrgMembershipRole,
AccessScope,
ProjectMembershipRole,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -14,7 +15,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -67,14 +67,23 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { data: memberships } = await server.services.membershipUser.listMemberships({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -125,15 +134,30 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
id: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const membership = await server.services.membershipUser.getMembershipByUserId({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
}
});
@@ -242,20 +266,17 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
const { memberships } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role })),
usernames: usernamesAndEmails
}
});
await server.services.auditLog.createAuditLog({
@@ -263,15 +284,21 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
metadata: memberships.map(({ actorUserId, id }) => ({
userId: actorUserId || "",
membershipId: id,
email: ""
}))
}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -305,7 +332,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -323,17 +350,28 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId,
roles: req.body.roles
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
},
data: {
roles: req.body.roles
}
});
return { roles };
return { roles: membership.roles.map((el) => ({ ...el, projectMembershipId: req.params.membershipId })) };
}
});
@@ -396,13 +434,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
}
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -431,13 +475,22 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
});
await server.services.auditLog.createAuditLog({
@@ -446,12 +499,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
}
});
@@ -479,7 +539,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
projectId: req.params.projectId
});
return { membership };
return {
membership: {
...membership,
userId: membership.actorUserId as string,
projectId: req.params.projectId
}
};
}
});
};
@@ -1,19 +1,21 @@
import { z } from "zod";
import {
AccessScope,
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UsersSchema
} from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -54,7 +56,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -73,17 +75,32 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupIdOrName: req.params.groupIdOrName
let groupId = req.params.groupIdOrName;
if (!isUuidV4(req.params.groupIdOrName)) {
const groupDetails = await server.services.convertor.getGroupIdFromName(groupId, req.permission.orgId);
groupId = groupDetails.groupId;
}
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
permission: req.permission,
data: {
groupId,
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -115,7 +132,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -131,17 +148,22 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
groupId: req.params.groupId,
roles: req.body.roles
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
data: {
roles: req.body.roles
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { roles };
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
}
});
@@ -172,16 +194,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupId: req.params.groupId,
projectId: req.params.projectId
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -233,15 +264,17 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
permission: req.permission,
data: {},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMemberships };
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
}
});
@@ -292,15 +325,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.getGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -1,10 +1,12 @@
import { z } from "zod";
import {
AccessScope,
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
@@ -14,7 +16,6 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
@@ -56,7 +57,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
@@ -82,16 +83,22 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: roles || [{ role }]
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
identityId: req.params.identityId,
roles: roles || [{ role, isTemporary: false }]
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -130,7 +137,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
@@ -153,16 +160,24 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
}
},
handler: async (req) => {
const roles = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: req.body.roles
const { membership } = await server.services.membershipIdentity.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
},
data: {
roles: req.body.roles
}
});
return { roles };
return {
roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id }))
};
}
});
@@ -193,15 +208,21 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -1,6 +1,6 @@
import { z } from "zod";
import { OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
import { AccessScope, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, PROJECT_USERS } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
@@ -51,20 +51,17 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
const { memberships } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
usernames: usernamesAndEmails,
roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role }))
}
});
await server.services.auditLog.createAuditLog({
@@ -72,15 +69,21 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
metadata: memberships.map(({ actorUserId, id }) => ({
userId: actorUserId || "",
membershipId: id,
email: ""
}))
}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -143,13 +146,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
}
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
};
@@ -2,6 +2,7 @@ import { z } from "zod";
import {
OrgMembershipsSchema,
OrgMembershipStatus,
ProjectMembershipsSchema,
ProjectsSchema,
UserEncryptionKeysSchema,
@@ -63,7 +64,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.authMethod,
req.permission.orgId
);
return { users };
return { users: users.map((el) => ({ ...el, status: el.status || OrgMembershipStatus.Accepted })) };
}
});
@@ -138,6 +139,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
membership: OrgMembershipsSchema.extend({
customRoleSlug: z.string().nullish(),
metadata: z
.object({
key: z.string().trim().min(1),
@@ -168,7 +170,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
orgId: req.params.organizationId,
membershipId: req.params.membershipId
});
return { membership };
return { membership: { ...membership, status: membership.status || OrgMembershipStatus.Accepted } };
}
});
@@ -220,7 +222,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId,
...req.body
});
return { membership };
return {
membership: {
...membership,
role: req.body.role || "",
orgId: req.params.organizationId,
status: membership.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -260,7 +269,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
membershipId: req.params.membershipId,
actorOrgId: req.permission.orgId
});
return { membership };
return {
membership: {
...membership,
status: membership.status || OrgMembershipStatus.Accepted,
role: "",
orgId: req.params.organizationId
}
};
}
});
@@ -302,7 +318,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
membershipIds: req.body.membershipIds,
actorOrgId: req.permission.orgId
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
status: el?.status || OrgMembershipStatus.Accepted,
role: "",
orgId: req.params.organizationId
}))
};
}
});
@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>;
export const additionalPrivilegeDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.AdditionalPrivilege);
return orm;
};
@@ -0,0 +1,254 @@
// eslint-disable-next-line simple-import-sort/imports
import { RawRule } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TAdditionalPrivilegeDALFactory } from "./additional-privilege-dal";
import {
TAdditionalPrivilegesScopeFactory,
TCreateAdditionalPrivilegesDTO,
TDeleteAdditionalPrivilegesDTO,
TGetAdditionalPrivilegesByIdDTO,
TGetAdditionalPrivilegesByNameDTO,
TListAdditionalPrivilegesDTO,
TUpdateAdditionalPrivilegesDTO
} from "./additional-privilege-types";
import { newNamespaceAdditionalPrivilegesFactory } from "./namespace/namespace-additional-privilege-factory";
import { newOrgAdditionalPrivilegesFactory } from "./org/org-additional-privilege-factory";
import { newProjectAdditionalPrivilegesFactory } from "./project/project-additional-privilege-factory";
import { ActorType } from "../auth/auth-type";
type TAdditionalPrivilegeServiceFactoryDep = {
additionalPrivilegeDAL: TAdditionalPrivilegeDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
};
export type TAdditionalPrivilegeServiceFactory = ReturnType<typeof additionalPrivilegeServiceFactory>;
export const additionalPrivilegeServiceFactory = ({
additionalPrivilegeDAL,
permissionService,
orgDAL,
membershipDAL
}: TAdditionalPrivilegeServiceFactoryDep) => {
const scopeFactory: Record<AccessScope, TAdditionalPrivilegesScopeFactory> = {
[AccessScope.Organization]: newOrgAdditionalPrivilegesFactory({}),
[AccessScope.Project]: newProjectAdditionalPrivilegesFactory({
membershipDAL,
orgDAL,
permissionService
}),
[AccessScope.Namespace]: newNamespaceAdditionalPrivilegesFactory({})
};
const createAdditionalPrivilege = async (dto: TCreateAdditionalPrivilegesDTO) => {
const { scopeData, data } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onCreateAdditionalPrivilegesGuard(dto);
const scope = factory.getScopeField(dto.scopeData);
const dbActorField = data.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const existingSlug = await additionalPrivilegeDAL.findOne({
name: data.name,
[dbActorField]: data.actorId,
[scope.key]: scope.value
});
if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exists` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
if (!data.isTemporary) {
const additionalPrivilege = await additionalPrivilegeDAL.create({
name: data.name,
[dbActorField]: data.actorId,
[scope.key]: scope.value,
isTemporary: data.isTemporary,
permissions: JSON.stringify(packRules(data.permissions as RawRule[]))
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
}
if (!data.temporaryAccessStartTime || !data.temporaryRange) {
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
}
const relativeTempAllocatedTimeInMs = ms(data.temporaryRange);
const additionalPrivilege = await additionalPrivilegeDAL.create({
[dbActorField]: data.actorId,
[scope.key]: scope.value,
name: data.name,
isTemporary: data.isTemporary,
permissions: JSON.stringify(packRules(data.permissions as RawRule[])),
temporaryAccessEndTime: new Date(
new Date(data.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
),
temporaryAccessStartTime: new Date(data.temporaryAccessStartTime),
temporaryMode: TemporaryPermissionMode.Relative,
temporaryRange: data.temporaryRange
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
const { scopeData, data } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onUpdateAdditionalPrivilegesGuard(dto);
const scope = factory.getScopeField(dto.scopeData);
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const existingPrivilege = await additionalPrivilegeDAL.findOne({
[dbActorField]: dto.selector.actorId,
id: dto.selector.id,
[scope.key]: scope.value
});
if (!existingPrivilege)
throw new NotFoundError({ message: `Additional privilege with id ${dto.selector.id} doesn't exist` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
});
const updatedData = { ...existingPrivilege, ...data };
if (!updatedData.isTemporary) {
const additionalPrivilege = await additionalPrivilegeDAL.updateById(existingPrivilege.id, {
name: updatedData.name,
isTemporary: data.isTemporary,
permissions: data.permissions ? JSON.stringify(packRules(data.permissions as RawRule[])) : undefined
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
}
if (!updatedData.temporaryAccessStartTime || !updatedData.temporaryRange) {
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
}
const relativeTempAllocatedTimeInMs = ms(updatedData.temporaryRange);
const additionalPrivilege = await additionalPrivilegeDAL.updateById(existingPrivilege.id, {
name: updatedData.name,
isTemporary: updatedData.isTemporary,
permissions: JSON.stringify(packRules(updatedData.permissions as RawRule[])),
temporaryAccessEndTime: new Date(
new Date(updatedData.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
),
temporaryAccessStartTime: new Date(updatedData.temporaryAccessStartTime),
temporaryMode: TemporaryPermissionMode.Relative,
temporaryRange: updatedData.temporaryRange
});
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const deleteAdditionalPrivilege = async (dto: TDeleteAdditionalPrivilegesDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onDeleteAdditionalPrivilegesGuard(dto);
const scope = factory.getScopeField(dto.scopeData);
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const existingPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id,
[dbActorField]: dto.selector.actorId,
[scope.key]: scope.value
});
if (!existingPrivilege)
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
const additionalPrivilege = await additionalPrivilegeDAL.deleteById(existingPrivilege.id);
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onGetAdditionalPrivilegesByIdGuard(dto);
const scope = factory.getScopeField(dto.scopeData);
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
id: selector.id,
[dbActorField]: dto.selector.actorId,
[scope.key]: scope.value
});
if (!additionalPrivilege)
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const getAdditionalPrivilegeByName = async (dto: TGetAdditionalPrivilegesByNameDTO) => {
const { scopeData, selector } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onGetAdditionalPrivilegesByIdGuard(dto);
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const scope = factory.getScopeField(dto.scopeData);
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
name: selector.name,
[dbActorField]: dto.selector.actorId,
[scope.key]: scope.value
});
if (!additionalPrivilege)
throw new NotFoundError({ message: `Additional privilege with name ${selector.name} doesn't exist` });
return {
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
};
};
const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => {
const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onListAdditionalPrivilegesGuard(dto);
const scope = factory.getScopeField(dto.scopeData);
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const additionalPrivileges = await additionalPrivilegeDAL.find({
[dbActorField]: dto.selector.actorId,
[scope.key]: scope.value
});
return {
additionalPrivileges: additionalPrivileges.map((el) => ({
...el,
permissions: unpackPermissions(el.permissions)
}))
};
};
return {
createAdditionalPrivilege,
updateAdditionalPrivilege,
deleteAdditionalPrivilege,
getAdditionalPrivilegeById,
getAdditionalPrivilegeByName,
listAdditionalPrivileges
};
};
@@ -0,0 +1,87 @@
import { AccessScopeData, TemporaryPermissionMode } from "@app/db/schemas";
import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "../auth/auth-type";
export interface TAdditionalPrivilegesScopeFactory {
onCreateAdditionalPrivilegesGuard: (arg: TCreateAdditionalPrivilegesDTO) => Promise<void>;
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<void>;
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<void>;
onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<void>;
onGetAdditionalPrivilegesByIdGuard: (
arg: TGetAdditionalPrivilegesByIdDTO | TGetAdditionalPrivilegesByNameDTO
) => Promise<void>;
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
}
export type TCreateAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
data: {
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
name: string;
permissions: unknown;
isTemporary: boolean;
temporaryMode?: TemporaryPermissionMode.Relative;
temporaryRange?: string;
temporaryAccessStartTime?: string;
};
};
export type TUpdateAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
};
data: Partial<{
name: string;
permissions: unknown;
isTemporary: boolean;
temporaryMode?: TemporaryPermissionMode.Relative;
temporaryRange?: string;
temporaryAccessStartTime?: string;
}>;
};
export type TListAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
};
};
export type TDeleteAdditionalPrivilegesDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
};
};
export type TGetAdditionalPrivilegesByIdDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
id: string;
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
};
};
export type TGetAdditionalPrivilegesByNameDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
name: string;
actorId: string;
actorType: ActorType.USER | ActorType.IDENTITY;
};
};
@@ -0,0 +1,52 @@
import { AccessScope } from "@app/db/schemas";
import { InternalServerError } from "@app/lib/errors";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TNamespaceAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
export const newNamespaceAdditionalPrivilegesFactory = (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
deps: TNamespaceAdditionalPrivilegesScopeFactoryDep
): TAdditionalPrivilegesScopeFactory => {
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Namespace) {
return { key: "namespaceId" as const, value: dto.namespaceId };
}
throw new InternalServerError({ message: "Invalid scope provided for the namespace factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges create not implemented" });
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges update not implemented" });
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges delete not implemented" });
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges list not implemented" });
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace additional privileges get by id not implemented" });
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};
@@ -0,0 +1,52 @@
import { AccessScope } from "@app/db/schemas";
import { InternalServerError } from "@app/lib/errors";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TOrgAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
export const newOrgAdditionalPrivilegesFactory = (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
deps: TOrgAdditionalPrivilegesScopeFactoryDep
): TAdditionalPrivilegesScopeFactory => {
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) {
return { key: "orgId" as const, value: dto.orgId };
}
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges create not implemented" });
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges update not implemented" });
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges delete not implemented" });
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges list not implemented" });
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async () => {
throw new InternalServerError({ message: "Org additional privileges get by id not implemented" });
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};
@@ -0,0 +1,219 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope, ActionProjectType } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionIdentityActions,
ProjectPermissionMemberActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { BadRequestError, PermissionBoundaryError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { TMembershipDALFactory } from "@app/services/membership/membership-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
type TProjectAdditionalPrivilegesScopeFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
};
export const newProjectAdditionalPrivilegesFactory = ({
permissionService,
orgDAL,
membershipDAL
}: TProjectAdditionalPrivilegesScopeFactoryDep): TAdditionalPrivilegesScopeFactory => {
const $getPermission = (permission: OrgServiceActor, projectId: string) => {
return permissionService.getProjectPermission({
actor: permission.type,
actorId: permission.id,
actionProjectType: ActionProjectType.Any,
actorAuthMethod: permission.authMethod,
projectId,
actorOrgId: permission.orgId
});
};
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Project) {
return { key: "projectId" as const, value: dto.projectId };
}
throw new BadRequestError({ message: "Invalid scope provided for the factory" });
};
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const { actorType } = dto.data;
const { permission } = await $getPermission(dto.permission, scope.value);
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Edit, ProjectPermissionSub.Identity] as const);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
const { permission: targetUserPermission, memberships } = await $getPermission(
{ ...dto.permission, type: actorType, id: dto.data.actorId },
scope.value
);
const permissionAction =
actorType === ActorType.USER
? ProjectPermissionMemberActions.GrantPrivileges
: ProjectPermissionIdentityActions.GrantPrivileges;
const permissionSubject =
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged actor",
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const membership = memberships.find(
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.data.actorId
);
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
};
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const { actorType } = dto.selector;
const { permission } = await $getPermission(dto.permission, scope.value);
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Edit, ProjectPermissionSub.Identity] as const);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
const { permission: targetUserPermission, memberships } = await $getPermission(
{ ...dto.permission, type: actorType, id: dto.selector.actorId },
scope.value
);
const permissionAction =
actorType === ActorType.USER
? ProjectPermissionMemberActions.GrantPrivileges
: ProjectPermissionIdentityActions.GrantPrivileges;
const permissionSubject =
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject,
permission,
targetUserPermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update more privileged actor",
shouldUseNewPrivilegeSystem,
permissionAction,
permissionSubject
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const membership = memberships.find(
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.selector.actorId
);
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
};
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const { actorType } = dto.selector;
const { permission } = await $getPermission(dto.permission, scope.value);
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Edit, ProjectPermissionSub.Identity] as const);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
};
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] = async (
dto
) => {
const scope = getScopeField(dto.scopeData);
const { actorType } = dto.selector;
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
};
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
async (dto) => {
const scope = getScopeField(dto.scopeData);
const { actorType } = dto.selector;
const permissionSet =
actorType === ActorType.USER
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
const { permission } = await $getPermission(dto.permission, scope.value);
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
const membership = await membershipDAL.findOne({
scopeOrgId: dto.permission.orgId,
scopeProjectId: scope.value,
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
});
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
};
return {
onCreateAdditionalPrivilegesGuard,
onUpdateAdditionalPrivilegesGuard,
onDeleteAdditionalPrivilegesGuard,
onListAdditionalPrivilegesGuard,
onGetAdditionalPrivilegesByIdGuard,
getScopeField
};
};
@@ -1,12 +1,12 @@
import { Knex } from "knex";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal";
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
@@ -14,7 +14,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@@ -80,7 +80,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@@ -208,9 +208,10 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,
scopeOrgId: token.organizationId,
scope: AccessScope.Organization
});
if (!orgMembership) {
+45 -21
View File
@@ -1,6 +1,13 @@
import { Knex } from "knex";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
TUsers,
UserDeviceSchema
} from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env";
@@ -14,11 +21,12 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TNotificationServiceFactory } from "../notification/notification-service";
import { NotificationType } from "../notification/notification-types";
import { TOrgDALFactory } from "../org/org-dal";
import { getDefaultOrgMembershipRole } from "../org/org-role-fns";
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { LoginMethod } from "../super-admin/super-admin-types";
import { TTotpServiceFactory } from "../totp/totp-service";
@@ -48,7 +56,8 @@ type TAuthLoginServiceFactoryDep = {
smtpService: TSmtpService;
totpService: Pick<TTotpServiceFactory, "verifyUserTotp" | "verifyWithUserRecoveryCode">;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgMembershipDAL: TOrgMembershipDALFactory;
membershipUserDAL: TMembershipUserDALFactory;
membershipRoleDAL: TMembershipRoleDALFactory;
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
};
@@ -58,10 +67,11 @@ export const authLoginServiceFactory = ({
tokenService,
smtpService,
orgDAL,
orgMembershipDAL,
totpService,
auditLogService,
notificationService
notificationService,
membershipUserDAL,
membershipRoleDAL
}: TAuthLoginServiceFactoryDep) => {
/*
* Private
@@ -163,8 +173,8 @@ export const authLoginServiceFactory = ({
if (organizationId) {
const org = await orgDAL.findById(organizationId);
if (org) {
await orgMembershipDAL.update(
{ userId: user.id, orgId: org.id },
await membershipUserDAL.update(
{ actorUserId: user.id, scopeOrgId: org.id, scope: AccessScope.Organization },
{ lastLoginAuthMethod: authMethod, lastLoginTime: new Date() }
);
if (org.userTokenExpiration) {
@@ -858,21 +868,34 @@ export const authLoginServiceFactory = ({
}
orgId = defaultOrg.id;
const [orgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(defaultOrg.defaultMembershipRole);
await orgMembershipDAL.create({
userId: user.id,
inviteEmail: email,
orgId,
role,
roleId,
status: OrgMembershipStatus.Accepted,
isActive: true
await membershipUserDAL.transaction(async (tx) => {
const membership = await membershipUserDAL.create(
{
actorUserId: user?.id,
inviteEmail: email,
scopeOrgId: orgId,
scope: AccessScope.Organization,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role,
customRoleId: roleId
},
tx
);
});
}
}
@@ -895,10 +918,11 @@ export const authLoginServiceFactory = ({
if (org) {
// checks for the membership and only sets the orgId / orgName if the user is a member of the specified org
const orgMembership = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.OrgMembership}.orgId` as "orgId"]: org.id,
[`${TableName.OrgMembership}.isActive` as "isActive"]: true,
[`${TableName.OrgMembership}.status` as "status"]: OrgMembershipStatus.Accepted
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: org.id,
[`${TableName.Membership}.isActive` as "isActive"]: true,
[`${TableName.Membership}.status` as "status"]: OrgMembershipStatus.Accepted,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (orgMembership) {
@@ -1,3 +1,4 @@
import { AccessScope } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
@@ -6,7 +7,7 @@ import { OrgServiceActor } from "@app/lib/types";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TTotpConfigDALFactory } from "../totp/totp-config-dal";
import { TUserDALFactory } from "../user/user-dal";
@@ -23,7 +24,7 @@ import { ActorType, AuthMethod, AuthTokenType } from "./auth-type";
type TAuthPasswordServiceFactoryDep = {
authDAL: TAuthDALFactory;
userDAL: TUserDALFactory;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "find">;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
totpConfigDAL: Pick<TTotpConfigDALFactory, "delete">;
@@ -33,7 +34,7 @@ export type TAuthPasswordFactory = ReturnType<typeof authPaswordServiceFactory>;
export const authPaswordServiceFactory = ({
authDAL,
userDAL,
orgMembershipDAL,
membershipUserDAL,
tokenService,
smtpService,
totpConfigDAL
@@ -54,7 +55,10 @@ export const authPaswordServiceFactory = ({
const hasEmailAuth = user.authMethods?.includes(AuthMethod.EMAIL);
if (!hasEmailAuth) {
const orgMemberships = await orgMembershipDAL.find({ userId: user.id });
const orgMemberships = await membershipUserDAL.find({
actorUserId: user.id,
scope: AccessScope.Organization
});
const lastLoginMethod =
orgMemberships
.filter((membership) => membership.lastLoginAuthMethod)
@@ -1,4 +1,4 @@
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
import { AccessScope, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -8,17 +8,15 @@ import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { getMinExpiresIn } from "@app/lib/fn";
import { isDisposableEmail } from "@app/lib/validator";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
import { TokenType } from "../auth-token/auth-token-types";
import { TMembershipGroupDALFactory } from "../membership-group/membership-group-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TOrgServiceFactory } from "../org/org-service";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { getServerCfg } from "../super-admin/super-admin-service";
import { TUserDALFactory } from "../user/user-dal";
@@ -42,14 +40,12 @@ type TAuthSignupDep = {
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById" | "findById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
orgDAL: TOrgDALFactory;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "transaction" | "insertMany">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
membershipGroupDAL: TMembershipGroupDALFactory;
};
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
@@ -60,11 +56,11 @@ export const authSignupServiceFactory = ({
projectKeyDAL,
projectDAL,
projectBotDAL,
groupProjectDAL,
tokenService,
smtpService,
orgService,
orgDAL,
membershipGroupDAL,
licenseService
}: TAuthSignupDep) => {
// first step of signup. create user and send email
@@ -200,9 +196,10 @@ export const authSignupServiceFactory = ({
organizationId
) {
const [pendingOrgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
status: OrgMembershipStatus.Invited,
[`${TableName.OrgMembership}.orgId` as "orgId"]: organizationId
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: organizationId,
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
});
if (pendingOrgMembership) {
@@ -241,18 +238,18 @@ export const authSignupServiceFactory = ({
}
const updatedMembersips = await orgDAL.updateMembership(
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
{ userId: user.id, status: OrgMembershipStatus.Accepted }
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
{ actorUserId: user.id, status: OrgMembershipStatus.Accepted }
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
await convertPendingGroupAdditionsToGroupMemberships({
userIds: [user.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
membershipGroupDAL,
projectDAL,
projectBotDAL
});
@@ -351,21 +348,21 @@ export const authSignupServiceFactory = ({
);
const updatedMembersips = await orgDAL.updateMembership(
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
{ userId: us.id, status: OrgMembershipStatus.Accepted },
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
{ actorUserId: us.id, status: OrgMembershipStatus.Accepted },
tx
);
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId, tx)));
await convertPendingGroupAdditionsToGroupMemberships({
userIds: [user.id],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
membershipGroupDAL,
tx
});
@@ -0,0 +1,130 @@
import { AccessScope } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { NotFoundError } from "@app/lib/errors";
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TProjectDALFactory } from "../project/project-dal";
type TConvertorServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne">;
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
groupDAL: Pick<TGroupDALFactory, "findOne">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "findOne">;
};
export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>;
export const convertorServiceFactory = ({
projectDAL,
membershipDAL,
additionalPrivilegeDAL,
groupDAL
}: TConvertorServiceFactoryDep) => {
const projectSlugToId = async (dto: { slug: string; orgId: string }) => {
const project = await projectDAL.findOne({
orgId: dto.orgId,
slug: dto.slug
});
if (!project) throw new NotFoundError({ message: `Project with slug ${dto.slug} not found` });
return project;
};
const userMembershipIdToUserId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope,
id: membershipId,
scopeOrgId: orgId
});
if (!membership || !membership.actorUserId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return { userId: membership.actorUserId, membership };
};
const groupMembershipIdToGroupId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope,
id: membershipId,
scopeOrgId: orgId
});
if (!membership || !membership.actorGroupId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return { groupId: membership.actorGroupId, membership };
};
const identityMembershipIdToIdentityId = async (membershipId: string, scope: AccessScope, orgId: string) => {
const membership = await membershipDAL.findOne({
scope,
id: membershipId,
scopeOrgId: orgId
});
if (!membership || !membership.actorIdentityId) {
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
}
return { identityId: membership.actorIdentityId, membership };
};
const identityIdToMembershipId = async (identityId: string, scope: AccessScope, scopeId: string) => {
let fieldName = "scopeOrgId";
if (scope === AccessScope.Project) {
fieldName = "scopeProjectId";
} else if (scope === AccessScope.Namespace) {
fieldName = "scopeNamespaceId";
}
const membership = await membershipDAL.findOne({
scope,
actorIdentityId: identityId,
[fieldName]: scopeId
});
if (!membership) {
throw new NotFoundError({ message: `Identity with id ${identityId} not found` });
}
return { membershipId: membership.id, membership };
};
const additionalPrivilegeIdToDoc = async (privilegeId: string) => {
const doc = await additionalPrivilegeDAL.findOne({
id: privilegeId
});
if (!doc) {
throw new NotFoundError({ message: `Privilege with id ${privilegeId} not found` });
}
return { privilege: doc };
};
const additionalPrivilegeNameToDoc = async (privilegeName: string, projectId: string) => {
const privilege = await additionalPrivilegeDAL.findOne({
name: privilegeName,
projectId
});
if (!privilege) {
throw new NotFoundError({ message: `Privilege with name ${privilegeName} not found` });
}
return { privilegeId: privilege.id, privilege };
};
const getGroupIdFromName = async (name: string, orgId: string) => {
const group = await groupDAL.findOne({ orgId, name });
if (!group) throw new NotFoundError({ message: `Failed to find group with name ${name}` });
return { groupId: group.id, group };
};
return {
projectSlugToId,
userMembershipIdToUserId,
groupMembershipIdToGroupId,
identityMembershipIdToIdentityId,
additionalPrivilegeIdToDoc,
additionalPrivilegeNameToDoc,
identityIdToMembershipId,
getGroupIdFromName
};
};
@@ -1,19 +1,19 @@
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
import { OrgMembershipRole, TRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
import { TRoleDALFactory } from "../role/role-dal";
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
export const constructGroupOrgMembershipRoleMappings = async ({
mappingsDTO,
orgId,
orgRoleDAL,
roleDAL,
licenseService
}: {
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
orgRoleDAL: TOrgRoleDALFactory;
roleDAL: TRoleDALFactory;
licenseService: TLicenseServiceFactory;
orgId: string;
}) => {
@@ -30,9 +30,9 @@ export const constructGroupOrgMembershipRoleMappings = async ({
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
.map((mapping) => mapping.roleSlug);
let customRolesMap: Map<string, TOrgRoles> = new Map();
let customRolesMap: Map<string, TRoles> = new Map();
if (customRoleSlugs.length > 0) {
const customRoles = await orgRoleDAL.find({
const customRoles = await roleDAL.find({
orgId,
$in: {
slug: customRoleSlugs
@@ -6,15 +6,15 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { OrgServiceActor } from "@app/lib/types";
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TRoleDALFactory } from "../role/role-dal";
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
permissionService: TPermissionServiceFactory;
licenseService: TLicenseServiceFactory;
orgRoleDAL: TOrgRoleDALFactory;
roleDAL: TRoleDALFactory;
};
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
@@ -23,7 +23,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
externalGroupOrgRoleMappingDAL,
licenseService,
permissionService,
orgRoleDAL
roleDAL
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
@@ -61,7 +61,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
const mappings = await constructGroupOrgMembershipRoleMappings({
mappingsDTO: dto.mappings,
orgRoleDAL,
roleDAL,
licenseService,
orgId: actor.orgId
});

Some files were not shown because too many files have changed in this diff Show More