Merge pull request #4701 from Infisical/feat/sub-org

feat: sub organization
This commit is contained in:
Scott Wilson
2025-10-21 14:18:48 -07:00
committed by GitHub
200 changed files with 5621 additions and 2275 deletions
+4
View File
@@ -48,6 +48,7 @@ import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh
import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { TSubOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types"; import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity"; import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service"; import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service";
@@ -182,6 +183,8 @@ declare module "fastify" {
type: ActorType; type: ActorType;
id: string; id: string;
orgId: string; orgId: string;
parentOrgId: string;
rootOrgId: string;
}; };
rateLimits: RateLimitConfiguration; rateLimits: RateLimitConfiguration;
// passport data // passport data
@@ -335,6 +338,7 @@ declare module "fastify" {
additionalPrivilege: TAdditionalPrivilegeServiceFactory; additionalPrivilege: TAdditionalPrivilegeServiceFactory;
role: TRoleServiceFactory; role: TRoleServiceFactory;
convertor: TConvertorServiceFactory; convertor: TConvertorServiceFactory;
subOrganization: TSubOrgServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer
@@ -0,0 +1,66 @@
import { Knex } from "knex";
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
import { AccessScope, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasParentOrgId = await knex.schema.hasColumn(TableName.Organization, "parentOrgId");
if (!hasParentOrgId) {
await knex.schema.alterTable(TableName.Organization, async (t) => {
// the one just above the chain
t.uuid("parentOrgId");
t.foreign("parentOrgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
// this would root organization containing various informations like billing etc
t.uuid("rootOrgId");
t.foreign("rootOrgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
await dropConstraintIfExists(TableName.Organization, "organizations_slug_unique", knex);
t.unique(["rootOrgId", "parentOrgId", "slug"]);
});
// had to switch to raw for null not distinct
}
const hasIdentityOrgCol = await knex.schema.hasColumn(TableName.Identity, "orgId");
if (!hasIdentityOrgCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
await knex.raw(
`
UPDATE ?? AS identity
SET "orgId" = membership."scopeOrgId"
FROM ?? AS membership
WHERE
membership."actorIdentityId" = identity."id"
AND membership."scope" = ?
`,
[TableName.Identity, TableName.Membership, AccessScope.Organization]
);
await knex.schema.alterTable(TableName.Identity, (t) => {
t.uuid("orgId").notNullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasParentOrgId = await knex.schema.hasColumn(TableName.Organization, "parentOrgId");
const hasRootOrgId = await knex.schema.hasColumn(TableName.Organization, "rootOrgId");
if (hasParentOrgId || hasRootOrgId) {
await knex.schema.alterTable(TableName.Organization, (t) => {
if (hasParentOrgId) t.dropColumn("parentOrgId");
if (hasRootOrgId) t.dropColumn("rootOrgId");
});
}
const hasIdentityOrgCol = await knex.schema.hasColumn(TableName.Identity, "orgId");
if (hasIdentityOrgCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.dropColumn("orgId");
});
}
}
+2 -1
View File
@@ -13,7 +13,8 @@ export const IdentitiesSchema = z.object({
authMethod: z.string().nullable().optional(), authMethod: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
hasDeleteProtection: z.boolean().default(false) hasDeleteProtection: z.boolean().default(false),
orgId: z.string().uuid()
}); });
export type TIdentities = z.infer<typeof IdentitiesSchema>; export type TIdentities = z.infer<typeof IdentitiesSchema>;
+6
View File
@@ -316,6 +316,12 @@ export enum ActionProjectType {
Any = "any" Any = "any"
} }
export enum OrganizationActionScope {
ChildOrganization = "child-organization-only",
ParentOrganization = "parent-organization-only",
Any = "any"
}
export enum TemporaryPermissionMode { export enum TemporaryPermissionMode {
Relative = "relative" Relative = "relative"
} }
+3 -1
View File
@@ -38,7 +38,9 @@ export const OrganizationsSchema = z.object({
maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(), maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(),
maxSharedSecretViewLimit: z.number().nullable().optional(), maxSharedSecretViewLimit: z.number().nullable().optional(),
googleSsoAuthEnforced: z.boolean().default(false), googleSsoAuthEnforced: z.boolean().default(false),
googleSsoAuthLastUsed: z.date().nullable().optional() googleSsoAuthLastUsed: z.date().nullable().optional(),
parentOrgId: z.string().uuid().nullable().optional(),
rootOrgId: z.string().uuid().nullable().optional()
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
+2 -1
View File
@@ -24,7 +24,8 @@ export async function seed(knex: Knex): Promise<void> {
// @ts-ignore // @ts-ignore
id: seedData1.machineIdentity.id, id: seedData1.machineIdentity.id,
name: seedData1.machineIdentity.name, name: seedData1.machineIdentity.name,
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
orgId: seedData1.organization.id
} }
]); ]);
const identityUa = await knex(TableName.IdentityUniversalAuth) const identityUa = await knex(TableName.IdentityUniversalAuth)
+2
View File
@@ -48,12 +48,14 @@ import { registerSshCertRouter } from "./ssh-certificate-router";
import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router"; import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router";
import { registerSshHostGroupRouter } from "./ssh-host-group-router"; import { registerSshHostGroupRouter } from "./ssh-host-group-router";
import { registerSshHostRouter } from "./ssh-host-router"; import { registerSshHostRouter } from "./ssh-host-router";
import { registerSubOrgRouter } from "./sub-org-router";
import { registerTrustedIpRouter } from "./trusted-ip-router"; import { registerTrustedIpRouter } from "./trusted-ip-router";
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router"; import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
export const registerV1EERoutes = async (server: FastifyZodProvider) => { export const registerV1EERoutes = async (server: FastifyZodProvider) => {
// org role starts with organization // org role starts with organization
await server.register(registerOrgRoleRouter, { prefix: "/organization" }); await server.register(registerOrgRoleRouter, { prefix: "/organization" });
await server.register(registerSubOrgRouter, { prefix: "/sub-organizations" });
await server.register(registerLicenseRouter, { prefix: "/organizations" }); await server.register(registerLicenseRouter, { prefix: "/organizations" });
// depreciated in favour of infisical workspace // depreciated in favour of infisical workspace
+1 -1
View File
@@ -58,7 +58,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const plan = await server.services.license.getOrgPlan({ const plan = await server.services.license.getOrgPlan({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.rootOrgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId, orgId: req.params.organizationId,
refreshCache: req.query.refreshCache refreshCache: req.query.refreshCache
+34 -1
View File
@@ -3,12 +3,35 @@ import { z } from "zod";
import { AccessScope, OrgMembershipRole, 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 { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { OrgPermissionSchema } from "@app/ee/services/permission/org-permission"; import { OrgPermissionSchema, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { BadRequestError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
const INVALID_SUBORG_PERMISSIONS = [
OrgPermissionSubjects.Sso,
OrgPermissionSubjects.Ldap,
OrgPermissionSubjects.Scim,
OrgPermissionSubjects.GithubOrgSync,
OrgPermissionSubjects.GithubOrgSyncManual,
OrgPermissionSubjects.Billing,
OrgPermissionSubjects.SubOrganization
];
const validateSubOrganizationSubjects = (permissions: unknown) => {
const invalidPermissionSubjects = (permissions as { subject: OrgPermissionSubjects }[])
.filter((el) => INVALID_SUBORG_PERMISSIONS.includes(el.subject))
.map((el) => el.subject);
if (invalidPermissionSubjects.length) {
const deduplication = Array.from(new Set(invalidPermissionSubjects));
throw new BadRequestError({
message: `Suborganization contains invalid permission subjects: ${deduplication.join(",")}`
});
}
};
export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
@@ -37,6 +60,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const isSubOrganization = req.permission.rootOrgId !== req.permission.orgId;
if (isSubOrganization) {
validateSubOrganizationSubjects(req.body.permissions);
}
const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions)); const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions));
const role = await server.services.role.createRole({ const role = await server.services.role.createRole({
permission: req.permission, permission: req.permission,
@@ -133,6 +161,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const isSubOrganization = req.permission.rootOrgId !== req.permission.orgId;
if (isSubOrganization && req.body.permissions) {
validateSubOrganizationSubjects(req.body.permissions);
}
const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined; const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined;
const role = await server.services.role.updateRole({ const role = await server.services.role.updateRole({
permission: req.permission, permission: req.permission,
+163
View File
@@ -0,0 +1,163 @@
import { z } from "zod";
import { OrganizationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, SUB_ORGANIZATIONS } from "@app/lib/api-docs";
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 { AuthMode } from "@app/services/auth/auth-type";
const sanitizedSubOrganizationSchema = OrganizationsSchema.pick({
id: true,
name: true,
slug: true,
createdAt: true,
updatedAt: true,
parentOrgId: true
});
export const registerSubOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "Create a sub organization",
security: [
{
bearerAuth: []
}
],
body: z.object({
name: slugSchema().describe(SUB_ORGANIZATIONS.CREATE.name)
}),
response: {
200: z.object({
organization: sanitizedSubOrganizationSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organization } = await server.services.subOrganization.createSubOrg({
name: req.body.name,
permissionActor: req.permission
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.CREATE_SUB_ORGANIZATION,
metadata: {
name: req.body.name,
organizationId: organization.id
}
}
});
return { organization };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "List of sub organizations",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
limit: z.coerce.number().min(1).max(1000).default(25).describe(SUB_ORGANIZATIONS.LIST.limit),
offset: z.coerce.number().min(0).default(0).describe(SUB_ORGANIZATIONS.LIST.offset),
isAccessible: z
.enum(["true", "false"])
.optional()
.transform((value) => value === "true")
.describe(SUB_ORGANIZATIONS.LIST.isAccessible)
}),
response: {
200: z.object({
organizations: sanitizedSubOrganizationSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organizations } = await server.services.subOrganization.listSubOrgs({
permissionActor: req.permission,
data: {
limit: req.query.limit,
offset: req.query.offset,
isAccessible: req.query.isAccessible
}
});
return { organizations };
}
});
server.route({
method: "PATCH",
url: "/:subOrgId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "Update a sub organization",
security: [
{
bearerAuth: []
}
],
params: z.object({
subOrgId: z.string().trim().describe(SUB_ORGANIZATIONS.UPDATE.subOrgId)
}),
body: z.object({
name: slugSchema().describe(SUB_ORGANIZATIONS.UPDATE.name)
}),
response: {
200: z.object({
organization: sanitizedSubOrganizationSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organization } = await server.services.subOrganization.updateSubOrg({
subOrgId: req.params.subOrgId,
name: req.body.name,
permissionActor: req.permission
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.UPDATE_SUB_ORGANIZATION,
metadata: {
name: req.body.name,
organizationId: organization.id
}
}
});
return { organization };
}
});
};
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { TAuditLogs } from "@app/db/schemas"; import { OrganizationActionScope, TAuditLogs } from "@app/db/schemas";
import { import {
decryptLogStream, decryptLogStream,
decryptLogStreamCredentials, decryptLogStreamCredentials,
@@ -45,13 +45,14 @@ export const auditLogStreamServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -94,13 +95,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
logStream.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -160,13 +162,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
logStream.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
@@ -185,14 +188,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission({
const { permission } = await permissionService.getOrgPermission( scope: OrganizationActionScope.Any,
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
logStream.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -206,13 +209,14 @@ export const auditLogStreamServiceFactory = ({
}; };
const list = async (actor: OrgServiceActor) => { const list = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { requestContext } from "@fastify/request-context"; import { requestContext } from "@fastify/request-context";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
@@ -47,13 +47,14 @@ export const auditLogServiceFactory = ({
); );
} else { } else {
// Organization-wide logs // Organization-wide logs
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAuditLogsActions.Read, OrgPermissionAuditLogsActions.Read,
@@ -173,6 +173,9 @@ export enum EventType {
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth", UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth", GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
CREATE_SUB_ORGANIZATION = "create-sub-organization",
UPDATE_SUB_ORGANIZATION = "update-sub-organization",
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth", ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth", UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth", GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
@@ -616,6 +619,22 @@ interface GetSecretsEvent {
}; };
} }
interface CreateSubOrganizationEvent {
type: EventType.CREATE_SUB_ORGANIZATION;
metadata: {
name: string;
organizationId: string;
};
}
interface UpdateSubOrganizationEvent {
type: EventType.UPDATE_SUB_ORGANIZATION;
metadata: {
name: string;
organizationId: string;
};
}
type TSecretMetadata = { key: string; value: string }[]; type TSecretMetadata = { key: string; value: string }[];
interface GetSecretEvent { interface GetSecretEvent {
@@ -3964,6 +3983,8 @@ interface PamResourceDeleteEvent {
} }
export type Event = export type Event =
| CreateSubOrganizationEvent
| UpdateSubOrganizationEvent
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
| CreateSecretEvent | CreateSecretEvent
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -134,13 +134,14 @@ export const dynamicSecretServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
gateway?.orgId ?? gatewayv2?.orgId, orgId: gateway?.orgId || gatewayv2?.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
@@ -297,13 +298,14 @@ export const dynamicSecretServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: gateway?.orgId || gatewayv2?.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
@@ -3,6 +3,7 @@ import { STSServiceException } from "@aws-sdk/client-sts";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { OrganizationActionScope } from "@app/db/schemas";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
@@ -51,13 +52,14 @@ export const externalKmsServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod actorAuthMethod
}: TCreateExternalKmsDTO) => { }: TCreateExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Kms);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -154,13 +156,14 @@ export const externalKmsServiceFactory = ({
actorAuthMethod actorAuthMethod
}: TUpdateExternalKmsDTO) => { }: TUpdateExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
const plan = await licenseService.getPlan(kmsDoc.orgId); const plan = await licenseService.getPlan(kmsDoc.orgId);
@@ -257,13 +260,14 @@ export const externalKmsServiceFactory = ({
const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => { const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
@@ -278,13 +282,14 @@ export const externalKmsServiceFactory = ({
}; };
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => { const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId }); const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
@@ -294,13 +299,14 @@ export const externalKmsServiceFactory = ({
const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => { const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
@@ -342,13 +348,14 @@ export const externalKmsServiceFactory = ({
name: kmsName name: kmsName
}: TGetExternalKmsBySlugDTO) => { }: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId }); const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
@@ -3,7 +3,7 @@ import net from "node:net";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { OrgMembershipRole, TRelays } from "@app/db/schemas"; import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore"; import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { DatabaseErrorCode } from "@app/lib/error-codes"; import { DatabaseErrorCode } from "@app/lib/error-codes";
@@ -73,13 +73,14 @@ export const gatewayV2ServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId, actorId,
orgId, orgId,
actorAuthMethod, actorAuthMethod,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -258,13 +259,14 @@ export const gatewayV2ServiceFactory = ({
}; };
const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
@@ -815,13 +817,14 @@ export const gatewayV2ServiceFactory = ({
throw new NotFoundError({ message: `Gateway ${id} not found` }); throw new NotFoundError({ message: `Gateway ${id} not found` });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
gateway.orgId, orgId: gateway.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.DeleteGateways, OrgPermissionGatewayActions.DeleteGateways,
@@ -845,13 +848,14 @@ export const gatewayV2ServiceFactory = ({
}; };
const getPamSessionKey = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { const getPamSessionKey = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { z } from "zod"; import { z } from "zod";
import { OrganizationActionScope } from "@app/db/schemas";
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
@@ -68,13 +69,14 @@ export const gatewayServiceFactory = ({
"Gateway handshake failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan." "Gateway handshake failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
actorId, actorId,
orgId, orgId,
actorAuthMethod, actorAuthMethod,
orgId actorOrgId: orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -480,13 +482,14 @@ export const gatewayServiceFactory = ({
}; };
const listGateways = async ({ orgPermission }: TListGatewaysDTO) => { const listGateways = async ({ orgPermission }: TListGatewaysDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -501,13 +504,14 @@ export const gatewayServiceFactory = ({
}; };
const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => { const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -521,13 +525,14 @@ export const gatewayServiceFactory = ({
}; };
const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => { const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.EditGateways, OrgPermissionGatewayActions.EditGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -542,13 +547,14 @@ export const gatewayServiceFactory = ({
}; };
const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => { const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.DeleteGateways, OrgPermissionGatewayActions.DeleteGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -6,7 +6,7 @@ import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
import { Octokit as OctokitRest } from "@octokit/rest"; import { Octokit as OctokitRest } from "@octokit/rest";
import RE2 from "re2"; import RE2 from "re2";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -104,13 +104,14 @@ export const githubOrgSyncServiceFactory = ({
githubOrgAccessToken, githubOrgAccessToken,
isActive isActive
}: TCreateGithubOrgSyncDTO) => { }: TCreateGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, scope: OrganizationActionScope.ParentOrganization,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, actorId: orgPermission.id,
orgPermission.authMethod, orgId: orgPermission.orgId,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
const plan = await licenseService.getPlan(orgPermission.orgId); const plan = await licenseService.getPlan(orgPermission.orgId);
@@ -162,13 +163,14 @@ export const githubOrgSyncServiceFactory = ({
githubOrgAccessToken, githubOrgAccessToken,
isActive isActive
}: TUpdateGithubOrgSyncDTO) => { }: TUpdateGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, scope: OrganizationActionScope.ParentOrganization,
orgPermission.orgId, actorId: orgPermission.id,
orgPermission.authMethod, orgId: orgPermission.orgId,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
const plan = await licenseService.getPlan(orgPermission.orgId); const plan = await licenseService.getPlan(orgPermission.orgId);
@@ -226,13 +228,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => { const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
@@ -256,13 +259,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => { const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actorId: orgPermission.id,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
@@ -422,13 +426,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const validateGithubToken = async ({ orgPermission, githubOrgAccessToken }: TValidateGithubTokenDTO) => { const validateGithubToken = async ({ orgPermission, githubOrgAccessToken }: TValidateGithubTokenDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actorId: orgPermission.id,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
@@ -509,13 +514,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const syncAllTeams = async ({ orgPermission }: TSyncAllTeamsDTO): Promise<TSyncResult> => { const syncAllTeams = async ({ orgPermission }: TSyncAllTeamsDTO): Promise<TSyncResult> => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, scope: OrganizationActionScope.ParentOrganization,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorId: orgPermission.id,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit, OrgPermissionActions.Edit,
+31 -24
View File
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { AccessScope, OrgMembershipRole, TRoles } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole, TRoles } from "@app/db/schemas";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal"; import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -73,13 +73,14 @@ export const groupServiceFactory = ({
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => { const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -167,13 +168,14 @@ export const groupServiceFactory = ({
}: TUpdateGroupDTO) => { }: TUpdateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
@@ -270,13 +272,14 @@ export const groupServiceFactory = ({
const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => { const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -297,17 +300,18 @@ export const groupServiceFactory = ({
const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => { const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const group = await groupDAL.findById(id); const group = await groupDAL.findById(id);
if (!group) { if (!group || group.orgId !== actorOrgId) {
throw new NotFoundError({ throw new NotFoundError({
message: `Cannot find group with ID ${id}` message: `Cannot find group with ID ${id}`
}); });
@@ -330,13 +334,14 @@ export const groupServiceFactory = ({
}: TListGroupUsersDTO) => { }: TListGroupUsersDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const group = await groupDAL.findOne({ const group = await groupDAL.findOne({
@@ -365,13 +370,14 @@ export const groupServiceFactory = ({
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => { const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists // check if group with slug exists
@@ -451,13 +457,14 @@ export const groupServiceFactory = ({
}: TRemoveUserFromGroupDTO) => { }: TRemoveUserFromGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists // check if group with slug exists
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { import {
@@ -68,13 +69,14 @@ export const identityAuthTemplateServiceFactory = ({
templateFields: Record<string, unknown>; templateFields: Record<string, unknown>;
} & Omit<TOrgPermission, "orgId">) => { } & Omit<TOrgPermission, "orgId">) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates, OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -113,13 +115,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates, OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -227,13 +230,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates, OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -282,13 +286,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -316,13 +321,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TListIdentityAuthTemplatesDTO) => { }: TListIdentityAuthTemplatesDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -352,13 +358,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TGetTemplatesByAuthMethodDTO) => { }: TGetTemplatesByAuthMethodDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates, OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -385,13 +392,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TFindTemplateUsagesDTO) => { }: TFindTemplateUsagesDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -415,13 +423,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TUnlinkTemplateUsageDTO) => { }: TUnlinkTemplateUsageDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates, OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@@ -46,13 +47,14 @@ export const kmipOperationServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TKmipCreateDTO) => { }: TKmipCreateDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -78,13 +80,14 @@ export const kmipOperationServiceFactory = ({
}; };
const destroy = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDestroyDTO) => { const destroy = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDestroyDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -131,13 +134,14 @@ export const kmipOperationServiceFactory = ({
}; };
const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => { const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -189,13 +193,14 @@ export const kmipOperationServiceFactory = ({
}; };
const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => { const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -226,13 +231,14 @@ export const kmipOperationServiceFactory = ({
}; };
const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => { const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -287,13 +293,14 @@ export const kmipOperationServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TKmipGetAttributesDTO) => { }: TKmipGetAttributesDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -339,13 +346,14 @@ export const kmipOperationServiceFactory = ({
}; };
const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => { const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -377,13 +385,14 @@ export const kmipOperationServiceFactory = ({
actorOrgId, actorOrgId,
kmipMetadata kmipMetadata
}: TKmipRegisterDTO) => { }: TKmipRegisterDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
+17 -8
View File
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidIp } from "@app/lib/ip"; import { isValidIp } from "@app/lib/ip";
@@ -401,13 +401,14 @@ export const kmipServiceFactory = ({
}; };
const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => { const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
const kmipConfig = await kmipOrgConfigDAL.findOne({ const kmipConfig = await kmipOrgConfigDAL.findOne({
@@ -566,7 +567,14 @@ export const kmipServiceFactory = ({
}; };
const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => { const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => {
await permissionService.getOrgPermission(actor, actorId, actorOrgId, actorAuthMethod, actorOrgId); await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
const kmipConfig = await kmipOrgConfigDAL.findOne({ const kmipConfig = await kmipOrgConfigDAL.findOne({
orgId: actorOrgId orgId: actorOrgId
@@ -759,13 +767,14 @@ export const kmipServiceFactory = ({
keyAlgorithm, keyAlgorithm,
hostnamesOrIps hostnamesOrIps
}: TRegisterServerDTO) => { }: TRegisterServerDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -1,7 +1,14 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import { AccessScope, OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import {
AccessScope,
OrganizationActionScope,
OrgMembershipStatus,
TableName,
TLdapConfigsUpdate,
TUsers
} from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@@ -119,7 +126,14 @@ export const ldapConfigServiceFactory = ({
groupSearchFilter, groupSearchFilter,
caCert caCert
}: TCreateLdapCfgDTO) => { }: TCreateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -238,7 +252,14 @@ export const ldapConfigServiceFactory = ({
groupSearchFilter, groupSearchFilter,
caCert caCert
}: TUpdateLdapCfgDTO) => { }: TUpdateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -316,7 +337,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetLdapCfgDTO) => { }: TGetLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
return getLdapCfg({ return getLdapCfg({
orgId orgId
@@ -649,7 +677,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetLdapGroupMapsDTO) => { }: TGetLdapGroupMapsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
const ldapConfig = await ldapConfigDAL.findOne({ const ldapConfig = await ldapConfigDAL.findOne({
@@ -678,7 +713,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TCreateLdapGroupMapDTO) => { }: TCreateLdapGroupMapDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -732,7 +774,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TDeleteLdapGroupMapDTO) => { }: TDeleteLdapGroupMapDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -771,7 +820,14 @@ export const ldapConfigServiceFactory = ({
caCert, caCert,
url url
}: TTestLdapConnectionDTO) => { }: TTestLdapConnectionDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
+28 -5
View File
@@ -10,6 +10,7 @@ export const licenseDALFactory = (db: TDbClient) => {
const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => { const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => {
try { try {
const doc = await (tx || db.replicaNode())(TableName.Membership) const doc = await (tx || db.replicaNode())(TableName.Membership)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization }) .where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.andWhere((bd) => { .andWhere((bd) => {
if (orgId) { if (orgId) {
@@ -18,6 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => {
}) })
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.whereNull(`${TableName.Organization}.rootOrgId`)
.count(); .count();
return Number(doc?.[0]?.count ?? 0); return Number(doc?.[0]?.count ?? 0);
} catch (error) { } catch (error) {
@@ -25,10 +27,31 @@ export const licenseDALFactory = (db: TDbClient) => {
} }
}; };
const countOfOrgIdentities = async (orgId: string | null, tx?: Knex) => {
try {
// count org identities
const identityDoc = await (tx || db.replicaNode())(TableName.Identity)
.join(TableName.Organization, `${TableName.Identity}.orgId`, `${TableName.Organization}.id`)
.where((bd) => {
if (orgId) {
void bd.where(`${TableName.Organization}.rootOrgId`, orgId).orWhere(`${TableName.Organization}.id`, orgId);
}
})
.count();
const identityCount = Number(identityDoc?.[0].count);
return identityCount;
} catch (error) {
throw new DatabaseError({ error, name: "Count of Org Users + Identities" });
}
};
const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => { const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => {
try { try {
// count org users // count org users
const userDoc = await (tx || db.replicaNode())(TableName.Membership) const userDoc = await (tx || db.replicaNode())(TableName.Membership)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization }) .where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.whereNotNull(`${TableName.Membership}.actorUserId`) .whereNotNull(`${TableName.Membership}.actorUserId`)
.andWhere((bd) => { .andWhere((bd) => {
@@ -38,17 +61,17 @@ export const licenseDALFactory = (db: TDbClient) => {
}) })
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.whereNull(`${TableName.Organization}.rootOrgId`)
.count(); .count();
const userCount = Number(userDoc?.[0].count); const userCount = Number(userDoc?.[0].count);
// count org identities // count org identities
const identityDoc = await (tx || db.replicaNode())(TableName.Membership) const identityDoc = await (tx || db.replicaNode())(TableName.Identity)
.where({ scope: AccessScope.Organization }) .join(TableName.Organization, `${TableName.Identity}.orgId`, `${TableName.Organization}.id`)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where((bd) => { .where((bd) => {
if (orgId) { if (orgId) {
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId); void bd.where(`${TableName.Organization}.rootOrgId`, orgId).orWhere(`${TableName.Organization}.id`, orgId);
} }
}) })
.count(); .count();
@@ -61,5 +84,5 @@ export const licenseDALFactory = (db: TDbClient) => {
} }
}; };
return { countOfOrgMembers, countOrgUsersAndIdentities }; return { countOfOrgMembers, countOrgUsersAndIdentities, countOfOrgIdentities };
}; };
@@ -28,6 +28,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
rbac: false, rbac: false,
githubOrgSync: false, githubOrgSync: false,
customRateLimits: false, customRateLimits: false,
subOrganization: false,
customAlerts: false, customAlerts: false,
secretAccessInsights: false, secretAccessInsights: false,
auditLogs: false, auditLogs: false,
@@ -9,12 +9,12 @@ import { AxiosError } from "axios";
import { CronJob } from "cron"; import { CronJob } from "cron";
import { Knex } from "knex"; import { Knex } from "knex";
import { OrganizationActionScope } from "@app/db/schemas";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { verifyOfflineLicense } from "@app/lib/crypto"; import { verifyOfflineLicense } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -45,11 +45,10 @@ import {
} from "./license-types"; } from "./license-types";
type TLicenseServiceFactoryDep = { type TLicenseServiceFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findOrgById" | "countAllOrgMembers">; orgDAL: Pick<TOrgDALFactory, "findRootOrgDetails" | "countAllOrgMembers" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseDAL: TLicenseDALFactory; licenseDAL: TLicenseDALFactory;
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">; keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
projectDAL: TProjectDALFactory; projectDAL: TProjectDALFactory;
}; };
@@ -66,7 +65,6 @@ export const licenseServiceFactory = ({
permissionService, permissionService,
licenseDAL, licenseDAL,
keyStore, keyStore,
identityOrgMembershipDAL,
projectDAL projectDAL
}: TLicenseServiceFactoryDep) => { }: TLicenseServiceFactoryDep) => {
let isValidLicense = false; let isValidLicense = false;
@@ -199,19 +197,21 @@ export const licenseServiceFactory = ({
return JSON.parse(cachedPlan) as TFeatureSet; return JSON.parse(cachedPlan) as TFeatureSet;
} }
const org = await orgDAL.findOrgById(orgId); const org = await orgDAL.findRootOrgDetails(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const rootOrgId = org.id;
const { const {
data: { currentPlan } data: { currentPlan }
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>( } = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
`/api/license-server/v1/customers/${org.customerId}/cloud-plan` `/api/license-server/v1/customers/${org.customerId}/cloud-plan`
); );
const workspacesUsed = await projectDAL.countOfOrgProjects(orgId); const workspacesUsed = await projectDAL.countOfOrgProjects(rootOrgId);
currentPlan.workspacesUsed = workspacesUsed; currentPlan.workspacesUsed = workspacesUsed;
const membersUsed = await licenseDAL.countOfOrgMembers(orgId); const membersUsed = await licenseDAL.countOfOrgMembers(rootOrgId);
currentPlan.membersUsed = membersUsed; currentPlan.membersUsed = membersUsed;
const identityUsed = await licenseDAL.countOrgUsersAndIdentities(orgId); const identityUsed = await licenseDAL.countOrgUsersAndIdentities(rootOrgId);
currentPlan.identitiesUsed = identityUsed; currentPlan.identitiesUsed = identityUsed;
if (currentPlan.identityLimit && currentPlan.identityLimit !== identityUsed) { if (currentPlan.identityLimit && currentPlan.identityLimit !== identityUsed) {
@@ -284,19 +284,20 @@ export const licenseServiceFactory = ({
}; };
const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => { const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => {
if (instanceType === InstanceType.Cloud) { const org = await orgDAL.findRootOrgDetails(orgId, tx);
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const quantity = await licenseDAL.countOfOrgMembers(orgId, tx); const rootOrgId = org.id;
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId, tx); if (instanceType === InstanceType.Cloud) {
const quantity = await licenseDAL.countOfOrgMembers(rootOrgId, tx);
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(rootOrgId, tx);
if (org?.customerId) { if (org?.customerId) {
await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, { await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, {
quantity, quantity,
quantityIdentities quantityIdentities
}); });
} }
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId)); await keyStore.deleteItem(FEATURE_CACHE_KEY(rootOrgId));
} else if (instanceType === InstanceType.EnterpriseOnPrem) { } else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx); const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx); const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
@@ -307,7 +308,7 @@ export const licenseServiceFactory = ({
usedIdentitySeats usedIdentitySeats
}); });
} }
await refreshPlan(orgId); await refreshPlan(rootOrgId);
}; };
// below all are api calls // below all are api calls
@@ -319,7 +320,14 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
billingCycle billingCycle
}: TOrgPlansTableDTO) => { }: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get( const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
@@ -336,7 +344,14 @@ export const licenseServiceFactory = ({
projectId, projectId,
refreshCache refreshCache
}: TOrgPlanDTO) => { }: TOrgPlanDTO) => {
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
if (refreshCache) { if (refreshCache) {
await refreshPlan(orgId); await refreshPlan(orgId);
} }
@@ -352,13 +367,20 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
success_url success_url
}: TStartOrgTrialDTO) => { }: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -384,13 +406,20 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TCreateOrgPortalSession) => { }: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: "Organization not found" message: "Organization not found"
@@ -433,10 +462,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -502,7 +538,7 @@ export const licenseServiceFactory = ({
const getUsageMetrics = async (orgId: string) => { const getUsageMetrics = async (orgId: string) => {
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([ const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
orgDAL.countAllOrgMembers(orgId), orgDAL.countAllOrgMembers(orgId),
identityOrgMembershipDAL.countAllOrgIdentities({ scopeOrgId: orgId }), licenseDAL.countOfOrgIdentities(orgId),
projectDAL.countOfOrgProjects(orgId) projectDAL.countOfOrgProjects(orgId)
]); ]);
@@ -516,10 +552,17 @@ export const licenseServiceFactory = ({
// returns org current plan feature table // returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -553,10 +596,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -578,13 +628,20 @@ export const licenseServiceFactory = ({
name, name,
email email
}: TUpdateOrgBillingDetailsDTO) => { }: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -601,10 +658,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => { const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -628,13 +692,20 @@ export const licenseServiceFactory = ({
success_url, success_url,
cancel_url cancel_url
}: TAddOrgPmtMethodDTO) => { }: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -660,13 +731,20 @@ export const licenseServiceFactory = ({
orgId, orgId,
pmtMethodId pmtMethodId
}: TDelOrgPmtMethodDTO) => { }: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -692,10 +770,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => { const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -710,13 +795,20 @@ export const licenseServiceFactory = ({
}; };
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -734,13 +826,20 @@ export const licenseServiceFactory = ({
}; };
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -754,10 +853,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => { const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -771,10 +877,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => { const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -819,7 +932,6 @@ export const licenseServiceFactory = ({
getLicenseId, getLicenseId,
invalidateGetPlan, invalidateGetPlan,
updateSubscriptionOrgMemberCount, updateSubscriptionOrgMemberCount,
refreshPlan,
getOrgPlan, getOrgPlan,
getOrgPlansTableByBillCycle, getOrgPlansTableByBillCycle,
startOrgTrial, startOrgTrial,
@@ -33,6 +33,7 @@ export type TFeatureSet = {
membersUsed: number; membersUsed: number;
identityLimit: null; identityLimit: null;
identitiesUsed: number; identitiesUsed: number;
subOrganization: false;
environmentLimit: null; environmentLimit: null;
environmentsUsed: 0; environmentsUsed: 0;
secretVersioning: true; secretVersioning: true;
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
import { AccessScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs"; import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
@@ -118,13 +118,14 @@ export const oidcConfigServiceFactory = ({
} }
if (dto.type === "external") { if (dto.type === "external") {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.actor, actorId: dto.actorId,
dto.actorId, actor: dto.actor,
dto.organizationId, orgId: dto.organizationId,
dto.actorAuthMethod, actorOrgId: dto.actorOrgId,
dto.actorOrgId actorAuthMethod: dto.actorAuthMethod,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
} }
@@ -508,13 +509,14 @@ export const oidcConfigServiceFactory = ({
"Failed to update OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." "Failed to update OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
org.id, actor,
orgId: org.id,
actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.ParentOrganization
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
if (org.googleSsoAuthEnforced && isActive) { if (org.googleSsoAuthEnforced && isActive) {
@@ -602,13 +604,14 @@ export const oidcConfigServiceFactory = ({
"Failed to create OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." "Failed to create OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
org.id, actor,
orgId: org.id,
actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.ParentOrganization
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
if (org.googleSsoAuthEnforced && isActive) { if (org.googleSsoAuthEnforced && isActive) {
@@ -764,7 +767,14 @@ export const oidcConfigServiceFactory = ({
}; };
const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => { const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => {
await permissionService.getOrgPermission(ActorType.USER, actor.id, orgId, actor.authMethod, actor.orgId); await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: actor.id,
orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
scope: OrganizationActionScope.ParentOrganization
});
const oidcConfig = await oidcConfigDAL.findOne({ const oidcConfig = await oidcConfigDAL.findOne({
orgId, orgId,
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, TPamAccounts, TPamResources } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope, TPamAccounts, TPamResources } from "@app/db/schemas";
import { PAM_RESOURCE_FACTORY_MAP } from "@app/ee/services/pam-resource/pam-resource-factory"; import { PAM_RESOURCE_FACTORY_MAP } from "@app/ee/services/pam-resource/pam-resource-factory";
import { decryptResource, decryptResourceConnectionDetails } from "@app/ee/services/pam-resource/pam-resource-fns"; import { decryptResource, decryptResourceConnectionDetails } from "@app/ee/services/pam-resource/pam-resource-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -459,13 +459,14 @@ export const pamAccountServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
@@ -102,13 +102,14 @@ export const pamSessionServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -142,13 +143,14 @@ export const pamSessionServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
if (actor.type === ActorType.IDENTITY) { if (actor.type === ActorType.IDENTITY) {
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -15,6 +15,11 @@ export enum OrgPermissionActions {
Delete = "delete" Delete = "delete"
} }
export enum OrgPermissionSubOrgActions {
Create = "create",
DirectAccess = "direct-access"
}
export enum OrgPermissionAppConnectionActions { export enum OrgPermissionAppConnectionActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -117,7 +122,8 @@ export enum OrgPermissionSubjects {
Kmip = "kmip", Kmip = "kmip",
Gateway = "gateway", Gateway = "gateway",
Relay = "relay", Relay = "relay",
SecretShare = "secret-share" SecretShare = "secret-share",
SubOrganization = "sub-organization"
} }
export type AppConnectionSubjectFields = { export type AppConnectionSubjectFields = {
@@ -128,6 +134,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace] | [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
| [OrgPermissionActions.Create, OrgPermissionSubjects.Project] | [OrgPermissionActions.Create, OrgPermissionSubjects.Project]
| [OrgPermissionActions, OrgPermissionSubjects.Role] | [OrgPermissionActions, OrgPermissionSubjects.Role]
| [OrgPermissionSubOrgActions, OrgPermissionSubjects.SubOrganization]
| [OrgPermissionActions, OrgPermissionSubjects.Member] | [OrgPermissionActions, OrgPermissionSubjects.Member]
| [OrgPermissionActions, OrgPermissionSubjects.Settings] | [OrgPermissionActions, OrgPermissionSubjects.Settings]
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount] | [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
@@ -185,6 +192,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."), subject: z.literal(OrgPermissionSubjects.Role).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(OrgPermissionActions).describe("Describe what action an entity can take.")
}), }),
z.object({
subject: z.literal(OrgPermissionSubjects.SubOrganization).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSubOrgActions).describe(
"Describe what action an entity can take."
)
}),
z.object({ z.object({
subject: z.literal(OrgPermissionSubjects.Member).describe("The entity this permission pertains to."), subject: z.literal(OrgPermissionSubjects.Member).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(OrgPermissionActions).describe("Describe what action an entity can take.")
@@ -308,6 +321,10 @@ const buildAdminPermission = () => {
// ws permissions // ws permissions
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Project); can(OrgPermissionActions.Create, OrgPermissionSubjects.Project);
can(OrgPermissionSubOrgActions.Create, OrgPermissionSubjects.SubOrganization);
can(OrgPermissionSubOrgActions.DirectAccess, OrgPermissionSubjects.SubOrganization);
// role permission // role permission
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Role); can(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
@@ -19,6 +19,7 @@ interface TPermissionDataReturn extends TMemberships {
orgAuthEnforced?: boolean | null; orgAuthEnforced?: boolean | null;
orgGoogleSsoAuthEnforced?: boolean | null; orgGoogleSsoAuthEnforced?: boolean | null;
shouldUseNewPrivilegeSystem?: boolean | null; shouldUseNewPrivilegeSystem?: boolean | null;
rootOrgId?: string | null;
bypassOrgAuthEnabled?: boolean | null; bypassOrgAuthEnabled?: boolean | null;
roles: { roles: {
id: string; id: string;
@@ -273,7 +274,8 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization), db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"), db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled") db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("rootOrgId").withSchema(TableName.Organization).as("rootOrgId")
); );
const data = sqlNestRelationships({ const data = sqlNestRelationships({
@@ -283,6 +285,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
MembershipsSchema.extend({ MembershipsSchema.extend({
orgAuthEnforced: z.boolean().optional().nullable(), orgAuthEnforced: z.boolean().optional().nullable(),
shouldUseNewPrivilegeSystem: z.boolean().optional().nullable(), shouldUseNewPrivilegeSystem: z.boolean().optional().nullable(),
rootOrgId: z.string().optional().nullable(),
orgGoogleSsoAuthEnforced: z.boolean(), orgGoogleSsoAuthEnforced: z.boolean(),
bypassOrgAuthEnabled: z.boolean() bypassOrgAuthEnabled: z.boolean()
}).parse(el), }).parse(el),
@@ -2,7 +2,7 @@ import { MongoAbility } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js"; import { MongoQuery } from "@ucast/mongo2js";
import { Knex } from "knex"; import { Knex } from "knex";
import { ActionProjectType, TMemberships } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope, TMemberships } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission"; import { OrgPermissionSet } from "./org-permission";
@@ -18,21 +18,6 @@ export type TBuildOrgPermissionDTO = {
role: string; role: string;
}[]; }[];
export type TGetUserProjectPermissionArg = {
userId: string;
projectId: string;
authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string;
};
export type TGetIdentityProjectPermissionArg = {
identityId: string;
projectId: string;
identityOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetServiceTokenProjectPermissionArg = { export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string; serviceTokenId: string;
projectId: string; projectId: string;
@@ -54,17 +39,12 @@ export type TGetOrgPermissionArg = {
actorId: string; actorId: string;
orgId: string; orgId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId?: string; actorOrgId: string;
scope: OrganizationActionScope;
}; };
export type TPermissionServiceFactory = { export type TPermissionServiceFactory = {
getOrgPermission: ( getOrgPermission: (arg: TGetOrgPermissionArg) => Promise<{
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>; permission: MongoAbility<OrgPermissionSet, MongoQuery>;
memberships: Array< memberships: Array<
TMemberships & { TMemberships & {
@@ -7,6 +7,7 @@ import { Knex } from "knex";
import { import {
AccessScope, AccessScope,
ActionProjectType, ActionProjectType,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
ProjectMembershipRole, ProjectMembershipRole,
ServiceTokenScopes ServiceTokenScopes
@@ -179,14 +180,15 @@ export const permissionServiceFactory = ({
// return minTtl; // return minTtl;
// }; // };
const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async ( const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async ({
type, actor,
id, actorId,
orgId, orgId,
authMethod, actorOrgId,
actorOrgId scope,
) => { actorAuthMethod
if (type !== ActorType.USER && type !== ActorType.IDENTITY) { }) => {
if (actor !== ActorType.USER && actor !== ActorType.IDENTITY) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid actor provided", message: "Invalid actor provided",
name: "Get org permission" name: "Get org permission"
@@ -202,11 +204,19 @@ export const permissionServiceFactory = ({
scope: AccessScope.Organization, scope: AccessScope.Organization,
orgId orgId
}, },
actorId: id, actorId,
actorType: type actorType: actor
}); });
if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" }); if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" });
const rootOrgId = permissionData?.[0]?.rootOrgId;
const isChild = Boolean(rootOrgId);
if (scope === OrganizationActionScope.ParentOrganization && isChild) {
throw new ForbiddenRequestError({ message: `Child organization cannot do this operation` });
} else if (scope === OrganizationActionScope.ChildOrganization && !isChild) {
throw new ForbiddenRequestError({ message: `Parent organization cannot do this operation` });
}
const permissionFromRoles = permissionData.flatMap((membership) => { const permissionFromRoles = permissionData.flatMap((membership) => {
const activeRoles = membership?.roles const activeRoles = membership?.roles
.filter( .filter(
@@ -227,7 +237,7 @@ export const permissionServiceFactory = ({
permissionData.some((memberships) => memberships.roles.some((el) => role === (el.customRoleSlug || el.role))); permissionData.some((memberships) => memberships.roles.some((el) => role === (el.customRoleSlug || el.role)));
validateOrgSSO( validateOrgSSO(
authMethod, actorAuthMethod,
permissionData?.[0].orgAuthEnforced, permissionData?.[0].orgAuthEnforced,
Boolean(permissionData?.[0].orgGoogleSsoAuthEnforced), Boolean(permissionData?.[0].orgGoogleSsoAuthEnforced),
Boolean(permissionData?.[0].bypassOrgAuthEnabled), Boolean(permissionData?.[0].bypassOrgAuthEnabled),
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { ProjectType, TProjectTemplates } from "@app/db/schemas"; import { OrganizationActionScope, ProjectType, TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -59,13 +59,14 @@ export const projectTemplateServiceFactory = ({
message: "Failed to access project templates due to plan restriction. Upgrade plan to access project templates." message: "Failed to access project templates due to plan restriction. Upgrade plan to access project templates."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -97,13 +98,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with Name "${name}"` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with Name "${name}"` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -125,13 +127,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -152,13 +155,14 @@ export const projectTemplateServiceFactory = ({
message: "Failed to create project template due to plan restriction. Upgrade plan to access project templates." message: "Failed to create project template due to plan restriction. Upgrade plan to access project templates."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
@@ -213,13 +217,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments) if (projectTemplate.type !== ProjectType.SecretManager && environments)
@@ -272,13 +277,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
@@ -2,7 +2,7 @@ import { z } from "zod";
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { OrgServiceActor } from "@app/lib/types"; import { ProjectServiceActor } from "@app/lib/types";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">; export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">;
@@ -31,7 +31,7 @@ export enum InfisicalProjectTemplate {
export type TProjectTemplateServiceFactory = { export type TProjectTemplateServiceFactory = {
listProjectTemplatesByOrg: ( listProjectTemplatesByOrg: (
actor: OrgServiceActor, actor: ProjectServiceActor,
type?: ProjectType type?: ProjectType
) => Promise< ) => Promise<
( (
@@ -85,7 +85,7 @@ export type TProjectTemplateServiceFactory = {
>; >;
createProjectTemplate: ( createProjectTemplate: (
arg: TCreateProjectTemplateDTO, arg: TCreateProjectTemplateDTO,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -109,7 +109,7 @@ export type TProjectTemplateServiceFactory = {
updateProjectTemplateById: ( updateProjectTemplateById: (
id: string, id: string,
{ roles, environments, ...params }: TUpdateProjectTemplateDTO, { roles, environments, ...params }: TUpdateProjectTemplateDTO,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -132,7 +132,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
deleteProjectTemplateById: ( deleteProjectTemplateById: (
id: string, id: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -155,7 +155,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
findProjectTemplateById: ( findProjectTemplateById: (
id: string, id: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
packedRoles: TProjectTemplateRole[]; packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
@@ -179,7 +179,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
findProjectTemplateByName: ( findProjectTemplateByName: (
name: string, name: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
packedRoles: TProjectTemplateRole[]; packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
+25 -21
View File
@@ -3,7 +3,7 @@ import { isIP } from "node:net";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { OrgMembershipRole, TRelays } from "@app/db/schemas"; import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore"; import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -126,8 +126,8 @@ export const relayServiceFactory = ({
// generate instance relay CA // generate instance relay CA
const instanceRelayCaSerialNumber = createSerialNumber(); const instanceRelayCaSerialNumber = createSerialNumber();
const instanceRelayCaIssuedAt = new Date();
const instanceRelayCaExpiration = new Date(new Date().setFullYear(2045)); const instanceRelayCaExpiration = new Date(new Date().setFullYear(2045));
const instanceRelayCaIssuedAt = new Date();
const instanceRelayCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const instanceRelayCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const instanceRelayCaSkObj = crypto.nativeCrypto.KeyObject.from(instanceRelayCaKeys.privateKey); const instanceRelayCaSkObj = crypto.nativeCrypto.KeyObject.from(instanceRelayCaKeys.privateKey);
const instanceRelayCaCert = await x509.X509CertificateGenerator.create({ const instanceRelayCaCert = await x509.X509CertificateGenerator.create({
@@ -972,13 +972,14 @@ export const relayServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityId, actor: ActorType.IDENTITY,
actorId: identityId,
orgId, orgId,
actorAuthMethod!, actorAuthMethod: actorAuthMethod!,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionRelayActions.CreateRelays, OrgPermissionRelayActions.CreateRelays,
@@ -1102,13 +1103,14 @@ export const relayServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityId, actor: ActorType.IDENTITY,
actorId: identityId,
orgId, orgId,
actorAuthMethod!, actorAuthMethod: actorAuthMethod!,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionRelayActions.CreateRelays, OrgPermissionRelayActions.CreateRelays,
OrgPermissionSubjects.Relay OrgPermissionSubjects.Relay
@@ -1155,13 +1157,14 @@ export const relayServiceFactory = ({
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
}) => { }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod: actorAuthMethod!,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay);
@@ -1189,13 +1192,14 @@ export const relayServiceFactory = ({
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
}) => { }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay);
@@ -5,6 +5,7 @@ import RE2 from "re2";
import { import {
AccessScope, AccessScope,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
OrgMembershipStatus, OrgMembershipStatus,
TableName, TableName,
@@ -251,7 +252,14 @@ export const samlConfigServiceFactory = ({
authProvider, authProvider,
enableGroupSync enableGroupSync
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -317,7 +325,14 @@ export const samlConfigServiceFactory = ({
authProvider, authProvider,
enableGroupSync enableGroupSync
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (!plan.samlSSO) if (!plan.samlSSO)
@@ -393,7 +408,7 @@ export const samlConfigServiceFactory = ({
}); });
} }
} else if (dto.type === "orgSlug") { } else if (dto.type === "orgSlug") {
const org = await orgDAL.findOne({ slug: dto.orgSlug }); const org = await orgDAL.findOne({ slug: dto.orgSlug, rootOrgId: null });
if (!org) { if (!org) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with slug '${dto.orgSlug}' not found` message: `Organization with slug '${dto.orgSlug}' not found`
@@ -424,13 +439,14 @@ export const samlConfigServiceFactory = ({
// when dto is type id means it's internally used // when dto is type id means it's internally used
if (dto.type === "org") { if (dto.type === "org") {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.actor, scope: OrganizationActionScope.ParentOrganization,
dto.actorId, actor: dto.actor,
samlConfig.orgId, actorId: dto.actorId,
dto.actorAuthMethod, orgId: samlConfig.orgId,
dto.actorOrgId actorAuthMethod: dto.actorAuthMethod,
); actorOrgId: dto.actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
} }
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -37,7 +37,7 @@ export type TGetSamlCfgDTO =
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined; actorOrgId: string;
} }
| { | {
type: "orgSlug"; type: "orgSlug";
+22 -5
View File
@@ -4,6 +4,7 @@ import { scimPatch } from "scim-patch";
import { import {
AccessScope, AccessScope,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
OrgMembershipStatus, OrgMembershipStatus,
TableName, TableName,
@@ -56,6 +57,7 @@ type TScimServiceFactoryDep = {
TOrgDALFactory, TOrgDALFactory,
| "createMembership" | "createMembership"
| "findById" | "findById"
| "find"
| "findMembership" | "findMembership"
| "findMembershipWithScimFilter" | "findMembershipWithScimFilter"
| "deleteMembershipById" | "deleteMembershipById"
@@ -125,7 +127,14 @@ export const scimServiceFactory = ({
description, description,
ttlDays ttlDays
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -160,7 +169,14 @@ export const scimServiceFactory = ({
actorAuthMethod, actorAuthMethod,
orgId orgId
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -183,13 +199,14 @@ export const scimServiceFactory = ({
let scimToken = await scimDAL.findById(scimTokenId); let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` }); if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor, actor,
actorId, actorId,
scimToken.orgId, orgId: scimToken.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(scimToken.orgId); const plan = await licenseService.getPlan(scimToken.orgId);
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { WebhookEventMap } from "@octokit/webhooks-types"; import { WebhookEventMap } from "@octokit/webhooks-types";
import { ProbotOctokit } from "probot"; import { ProbotOctokit } from "probot";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -49,7 +50,14 @@ export const secretScanningServiceFactory = ({
}: TInstallAppSessionDTO) => { }: TInstallAppSessionDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const sessionId = crypto.randomBytes(16).toString("hex"); const sessionId = crypto.randomBytes(16).toString("hex");
@@ -68,13 +76,14 @@ export const secretScanningServiceFactory = ({
const session = await gitAppInstallSessionDAL.findOne({ sessionId }); const session = await gitAppInstallSessionDAL.findOne({ sessionId });
if (!session) throw new NotFoundError({ message: "Session was not found" }); if (!session) throw new NotFoundError({ message: "Session was not found" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
session.orgId, orgId: session.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { const installatedApp = await gitAppOrgDAL.transaction(async (tx) => {
await gitAppInstallSessionDAL.deleteById(session.id, tx); await gitAppInstallSessionDAL.deleteById(session.id, tx);
@@ -117,7 +126,14 @@ export const secretScanningServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetOrgInstallStatusDTO) => { }: TGetOrgInstallStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const appInstallation = await gitAppOrgDAL.findOne({ orgId }); const appInstallation = await gitAppOrgDAL.findOne({ orgId });
@@ -125,7 +141,14 @@ export const secretScanningServiceFactory = ({
}; };
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => { const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const results = await secretScanningDAL.findByOrgId(orgId, filter); const results = await secretScanningDAL.findByOrgId(orgId, filter);
@@ -134,7 +157,14 @@ export const secretScanningServiceFactory = ({
}; };
const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => { const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] }); const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
@@ -150,7 +180,14 @@ export const secretScanningServiceFactory = ({
riskId, riskId,
status status
}: TUpdateRiskStatusDTO) => { }: TUpdateRiskStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
const isRiskResolved = Boolean( const isRiskResolved = Boolean(
@@ -0,0 +1,160 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope, OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TMembershipDALFactory } from "@app/services/membership/membership-dal";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects, OrgPermissionSubOrgActions } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TCreateSubOrgDTO, TListSubOrgDTO, TUpdateSubOrgDTO } from "./sub-org-types";
type TSubOrgServiceFactoryDep = {
orgDAL: Pick<
TOrgDALFactory,
"findOne" | "create" | "transaction" | "listSubOrganizations" | "updateById" | "findById"
>;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
membershipDAL: Pick<TMembershipDALFactory, "create">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
};
export type TSubOrgServiceFactory = ReturnType<typeof subOrgServiceFactory>;
export const subOrgServiceFactory = ({
orgDAL,
permissionService,
licenseService,
membershipDAL,
membershipRoleDAL
}: TSubOrgServiceFactoryDep) => {
const createSubOrg = async ({ name, permissionActor }: TCreateSubOrgDTO) => {
const { permission } = await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: permissionActor.orgId,
actorOrgId: permissionActor.orgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionSubOrgActions.Create,
OrgPermissionSubjects.SubOrganization
);
const orgLicensePlan = await licenseService.getPlan(permissionActor.rootOrgId);
if (!orgLicensePlan.subOrganization) {
throw new BadRequestError({
message: "Sub-organization creation failed. Please upgrade your instance to Infisical's Enterprise plan."
});
}
const existingSubOrg = await orgDAL.findOne({
parentOrgId: permissionActor.orgId,
name
});
if (existingSubOrg) {
throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` });
}
const organization = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.create(
{ name, slug: name, rootOrgId: permissionActor.rootOrgId, parentOrgId: permissionActor.orgId },
tx
);
const membership = await membershipDAL.create(
{
scope: AccessScope.Organization,
[permissionActor.type === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"]: permissionActor.id,
scopeOrgId: org.id,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role: OrgMembershipRole.Admin
},
tx
);
return org;
});
return {
organization
};
};
const listSubOrgs = async ({ permissionActor, data }: TListSubOrgDTO) => {
await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: permissionActor.rootOrgId,
actorOrgId: permissionActor.rootOrgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.Any
});
const organizations = await orgDAL.listSubOrganizations({
actorId: permissionActor.id,
actorType: permissionActor.type,
orgId: permissionActor.rootOrgId,
isAccessible: data?.isAccessible,
limit: data?.limit,
offset: data?.offset
});
return {
organizations
};
};
const updateSubOrg = async ({ subOrgId, name, permissionActor }: TUpdateSubOrgDTO) => {
const subOrg = await orgDAL.findOne({
rootOrgId: permissionActor.rootOrgId,
id: subOrgId
});
if (!subOrg) {
throw new BadRequestError({ message: "Sub-organization not found" });
}
const { permission } = await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: subOrgId,
actorOrgId: subOrgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.ChildOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const existingSubOrg = await orgDAL.findOne({
parentOrgId: subOrg.parentOrgId,
slug: name
});
if (existingSubOrg && existingSubOrg.id !== subOrgId) {
throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` });
}
const organization = await orgDAL.updateById(subOrgId, { name, slug: name });
return {
organization
};
};
return {
createSubOrg,
listSubOrgs,
updateSubOrg
};
};
@@ -0,0 +1,22 @@
import { OrgServiceActor } from "@app/lib/types";
export type TCreateSubOrgDTO = {
name: string;
permissionActor: OrgServiceActor;
};
export type TListSubOrgDTO = {
permissionActor: OrgServiceActor;
data: Partial<{
limit?: number;
offset?: number;
search?: string;
isAccessible?: boolean;
}>;
};
export type TUpdateSubOrgDTO = {
subOrgId: string;
name: string;
permissionActor: OrgServiceActor;
};
+16
View File
@@ -33,6 +33,7 @@ export enum ApiDocsTags {
LdapAuth = "LDAP Auth", LdapAuth = "LDAP Auth",
Groups = "Groups", Groups = "Groups",
Organizations = "Organizations", Organizations = "Organizations",
SubOrganizations = "Sub Organizations",
Projects = "Projects", Projects = "Projects",
ProjectUsers = "Project Users", ProjectUsers = "Project Users",
ProjectGroups = "Project Groups", ProjectGroups = "Project Groups",
@@ -717,6 +718,21 @@ export const ORGANIZATIONS = {
} }
} as const; } as const;
export const SUB_ORGANIZATIONS = {
CREATE: {
name: "The name of the sub organization to create."
},
UPDATE: {
name: "The name of the sub organization to update.",
subOrgId: "The id of the sub organization to update."
},
LIST: {
limit: "The number of sub organizations to return.",
offset: "The offset to start from. If you enter 10, it will start from the 10th sub organization.",
isAccessible: "Filter to only return sub organizations that the actor has access to."
}
} as const;
export const PROJECTS = { export const PROJECTS = {
CREATE: { CREATE: {
organizationSlug: "The slug of the organization to create the project in.", organizationSlug: "The slug of the organization to create the project in.",
+10 -1
View File
@@ -5,7 +5,7 @@ export type TGenericPermission = {
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined; actorOrgId: string;
}; };
/** /**
@@ -78,6 +78,15 @@ export type OrgServiceActor = {
id: string; id: string;
authMethod: ActorAuthMethod; authMethod: ActorAuthMethod;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
};
export type ProjectServiceActor = {
type: ActorType;
id: string;
authMethod: ActorAuthMethod;
orgId: string;
}; };
export enum QueueWorkerProfile { export enum QueueWorkerProfile {
@@ -8,6 +8,7 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { slugSchema } from "@app/server/lib/schemas";
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@@ -20,6 +21,8 @@ export type TAuthMode =
tokenVersionId: string; // the session id of token used tokenVersionId: string; // the session id of token used
user: TUsers; user: TUsers;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: AuthMethod; authMethod: AuthMethod;
isMfaVerified?: boolean; isMfaVerified?: boolean;
token: AuthModeJwtTokenPayload; token: AuthModeJwtTokenPayload;
@@ -31,6 +34,8 @@ export type TAuthMode =
userId: string; userId: string;
user: TUsers; user: TUsers;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
token: string; token: string;
} }
| { | {
@@ -39,6 +44,8 @@ export type TAuthMode =
actor: ActorType.SERVICE; actor: ActorType.SERVICE;
serviceTokenId: string; serviceTokenId: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
token: string; token: string;
} }
@@ -48,6 +55,8 @@ export type TAuthMode =
identityId: string; identityId: string;
identityName: string; identityName: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
isInstanceAdmin?: boolean; isInstanceAdmin?: boolean;
token: TIdentityAccessTokenJwtPayload; token: TIdentityAccessTokenJwtPayload;
@@ -57,6 +66,8 @@ export type TAuthMode =
actor: ActorType.SCIM_CLIENT; actor: ActorType.SCIM_CLIENT;
scimTokenId: string; scimTokenId: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
}; };
@@ -136,17 +147,26 @@ export const injectIdentity = fp(
if (!authMode) return; if (!authMode) return;
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
if (subOrganizationSelector) {
await slugSchema().parseAsync(subOrganizationSelector);
}
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); const { user, tokenVersionId, orgId, rootOrgId, parentOrgId } =
await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector);
requestContext.set("orgId", orgId); requestContext.set("orgId", orgId);
req.auth = { req.auth = {
authMode: AuthMode.JWT, authMode: AuthMode.JWT,
user, user,
userId: user.id, userId: user.id,
tokenVersionId, tokenVersionId,
actor, actor,
orgId: orgId as string, orgId,
rootOrgId,
parentOrgId,
authMethod: token.authMethod, authMethod: token.authMethod,
isMfaVerified: token.isMfaVerified, isMfaVerified: token.isMfaVerified,
token token
@@ -154,13 +174,19 @@ export const injectIdentity = fp(
break; break;
} }
case AuthMode.IDENTITY_ACCESS_TOKEN: { case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
token,
subOrganizationSelector,
req.realIp
);
const serverCfg = await getServerCfg(); const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId); requestContext.set("orgId", identity.orgId);
req.auth = { req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor, actor,
orgId: identity.orgId, orgId: identity.orgId,
rootOrgId: identity.rootOrgId,
parentOrgId: identity.parentOrgId,
identityId: identity.identityId, identityId: identity.identityId,
identityName: identity.name, identityName: identity.name,
authMethod: null, authMethod: null,
@@ -190,8 +216,14 @@ export const injectIdentity = fp(
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
requestContext.set("orgId", serviceToken.orgId); requestContext.set("orgId", serviceToken.orgId);
if (subOrganizationSelector)
throw new BadRequestError({ message: `Service token doesn't support sub organization selector` });
req.auth = { req.auth = {
orgId: serviceToken.orgId, orgId: serviceToken.orgId,
rootOrgId: serviceToken.rootOrgId,
parentOrgId: serviceToken.parentOrgId,
authMode: AuthMode.SERVICE_TOKEN as const, authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken, serviceToken,
serviceTokenId: serviceToken.id, serviceTokenId: serviceToken.id,
@@ -202,22 +234,27 @@ export const injectIdentity = fp(
break; break;
} }
case AuthMode.API_KEY: { case AuthMode.API_KEY: {
const user = await server.services.apiKey.fnValidateApiKey(token as string); throw new BadRequestError({
req.auth = { message: "API key authentication is not supported anymore. Please switch to identity authentication."
authMode: AuthMode.API_KEY as const, });
userId: user.id,
actor,
user,
orgId: "API_KEY", // We set the orgId to an arbitrary value, since we can't link an API key to a specific org. We have to deprecate API keys soon!
authMethod: null,
token: token as string
};
break;
} }
case AuthMode.SCIM_TOKEN: { case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
requestContext.set("orgId", orgId); requestContext.set("orgId", orgId);
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null };
if (subOrganizationSelector)
throw new BadRequestError({ message: `SCIM token doesn't support sub organization selector` });
req.auth = {
authMode: AuthMode.SCIM_TOKEN,
actor,
scimTokenId,
orgId,
authMethod: null,
// scim cannot be done for sub organization
rootOrgId: orgId,
parentOrgId: orgId
};
break; break;
} }
default: default:
@@ -14,7 +14,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.USER, type: ActorType.USER,
id: req.auth.userId, id: req.auth.userId,
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY" orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null authMethod: req.auth.authMethod, // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
}; };
logger.info( logger.info(
@@ -25,7 +27,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.IDENTITY, type: ActorType.IDENTITY,
id: req.auth.identityId, id: req.auth.identityId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
authMethod: null authMethod: null,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
}; };
logger.info( logger.info(
@@ -36,6 +40,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SERVICE, type: ActorType.SERVICE,
id: req.auth.serviceTokenId, id: req.auth.serviceTokenId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null authMethod: null
}; };
@@ -47,6 +53,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SCIM_CLIENT, type: ActorType.SCIM_CLIENT,
id: req.auth.scimTokenId, id: req.auth.scimTokenId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null authMethod: null
}; };
+29 -4
View File
@@ -131,6 +131,7 @@ import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-
import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal";
import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service";
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { keyValueStoreDALFactory } from "@app/keystore/key-value-store-dal"; import { keyValueStoreDALFactory } from "@app/keystore/key-value-store-dal";
@@ -568,11 +569,10 @@ export const registerRoutes = async (
orgDAL, orgDAL,
licenseDAL, licenseDAL,
keyStore, keyStore,
identityOrgMembershipDAL,
projectDAL projectDAL
}); });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL }); const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL, orgDAL });
const membershipUserService = membershipUserServiceFactory({ const membershipUserService = membershipUserServiceFactory({
licenseService, licenseService,
@@ -592,6 +592,7 @@ export const registerRoutes = async (
}); });
const membershipIdentityService = membershipIdentityServiceFactory({ const membershipIdentityService = membershipIdentityServiceFactory({
identityDAL,
membershipIdentityDAL, membershipIdentityDAL,
membershipRoleDAL, membershipRoleDAL,
orgDAL, orgDAL,
@@ -912,6 +913,15 @@ export const registerRoutes = async (
userGroupMembershipDAL, userGroupMembershipDAL,
additionalPrivilegeDAL additionalPrivilegeDAL
}); });
const subOrgService = subOrgServiceFactory({
licenseService,
membershipDAL,
membershipRoleDAL,
orgDAL,
permissionService
});
const signupService = authSignupServiceFactory({ const signupService = authSignupServiceFactory({
tokenService, tokenService,
smtpService, smtpService,
@@ -1594,10 +1604,12 @@ export const registerRoutes = async (
permissionService, permissionService,
projectDAL, projectDAL,
accessTokenQueue, accessTokenQueue,
smtpService smtpService,
orgDAL
}); });
const identityService = identityServiceFactory({ const identityService = identityServiceFactory({
additionalPrivilegeDAL,
permissionService, permissionService,
identityDAL, identityDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
@@ -1628,10 +1640,12 @@ export const registerRoutes = async (
identityAccessTokenDAL, identityAccessTokenDAL,
accessTokenQueue, accessTokenQueue,
identityDAL, identityDAL,
membershipIdentityDAL membershipIdentityDAL,
orgDAL
}); });
const identityTokenAuthService = identityTokenAuthServiceFactory({ const identityTokenAuthService = identityTokenAuthServiceFactory({
identityDAL,
identityTokenAuthDAL, identityTokenAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
@@ -1641,6 +1655,7 @@ export const registerRoutes = async (
}); });
const identityUaService = identityUaServiceFactory({ const identityUaService = identityUaServiceFactory({
identityDAL,
permissionService, permissionService,
identityAccessTokenDAL, identityAccessTokenDAL,
identityUaClientSecretDAL, identityUaClientSecretDAL,
@@ -1652,6 +1667,7 @@ export const registerRoutes = async (
}); });
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityDAL,
identityKubernetesAuthDAL, identityKubernetesAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
@@ -1665,6 +1681,7 @@ export const registerRoutes = async (
membershipIdentityDAL membershipIdentityDAL
}); });
const identityGcpAuthService = identityGcpAuthServiceFactory({ const identityGcpAuthService = identityGcpAuthServiceFactory({
identityDAL,
identityGcpAuthDAL, identityGcpAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1674,6 +1691,7 @@ export const registerRoutes = async (
}); });
const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({ const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityAliCloudAuthDAL, identityAliCloudAuthDAL,
@@ -1683,6 +1701,7 @@ export const registerRoutes = async (
}); });
const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({ const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityTlsCertAuthDAL, identityTlsCertAuthDAL,
licenseService, licenseService,
@@ -1692,6 +1711,7 @@ export const registerRoutes = async (
}); });
const identityAwsAuthService = identityAwsAuthServiceFactory({ const identityAwsAuthService = identityAwsAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityAwsAuthDAL, identityAwsAuthDAL,
@@ -1701,6 +1721,7 @@ export const registerRoutes = async (
}); });
const identityAzureAuthService = identityAzureAuthServiceFactory({ const identityAzureAuthService = identityAzureAuthServiceFactory({
identityDAL,
identityAzureAuthDAL, identityAzureAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1710,6 +1731,7 @@ export const registerRoutes = async (
}); });
const identityOciAuthService = identityOciAuthServiceFactory({ const identityOciAuthService = identityOciAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityOciAuthDAL, identityOciAuthDAL,
@@ -1733,6 +1755,7 @@ export const registerRoutes = async (
}); });
const identityOidcAuthService = identityOidcAuthServiceFactory({ const identityOidcAuthService = identityOidcAuthServiceFactory({
identityDAL,
identityOidcAuthDAL, identityOidcAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1743,6 +1766,7 @@ export const registerRoutes = async (
}); });
const identityJwtAuthService = identityJwtAuthServiceFactory({ const identityJwtAuthService = identityJwtAuthServiceFactory({
identityDAL,
identityJwtAuthDAL, identityJwtAuthDAL,
orgDAL, orgDAL,
permissionService, permissionService,
@@ -2296,6 +2320,7 @@ export const registerRoutes = async (
groupProject: groupProjectService, groupProject: groupProjectService,
permission: permissionService, permission: permissionService,
org: orgService, org: orgService,
subOrganization: subOrgService,
oidc: oidcService, oidc: oidcService,
apiKey: apiKeyService, apiKey: apiKeyService,
authToken: tokenService, authToken: tokenService,
@@ -94,6 +94,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
decodedToken.userId, decodedToken.userId,
decodedToken.organizationId, decodedToken.organizationId,
decodedToken.authMethod, decodedToken.authMethod,
decodedToken.organizationId,
decodedToken.organizationId decodedToken.organizationId
); );
if (org && org.userTokenExpiration) { if (org && org.userTokenExpiration) {
@@ -73,12 +73,12 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAliCloudAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAliCloudAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAliCloudAuth.login(req.body); await server.services.identityAliCloudAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH, type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
metadata: { metadata: {
@@ -40,12 +40,12 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAwsAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAwsAuth.login(req.body); await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH, type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: { metadata: {
@@ -35,12 +35,12 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAzureAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAzureAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAzureAuth.login(req.body); await server.services.identityAzureAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH, type: EventType.LOGIN_IDENTITY_AZURE_AUTH,
metadata: { metadata: {
@@ -35,12 +35,12 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityGcpAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityGcpAuth, accessToken, identityAccessToken, identity } =
await server.services.identityGcpAuth.login(req.body); await server.services.identityGcpAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_GCP_AUTH, type: EventType.LOGIN_IDENTITY_GCP_AUTH,
metadata: { metadata: {
@@ -111,7 +111,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityJwtAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityJwtAuth, accessToken, identityAccessToken, identity } =
await server.services.identityJwtAuth.login({ await server.services.identityJwtAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
jwt: req.body.jwt jwt: req.body.jwt
@@ -119,7 +119,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_JWT_AUTH, type: EventType.LOGIN_IDENTITY_JWT_AUTH,
metadata: { metadata: {
@@ -56,7 +56,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityKubernetesAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityKubernetesAuth, accessToken, identityAccessToken, identity } =
await server.services.identityKubernetesAuth.login({ await server.services.identityKubernetesAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
jwt: req.body.jwt jwt: req.body.jwt
@@ -64,7 +64,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH, type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH,
metadata: { metadata: {
@@ -162,13 +162,13 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
const { identityId, user } = req.passportMachineIdentity; const { identityId, user } = req.passportMachineIdentity;
const { accessToken, identityLdapAuth, identityMembershipOrg } = await server.services.identityLdapAuth.login({ const { accessToken, identityLdapAuth, identity } = await server.services.identityLdapAuth.login({
identityId identityId
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH, type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
metadata: { metadata: {
@@ -52,12 +52,12 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityOciAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityOciAuth, accessToken, identityAccessToken, identity } =
await server.services.identityOciAuth.login(req.body); await server.services.identityOciAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_OCI_AUTH, type: EventType.LOGIN_IDENTITY_OCI_AUTH,
metadata: { metadata: {
@@ -59,7 +59,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityOidcAuth, accessToken, identityAccessToken, identityMembershipOrg, oidcTokenData } = const { identityOidcAuth, accessToken, identityAccessToken, identity, oidcTokenData } =
await server.services.identityOidcAuth.login({ await server.services.identityOidcAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
jwt: req.body.jwt jwt: req.body.jwt
@@ -67,7 +67,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_OIDC_AUTH, type: EventType.LOGIN_IDENTITY_OIDC_AUTH,
metadata: { metadata: {
@@ -0,0 +1,137 @@
import { z } from "zod";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { ApiDocsTags, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const sanitizedOrgIdentityMembershipSchema = z.object({
id: z.string().uuid(),
orgId: z.string(),
identityId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
hide: true,
// this is hidden so not updating tags
tags: [ApiDocsTags.ProjectIdentities],
description: "Create org identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
}),
z.object({
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(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
})
])
)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
.max(1)
}),
response: {
200: z.object({
identityMembership: sanitizedOrgIdentityMembershipSchema
})
}
},
handler: async (req) => {
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
data: {
identityId: req.params.identityId,
roles: req.body.roles
}
});
return {
identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId }
};
}
});
server.route({
method: "DELETE",
url: "/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
hide: true,
tags: [ApiDocsTags.ProjectIdentities],
description: "Delete org identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
identityMembership: sanitizedOrgIdentityMembershipSchema
})
}
},
handler: async (req) => {
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
selector: {
identityId: req.params.identityId
}
});
return {
identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId }
};
}
});
};
@@ -249,7 +249,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
authMethods: z.array(z.string()), authMethods: z.array(z.string()),
activeLockoutAuthMethods: z.array(z.string()) activeLockoutAuthMethods: z.array(z.string())
}) })
@@ -393,7 +393,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}).array(), }).array(),
@@ -64,7 +64,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
throw new BadRequestError({ message: "Missing TLS certificate in header" }); throw new BadRequestError({ message: "Missing TLS certificate in header" });
} }
const { identityTlsCertAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityTlsCertAuth, accessToken, identityAccessToken, identity } =
await server.services.identityTlsCertAuth.login({ await server.services.identityTlsCertAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
clientCertificate: clientCertificate as string clientCertificate: clientCertificate as string
@@ -72,7 +72,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH, type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH,
metadata: { metadata: {
@@ -319,7 +319,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityTokenAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityTokenAuth, accessToken, identityAccessToken, identity } =
await server.services.identityTokenAuth.createTokenAuthToken({ await server.services.identityTokenAuth.createTokenAuthToken({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@@ -332,7 +332,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.CREATE_TOKEN_IDENTITY_TOKEN_AUTH, type: EventType.CREATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: { metadata: {
@@ -52,14 +52,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessToken, accessToken,
identityAccessToken, identityAccessToken,
validClientSecretInfo, validClientSecretInfo,
identityMembershipOrg, identity,
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL accessTokenMaxTTL
} = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp); } = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH, type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: { metadata: {
+2
View File
@@ -33,6 +33,7 @@ import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-rou
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router"; import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router"; import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router";
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router"; import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
import { registerOrgIdentityMembershipRouter } from "./identity-org-membership-router";
import { registerIdentityProjectRouter } from "./identity-project-router"; import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerIdentityRouter } from "./identity-router"; import { registerIdentityRouter } from "./identity-router";
import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router"; import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router";
@@ -90,6 +91,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
); );
await server.register(registerPasswordRouter, { prefix: "/password" }); await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerOrgRouter, { prefix: "/organization" }); await server.register(registerOrgRouter, { prefix: "/organization" });
await server.register(registerOrgIdentityMembershipRouter, { prefix: "/organization" });
await server.register(registerAdminRouter, { prefix: "/admin" }); await server.register(registerAdminRouter, { prefix: "/admin" });
await server.register(registerOrgAdminRouter, { prefix: "/organization-admin" }); await server.register(registerOrgAdminRouter, { prefix: "/organization-admin" });
await server.register(registerUserRouter, { prefix: "/user" }); await server.register(registerUserRouter, { prefix: "/user" });
@@ -2,6 +2,7 @@ import RE2 from "re2";
import { z } from "zod"; import { z } from "zod";
import { import {
AccessScope,
AuditLogsSchema, AuditLogsSchema,
GroupsSchema, GroupsSchema,
IncidentContactsSchema, IncidentContactsSchema,
@@ -59,7 +60,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
organization: sanitizedOrganizationSchema organization: sanitizedOrganizationSchema.extend({
subOrganization: z
.object({
id: z.string(),
name: z.string()
})
.optional()
})
}) })
} }
}, },
@@ -69,6 +77,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.id, req.permission.id,
req.params.organizationId, req.params.organizationId,
req.permission.authMethod, req.permission.authMethod,
req.permission.rootOrgId,
req.permission.orgId req.permission.orgId
); );
return { organization }; return { organization };
@@ -467,4 +476,68 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
return { groups }; return { groups };
} }
}); });
server.route({
method: "GET",
url: "/users/available",
schema: {
response: {
200: z.object({
users: z
.object({
id: z.string().uuid(),
username: z.string(),
email: z.string().nullable().optional(),
firstName: z.string().nullable().optional(),
lastName: z.string().nullable().optional()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { users } = await server.services.membershipUser.listAvailableUsers({
permission: req.permission,
scopeData: {
orgId: req.permission.orgId,
scope: AccessScope.Organization
},
data: {}
});
return { users };
}
});
server.route({
method: "GET",
url: "/identities/available",
schema: {
response: {
200: z.object({
identities: z
.object({
id: z.string().uuid(),
name: z.string(),
hasDeleteProtection: z.boolean()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { identities } = await server.services.membershipIdentity.listAvailableIdentities({
permission: req.permission,
scopeData: {
orgId: req.permission.orgId,
scope: AccessScope.Organization
},
data: {}
});
return { identities };
}
});
}; };
@@ -60,7 +60,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}) })
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, TAppConnections } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope, TAppConnections } from "@app/db/schemas";
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci"; import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service"; import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb"; import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
@@ -215,13 +215,14 @@ export const appConnectionServiceFactory = ({
) )
); );
} else { } else {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read, OrgPermissionAppConnectionActions.Read,
@@ -268,13 +269,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId }) subject(ProjectPermissionSub.AppConnections, { connectionId })
); );
} else { } else {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read, OrgPermissionAppConnectionActions.Read,
@@ -318,13 +320,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id }) subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
); );
} else { } else {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read, OrgPermissionAppConnectionActions.Read,
@@ -342,13 +345,14 @@ export const appConnectionServiceFactory = ({
{ method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO, { method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (projectId) { if (projectId) {
const project = await projectDAL.findProjectById(projectId); const project = await projectDAL.findProjectById(projectId);
@@ -477,13 +481,14 @@ export const appConnectionServiceFactory = ({
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections." "Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
); );
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (appConnection.projectId) { if (appConnection.projectId) {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
@@ -635,13 +640,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId }) subject(ProjectPermissionSub.AppConnections, { connectionId })
); );
} else { } else {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Delete, OrgPermissionAppConnectionActions.Delete,
@@ -704,13 +710,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId }) subject(ProjectPermissionSub.AppConnections, { connectionId })
); );
} else { } else {
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionAppConnectionActions.Connect, OrgPermissionAppConnectionActions.Connect,
@@ -747,13 +754,14 @@ export const appConnectionServiceFactory = ({
}; };
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId?: string) => { const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId?: string) => {
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
let availableProjectConnections: TAppConnections[] = []; let availableProjectConnections: TAppConnections[] = [];
@@ -805,13 +813,14 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
appConnection.orgId, orgId: appConnection.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read, OrgPermissionAppConnectionActions.Read,
@@ -3,10 +3,11 @@ import { Knex } from "knex";
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas"; import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type"; import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal"; import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TUserDALFactory } from "../user/user-dal"; import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal"; import { TTokenDALFactory } from "./auth-token-dal";
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
@@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = { type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory; tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">; userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findOne">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">; membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">;
}; };
@@ -80,7 +82,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
} }
}; };
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TAuthTokenServiceFactoryDep) => { export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => { const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type); const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig(); const appCfg = getConfig();
@@ -194,7 +196,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
}; };
// to parse jwt identity in inject identity plugin // to parse jwt identity in inject identity plugin
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, subOrganizationSelector?: string) => {
const session = await tokenDAL.findOneTokenSession({ const session = await tokenDAL.findOneTokenSession({
id: token.tokenVersionId, id: token.tokenVersionId,
userId: token.userId userId: token.userId
@@ -207,7 +209,35 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
const user = await userDAL.findById(session.userId); const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` }); if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
let orgId = "";
let rootOrgId = "";
let parentOrgId = "";
if (token.organizationId) { if (token.organizationId) {
if (subOrganizationSelector) {
const subOrganization = await orgDAL.findOne({
rootOrgId: token.organizationId,
slug: subOrganizationSelector
});
if (!subOrganization)
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,
scopeOrgId: subOrganization.id,
scope: AccessScope.Organization
});
if (!orgMembership) {
throw new ForbiddenRequestError({ message: "User not member of organization" });
}
if (!orgMembership.isActive) {
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
}
orgId = subOrganization.id;
rootOrgId = token.organizationId;
parentOrgId = subOrganization.parentOrgId as string;
} else {
const orgMembership = await membershipUserDAL.findOne({ const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id, actorUserId: user.id,
scopeOrgId: token.organizationId, scopeOrgId: token.organizationId,
@@ -217,12 +247,18 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
if (!orgMembership) { if (!orgMembership) {
throw new ForbiddenRequestError({ message: "User not member of organization" }); throw new ForbiddenRequestError({ message: "User not member of organization" });
} }
if (!orgMembership.isActive) { if (!orgMembership.isActive) {
throw new ForbiddenRequestError({ message: "User organization membership is inactive" }); throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
} }
orgId = token.organizationId;
rootOrgId = token.organizationId;
parentOrgId = token.organizationId;
}
} }
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; return { user, tokenVersionId: token.tokenVersionId, orgId, rootOrgId, parentOrgId };
}; };
return { return {
@@ -258,7 +258,13 @@ export const authSignupServiceFactory = ({
let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME; let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME;
if (organizationId) { if (organizationId) {
const org = await orgService.findOrganizationById(user.id, organizationId, authMethod, organizationId); const org = await orgService.findOrganizationById(
user.id,
organizationId,
authMethod,
organizationId,
organizationId
);
if (org && org.userTokenExpiration) { if (org && org.userTokenExpiration) {
tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration); tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration; refreshTokenExpiresIn = org.userTokenExpiration;
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -26,13 +27,14 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
roleDAL roleDAL
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => { }: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => { const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
// TODO: will need to change if we add support for ldap, oidc, etc. // TODO: will need to change if we add support for ldap, oidc, etc.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
@@ -48,13 +50,14 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
dto: TSyncExternalGroupOrgMembershipRoleMappingsDTO, dto: TSyncExternalGroupOrgMembershipRoleMappingsDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
// TODO: will need to change if we add support for ldap, oidc, etc. // TODO: will need to change if we add support for ldap, oidc, etc.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
@@ -1,4 +1,4 @@
import { OrgMembershipRole } from "@app/db/schemas"; import { OrganizationActionScope, OrgMembershipRole } from "@app/db/schemas";
import { import {
AuditLogInfo, AuditLogInfo,
EventType, EventType,
@@ -89,13 +89,14 @@ export const externalMigrationServiceFactory = ({
throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." }); throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." });
} }
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
actor,
orgId: actorOrgId,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.Any
); });
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" }); throw new ForbiddenRequestError({ message: "Only admins can import data" });
} }
@@ -136,13 +137,14 @@ export const externalMigrationServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod actorAuthMethod
}: TImportVaultDataDTO) => { }: TImportVaultDataDTO) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
actor,
orgId: actorOrgId,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.Any
); });
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" }); throw new ForbiddenRequestError({ message: "Only admins can import data" });
@@ -192,13 +194,14 @@ export const externalMigrationServiceFactory = ({
actorAuthMethod, actorAuthMethod,
provider provider
}: THasCustomVaultMigrationDTO) => { }: THasCustomVaultMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
actor,
orgId: actorOrgId,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.Any
); });
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can check custom migration status" }); throw new ForbiddenRequestError({ message: "Only admins can check custom migration status" });
@@ -247,13 +250,14 @@ export const externalMigrationServiceFactory = ({
}; };
const createVaultExternalMigration = async ({ namespace, connectionId, actor }: TCreateVaultExternalMigrationDTO) => { const createVaultExternalMigration = async ({ namespace, connectionId, actor }: TCreateVaultExternalMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can configure vault external migration" }); throw new ForbiddenRequestError({ message: "Only admins can configure vault external migration" });
@@ -298,13 +302,14 @@ export const externalMigrationServiceFactory = ({
connectionId, connectionId,
actor actor
}: TUpdateVaultExternalMigrationDTO) => { }: TUpdateVaultExternalMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can update vault external migration" }); throw new ForbiddenRequestError({ message: "Only admins can update vault external migration" });
@@ -332,13 +337,14 @@ export const externalMigrationServiceFactory = ({
}; };
const getVaultExternalMigrationConfigs = async ({ actor }: { actor: OrgServiceActor }) => { const getVaultExternalMigrationConfigs = async ({ actor }: { actor: OrgServiceActor }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault external migration configs" }); throw new ForbiddenRequestError({ message: "Only admins can view vault external migration configs" });
@@ -352,13 +358,14 @@ export const externalMigrationServiceFactory = ({
}; };
const getVaultNamespaces = async ({ actor }: { actor: OrgServiceActor }) => { const getVaultNamespaces = async ({ actor }: { actor: OrgServiceActor }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault namespaces" }); throw new ForbiddenRequestError({ message: "Only admins can view vault namespaces" });
@@ -380,13 +387,14 @@ export const externalMigrationServiceFactory = ({
}; };
const getVaultPolicies = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => { const getVaultPolicies = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault policies" }); throw new ForbiddenRequestError({ message: "Only admins can view vault policies" });
@@ -422,13 +430,14 @@ export const externalMigrationServiceFactory = ({
}; };
const getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => { const getVaultMounts = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" }); throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" });
@@ -472,13 +481,14 @@ export const externalMigrationServiceFactory = ({
namespace: string; namespace: string;
mountPath: string; mountPath: string;
}) => { }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" }); throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" });
@@ -531,13 +541,14 @@ export const externalMigrationServiceFactory = ({
vaultSecretPath: string; vaultSecretPath: string;
auditLogInfo: AuditLogInfo; auditLogInfo: AuditLogInfo;
}) => { }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" }); throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" });
@@ -617,13 +628,14 @@ export const externalMigrationServiceFactory = ({
}; };
const deleteVaultExternalMigration = async ({ id, actor }: TDeleteVaultExternalMigrationDTO) => { const deleteVaultExternalMigration = async ({ id, actor }: TDeleteVaultExternalMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can delete vault external migration configs" }); throw new ForbiddenRequestError({ message: "Only admins can delete vault external migration configs" });
@@ -653,13 +665,14 @@ export const externalMigrationServiceFactory = ({
namespace: string; namespace: string;
authType?: string; authType?: string;
}) => { }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault auth mounts" }); throw new ForbiddenRequestError({ message: "Only admins can view vault auth mounts" });
@@ -704,13 +717,14 @@ export const externalMigrationServiceFactory = ({
namespace: string; namespace: string;
mountPath: string; mountPath: string;
}) => { }) => {
const { hasRole } = await permissionService.getOrgPermission( const { hasRole } = await permissionService.getOrgPermission({
actor.type, actorId: actor.id,
actor.id, actor: actor.type,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorOrgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) { if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault Kubernetes auth roles" }); throw new ForbiddenRequestError({ message: "Only admins can view vault Kubernetes auth roles" });
@@ -19,6 +19,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
.select(selectAllTableCols(TableName.IdentityAccessToken)) .select(selectAllTableCols(TableName.IdentityAccessToken))
.select(db.ref("name").withSchema(TableName.Identity)) .select(db.ref("name").withSchema(TableName.Identity))
.select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId"))
.first(); .first();
return doc; return doc;
@@ -8,6 +8,7 @@ import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-to
import { AuthTokenType } from "../auth/auth-type"; import { AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types"; import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types";
@@ -19,6 +20,7 @@ type TIdentityAccessTokenServiceFactoryDep = {
"updateIdentityAccessTokenStatus" | "getIdentityTokenDetailsInCache" "updateIdentityAccessTokenStatus" | "getIdentityTokenDetailsInCache"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">;
orgDAL: Pick<TOrgDALFactory, "findOne">;
}; };
export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>; export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>;
@@ -27,7 +29,8 @@ export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL, identityAccessTokenDAL,
accessTokenQueue, accessTokenQueue,
identityDAL, identityDAL,
membershipIdentityDAL membershipIdentityDAL,
orgDAL
}: TIdentityAccessTokenServiceFactoryDep) => { }: TIdentityAccessTokenServiceFactoryDep) => {
const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => { const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const { const {
@@ -181,7 +184,11 @@ export const identityAccessTokenServiceFactory = ({
return { revokedToken }; return { revokedToken };
}; };
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => { const fnValidateIdentityAccessToken = async (
token: TIdentityAccessTokenJwtPayload,
subOrganizationSelector?: string,
ipAddress?: string
) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({ const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId, [`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
isAccessTokenRevoked: false isAccessTokenRevoked: false
@@ -202,14 +209,41 @@ export const identityAccessTokenServiceFactory = ({
trustedIps: trustedIps as TIp[] trustedIps: trustedIps as TIp[]
}); });
} }
let orgId = "";
let parentOrgId = "";
const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId });
const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id;
if (subOrganizationSelector) {
const subOrganization = await orgDAL.findOne({ rootOrgId, slug: subOrganizationSelector });
if (!subOrganization)
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
const identityOrgMembership = await membershipIdentityDAL.findOne({ const identityOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization, scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId actorIdentityId: identityAccessToken.identityId,
scopeOrgId: subOrganization.id
}); });
if (!identityOrgMembership) { if (!identityOrgMembership) {
throw new BadRequestError({ message: "Identity does not belong to any organization" }); throw new BadRequestError({ message: "Identity does not belong to any organization" });
} }
orgId = subOrganization.id;
parentOrgId = subOrganization.parentOrgId as string;
} else {
const identityOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId,
scopeOrgId: rootOrgId
});
if (!identityOrgMembership) {
throw new BadRequestError({ message: "Identity does not belong to any organization" });
}
orgId = rootOrgId;
parentOrgId = rootOrgId;
}
let { accessTokenNumUses } = identityAccessToken; let { accessTokenNumUses } = identityAccessToken;
const tokenStatusInCache = await accessTokenQueue.getIdentityTokenDetailsInCache(identityAccessToken.id); const tokenStatusInCache = await accessTokenQueue.getIdentityTokenDetailsInCache(identityAccessToken.id);
@@ -219,7 +253,7 @@ export const identityAccessTokenServiceFactory = ({
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses }); await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1); await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1);
return { ...identityAccessToken, orgId: identityOrgMembership.scopeOrgId }; return { ...identityAccessToken, orgId, rootOrgId, parentOrgId };
}; };
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken }; return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -13,11 +13,18 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -34,12 +41,13 @@ import {
} from "./identity-alicloud-auth-types"; } from "./identity-alicloud-auth-types";
type TIdentityAliCloudAuthServiceFactoryDep = { type TIdentityAliCloudAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityAliCloudAuthDAL: Pick< identityAliCloudAuthDAL: Pick<
TIdentityAliCloudAuthDALFactory, TIdentityAliCloudAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete" "findOne" | "transaction" | "create" | "updateById" | "delete"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -48,6 +56,7 @@ type TIdentityAliCloudAuthServiceFactoryDep = {
export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>; export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>;
export const identityAliCloudAuthServiceFactory = ({ export const identityAliCloudAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityAliCloudAuthDAL, identityAliCloudAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
@@ -63,12 +72,8 @@ export const identityAliCloudAuthServiceFactory = ({
}); });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityAliCloudAuth.identityId);
actorIdentityId: identityAliCloudAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const requestUrl = new URL("https://sts.aliyuncs.com"); const requestUrl = new URL("https://sts.aliyuncs.com");
@@ -93,8 +98,8 @@ export const identityAliCloudAuthServiceFactory = ({
// Generate the token // Generate the token
const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ {
lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH, lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH,
lastLoginTime: new Date() lastLoginTime: new Date()
@@ -135,7 +140,7 @@ export const identityAliCloudAuthServiceFactory = ({
identityAliCloudAuth, identityAliCloudAuth,
accessToken, accessToken,
identityAccessToken, identityAccessToken,
identityMembershipOrg identity
}; };
}; };
@@ -162,6 +167,9 @@ export const identityAliCloudAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -173,13 +181,14 @@ export const identityAliCloudAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -238,6 +247,9 @@ export const identityAliCloudAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
throw new NotFoundError({ throw new NotFoundError({
@@ -255,13 +267,14 @@ export const identityAliCloudAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -304,6 +317,9 @@ export const identityAliCloudAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -313,13 +329,14 @@ export const identityAliCloudAuthServiceFactory = ({
const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId }); const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
}; };
@@ -339,27 +356,32 @@ export const identityAliCloudAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have Alibaba Cloud auth" message: "The identity does not have Alibaba Cloud auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityMembershipOrg.identity.id, actor: ActorType.IDENTITY,
identityMembershipOrg.scopeOrgId, actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import axios from "axios"; import axios from "axios";
import RE2 from "re2"; import RE2 from "re2";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -13,10 +13,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -35,9 +42,10 @@ import {
} from "./identity-aws-auth-types"; } from "./identity-aws-auth-types";
type TIdentityAwsAuthServiceFactoryDep = { type TIdentityAwsAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">; identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -80,6 +88,7 @@ function isValidAwsRegion(region: string | null): boolean {
} }
export const identityAwsAuthServiceFactory = ({ export const identityAwsAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityAwsAuthDAL, identityAwsAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
@@ -93,11 +102,8 @@ export const identityAwsAuthServiceFactory = ({
throw new NotFoundError({ message: "AWS auth method not found for identity, did you configure AWS auth?" }); throw new NotFoundError({ message: "AWS auth method not found for identity, did you configure AWS auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityAwsAuth.identityId);
actorIdentityId: identityAwsAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString(); const body: string = Buffer.from(iamRequestBody, "base64").toString();
@@ -159,8 +165,8 @@ export const identityAwsAuthServiceFactory = ({
} }
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ {
lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH, lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH,
lastLoginTime: new Date() lastLoginTime: new Date()
@@ -212,7 +218,7 @@ export const identityAwsAuthServiceFactory = ({
} }
); );
return { accessToken, identityAwsAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityAwsAuth, identityAccessToken, identity };
}; };
const attachAwsAuth = async ({ const attachAwsAuth = async ({
@@ -240,6 +246,9 @@ export const identityAwsAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -251,13 +260,14 @@ export const identityAwsAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -320,6 +330,9 @@ export const identityAwsAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) {
throw new NotFoundError({ throw new NotFoundError({
@@ -336,13 +349,14 @@ export const identityAwsAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -387,6 +401,9 @@ export const identityAwsAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -396,13 +413,14 @@ export const identityAwsAuthServiceFactory = ({
const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId }); const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIdentityAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...awsIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
}; };
@@ -422,27 +440,32 @@ export const identityAwsAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have aws auth" message: "The identity does not have aws auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityMembershipOrg.identity.id, actor: ActorType.IDENTITY,
identityMembershipOrg.scopeOrgId, actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -30,11 +37,12 @@ import {
} from "./identity-azure-auth-types"; } from "./identity-azure-auth-types";
type TIdentityAzureAuthServiceFactoryDep = { type TIdentityAzureAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAzureAuthDAL: Pick< identityAzureAuthDAL: Pick<
TIdentityAzureAuthDALFactory, TIdentityAzureAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete" "findOne" | "transaction" | "create" | "updateById" | "delete"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -44,6 +52,7 @@ type TIdentityAzureAuthServiceFactoryDep = {
export type TIdentityAzureAuthServiceFactory = ReturnType<typeof identityAzureAuthServiceFactory>; export type TIdentityAzureAuthServiceFactory = ReturnType<typeof identityAzureAuthServiceFactory>;
export const identityAzureAuthServiceFactory = ({ export const identityAzureAuthServiceFactory = ({
identityDAL,
identityAzureAuthDAL, identityAzureAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -57,11 +66,8 @@ export const identityAzureAuthServiceFactory = ({
throw new NotFoundError({ message: "Azure auth method not found for identity, did you configure Azure Auth?" }); throw new NotFoundError({ message: "Azure auth method not found for identity, did you configure Azure Auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityAzureAuth.identityId);
actorIdentityId: identityAzureAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const azureIdentity = await validateAzureIdentity({ const azureIdentity = await validateAzureIdentity({
tenantId: identityAzureAuth.tenantId, tenantId: identityAzureAuth.tenantId,
@@ -86,8 +92,8 @@ export const identityAzureAuthServiceFactory = ({
} }
const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ {
lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH, lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH,
lastLoginTime: new Date() lastLoginTime: new Date()
@@ -125,7 +131,7 @@ export const identityAzureAuthServiceFactory = ({
} }
); );
return { accessToken, identityAzureAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityAzureAuth, identityAccessToken, identity };
}; };
const attachAzureAuth = async ({ const attachAzureAuth = async ({
@@ -153,6 +159,9 @@ export const identityAzureAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -163,13 +172,14 @@ export const identityAzureAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -232,6 +242,9 @@ export const identityAzureAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to update Azure Auth" message: "Failed to update Azure Auth"
@@ -247,13 +260,14 @@ export const identityAzureAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -301,6 +315,9 @@ export const identityAzureAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have Azure Auth attached" message: "The identity does not have Azure Auth attached"
@@ -309,13 +326,14 @@ export const identityAzureAuthServiceFactory = ({
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId }); const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityAzureAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...identityAzureAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -336,27 +354,32 @@ export const identityAzureAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have azure auth" message: "The identity does not have azure auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityMembershipOrg.identity.id, actor: ActorType.IDENTITY,
identityMembershipOrg.scopeOrgId, actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -31,8 +38,9 @@ import {
} from "./identity-gcp-auth-types"; } from "./identity-gcp-auth-types";
type TIdentityGcpAuthServiceFactoryDep = { type TIdentityGcpAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">; identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -42,6 +50,7 @@ type TIdentityGcpAuthServiceFactoryDep = {
export type TIdentityGcpAuthServiceFactory = ReturnType<typeof identityGcpAuthServiceFactory>; export type TIdentityGcpAuthServiceFactory = ReturnType<typeof identityGcpAuthServiceFactory>;
export const identityGcpAuthServiceFactory = ({ export const identityGcpAuthServiceFactory = ({
identityDAL,
identityGcpAuthDAL, identityGcpAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -55,13 +64,8 @@ export const identityGcpAuthServiceFactory = ({
throw new NotFoundError({ message: "GCP auth method not found for identity, did you configure GCP auth?" }); throw new NotFoundError({ message: "GCP auth method not found for identity, did you configure GCP auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityGcpAuth.identityId);
actorIdentityId: identityGcpAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
}
let gcpIdentityDetails: TGcpIdentityDetails; let gcpIdentityDetails: TGcpIdentityDetails;
switch (identityGcpAuth.type) { switch (identityGcpAuth.type) {
@@ -125,8 +129,8 @@ export const identityGcpAuthServiceFactory = ({
} }
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ {
lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH, lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH,
lastLoginTime: new Date() lastLoginTime: new Date()
@@ -164,7 +168,7 @@ export const identityGcpAuthServiceFactory = ({
} }
); );
return { accessToken, identityGcpAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityGcpAuth, identityAccessToken, identity };
}; };
const attachGcpAuth = async ({ const attachGcpAuth = async ({
@@ -193,6 +197,9 @@ export const identityGcpAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -204,13 +211,14 @@ export const identityGcpAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -274,6 +282,9 @@ export const identityGcpAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -290,13 +301,14 @@ export const identityGcpAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -345,6 +357,9 @@ export const identityGcpAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -354,13 +369,14 @@ export const identityGcpAuthServiceFactory = ({
const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId }); const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityGcpAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...identityGcpAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -381,28 +397,33 @@ export const identityGcpAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have gcp auth" message: "The identity does not have gcp auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityMembershipOrg.identity.id, actor: ActorType.IDENTITY,
identityMembershipOrg.scopeOrgId, actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -3,7 +3,7 @@ import https from "https";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { JwksClient } from "jwks-rsa"; import { JwksClient } from "jwks-rsa";
import { AccessScope, IdentityAuthMethod, TIdentityJwtAuthsUpdate } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TIdentityJwtAuthsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -24,6 +24,7 @@ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getValueByDot } from "@app/lib/template/dot-access"; import { getValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -43,8 +44,9 @@ import {
} from "./identity-jwt-auth-types"; } from "./identity-jwt-auth-types";
type TIdentityJwtAuthServiceFactoryDep = { type TIdentityJwtAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityJwtAuthDAL: TIdentityJwtAuthDALFactory; identityJwtAuthDAL: TIdentityJwtAuthDALFactory;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -55,6 +57,7 @@ type TIdentityJwtAuthServiceFactoryDep = {
export type TIdentityJwtAuthServiceFactory = ReturnType<typeof identityJwtAuthServiceFactory>; export type TIdentityJwtAuthServiceFactory = ReturnType<typeof identityJwtAuthServiceFactory>;
export const identityJwtAuthServiceFactory = ({ export const identityJwtAuthServiceFactory = ({
identityDAL,
identityJwtAuthDAL, identityJwtAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
permissionService, permissionService,
@@ -69,19 +72,12 @@ export const identityJwtAuthServiceFactory = ({
throw new NotFoundError({ message: "JWT auth method not found for identity, did you configure JWT auth?" }); throw new NotFoundError({ message: "JWT auth method not found for identity, did you configure JWT auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityJwtAuth.identityId);
actorIdentityId: identityJwtAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityJwtAuth.identityId}' not found`
});
}
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId orgId: identity.orgId
}); });
const decodedToken = crypto.jwt().decode(jwtValue, { complete: true }); const decodedToken = crypto.jwt().decode(jwtValue, { complete: true });
@@ -211,12 +207,9 @@ export const identityJwtAuthServiceFactory = ({
} }
const identityAccessToken = await identityJwtAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityJwtAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -251,7 +244,7 @@ export const identityJwtAuthServiceFactory = ({
} }
); );
return { accessToken, identityJwtAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityJwtAuth, identityAccessToken, identity };
}; };
const attachJwtAuth = async ({ const attachJwtAuth = async ({
@@ -284,6 +277,9 @@ export const identityJwtAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to add JWT Auth to already configured identity" message: "Failed to add JWT Auth to already configured identity"
@@ -294,13 +290,14 @@ export const identityJwtAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -387,6 +384,9 @@ export const identityJwtAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -403,13 +403,14 @@ export const identityJwtAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
@@ -491,6 +492,9 @@ export const identityJwtAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -498,13 +502,14 @@ export const identityJwtAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
@@ -539,6 +544,9 @@ export const identityJwtAuthServiceFactory = ({
if (!identityMembershipOrg) { if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Failed to find identity" }); throw new NotFoundError({ message: "Failed to find identity" });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -546,23 +554,25 @@ export const identityJwtAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityMembershipOrg.identity.id, actor: ActorType.IDENTITY,
identityMembershipOrg.scopeOrgId, actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -3,7 +3,12 @@ import axios, { AxiosError } from "axios";
import https from "https"; import https from "https";
import RE2 from "re2"; import RE2 from "re2";
import { AccessScope, IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; import {
AccessScope,
IdentityAuthMethod,
OrganizationActionScope,
TIdentityKubernetesAuthsUpdate
} from "@app/db/schemas";
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TGatewayV2DALFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; import { TGatewayV2DALFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal";
@@ -21,13 +26,20 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -48,12 +60,13 @@ import {
} from "./identity-kubernetes-auth-types"; } from "./identity-kubernetes-auth-types";
type TIdentityKubernetesAuthServiceFactoryDep = { type TIdentityKubernetesAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityKubernetesAuthDAL: Pick< identityKubernetesAuthDAL: Pick<
TIdentityKubernetesAuthDALFactory, TIdentityKubernetesAuthDALFactory,
"create" | "findOne" | "transaction" | "updateById" | "delete" "create" | "findOne" | "transaction" | "updateById" | "delete"
>; >;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
@@ -69,6 +82,7 @@ export type TIdentityKubernetesAuthServiceFactory = ReturnType<typeof identityKu
const GATEWAY_AUTH_DEFAULT_HOST = "https://kubernetes.default.svc.cluster.local"; const GATEWAY_AUTH_DEFAULT_HOST = "https://kubernetes.default.svc.cluster.local";
export const identityKubernetesAuthServiceFactory = ({ export const identityKubernetesAuthServiceFactory = ({
identityDAL,
identityKubernetesAuthDAL, identityKubernetesAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -175,19 +189,12 @@ export const identityKubernetesAuthServiceFactory = ({
}); });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityKubernetesAuth.identityId);
actorIdentityId: identityKubernetesAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityKubernetesAuth.identityId}' not found`
});
}
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId orgId: identity.orgId
}); });
let caCert = ""; let caCert = "";
@@ -430,12 +437,9 @@ export const identityKubernetesAuthServiceFactory = ({
} }
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -475,7 +479,7 @@ export const identityKubernetesAuthServiceFactory = ({
} }
); );
return { accessToken, identityKubernetesAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityKubernetesAuth, identityAccessToken, identity };
}; };
const attachKubernetesAuth = async ({ const attachKubernetesAuth = async ({
@@ -508,6 +512,9 @@ export const identityKubernetesAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -519,13 +526,14 @@ export const identityKubernetesAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -560,13 +568,14 @@ export const identityKubernetesAuthServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -633,6 +642,9 @@ export const identityKubernetesAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -650,13 +662,14 @@ export const identityKubernetesAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -692,13 +705,14 @@ export const identityKubernetesAuthServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -779,6 +793,9 @@ export const identityKubernetesAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
if (!identityKubernetesAuth) { if (!identityKubernetesAuth) {
@@ -791,13 +808,14 @@ export const identityKubernetesAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -841,28 +859,33 @@ export const identityKubernetesAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have kubernetes auth" message: "The identity does not have kubernetes auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TIdentityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template"; import { TIdentityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template";
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns"; import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -21,6 +21,7 @@ import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { import {
BadRequestError, BadRequestError,
ForbiddenRequestError,
NotFoundError, NotFoundError,
PermissionBoundaryError, PermissionBoundaryError,
RateLimitError, RateLimitError,
@@ -56,11 +57,11 @@ type TIdentityLdapAuthServiceFactoryDep = {
TIdentityLdapAuthDALFactory, TIdentityLdapAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete" "findOne" | "transaction" | "create" | "updateById" | "delete"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: TKmsServiceFactory; kmsService: TKmsServiceFactory;
identityDAL: TIdentityDALFactory; identityDAL: Pick<TIdentityDALFactory, "findById" | "findOne">;
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory; identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
keyStore: Pick< keyStore: Pick<
TKeyStoreFactory, TKeyStoreFactory,
@@ -150,17 +151,6 @@ export const identityLdapAuthServiceFactory = ({
}; };
const login = async ({ identityId }: TLoginLdapAuthDTO) => { const login = async ({ identityId }: TLoginLdapAuthDTO) => {
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new UnauthorizedError({
message: "Invalid credentials"
});
}
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId }); const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
if (!identityLdapAuth) { if (!identityLdapAuth) {
@@ -169,7 +159,10 @@ export const identityLdapAuthServiceFactory = ({
}); });
} }
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const identity = await identityDAL.findById(identityLdapAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const plan = await licenseService.getPlan(identity.orgId);
if (!plan.ldap) { if (!plan.ldap) {
throw new BadRequestError({ throw new BadRequestError({
message: message:
@@ -178,12 +171,9 @@ export const identityLdapAuthServiceFactory = ({
} }
const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -217,7 +207,7 @@ export const identityLdapAuthServiceFactory = ({
} }
); );
return { accessToken, identityLdapAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityLdapAuth, identityAccessToken, identity };
}; };
const attachLdapAuth = async ({ const attachLdapAuth = async ({
@@ -254,6 +244,9 @@ export const identityLdapAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -265,13 +258,14 @@ export const identityLdapAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
if (templateId) { if (templateId) {
@@ -425,6 +419,9 @@ export const identityLdapAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new NotFoundError({ throw new NotFoundError({
@@ -441,13 +438,14 @@ export const identityLdapAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
if (templateId) { if (templateId) {
@@ -588,6 +586,9 @@ export const identityLdapAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -597,13 +598,14 @@ export const identityLdapAuthServiceFactory = ({
const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId }); const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
@@ -635,27 +637,32 @@ export const identityLdapAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have LDAP Auth attached" message: "The identity does not have LDAP Auth attached"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -785,13 +792,14 @@ export const identityLdapAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const deleted = await keyStore.deleteItems({ const deleted = await keyStore.deleteItems({
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import RE2 from "re2"; import RE2 from "re2";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -14,11 +14,18 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -35,9 +42,10 @@ import {
} from "./identity-oci-auth-types"; } from "./identity-oci-auth-types";
type TIdentityOciAuthServiceFactoryDep = { type TIdentityOciAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityOciAuthDAL: Pick<TIdentityOciAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">; identityOciAuthDAL: Pick<TIdentityOciAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -46,6 +54,7 @@ type TIdentityOciAuthServiceFactoryDep = {
export type TIdentityOciAuthServiceFactory = ReturnType<typeof identityOciAuthServiceFactory>; export type TIdentityOciAuthServiceFactory = ReturnType<typeof identityOciAuthServiceFactory>;
export const identityOciAuthServiceFactory = ({ export const identityOciAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityOciAuthDAL, identityOciAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
@@ -59,11 +68,8 @@ export const identityOciAuthServiceFactory = ({
throw new NotFoundError({ message: "OCI auth method not found for identity, did you configure OCI auth?" }); throw new NotFoundError({ message: "OCI auth method not found for identity, did you configure OCI auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityOciAuth.identityId);
actorIdentityId: identityOciAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
// Validate OCI host format. Ensures that the host is in "identity.<region>.oraclecloud.com" format. // Validate OCI host format. Ensures that the host is in "identity.<region>.oraclecloud.com" format.
if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) { if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) {
@@ -98,12 +104,9 @@ export const identityOciAuthServiceFactory = ({
// Generate the token // Generate the token
const identityAccessToken = await identityOciAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityOciAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -140,7 +143,7 @@ export const identityOciAuthServiceFactory = ({
identityOciAuth, identityOciAuth,
accessToken, accessToken,
identityAccessToken, identityAccessToken,
identityMembershipOrg identity
}; };
}; };
@@ -168,6 +171,9 @@ export const identityOciAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -179,13 +185,14 @@ export const identityOciAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -246,6 +253,9 @@ export const identityOciAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
throw new NotFoundError({ throw new NotFoundError({
@@ -262,13 +272,14 @@ export const identityOciAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -312,6 +323,9 @@ export const identityOciAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -321,13 +335,14 @@ export const identityOciAuthServiceFactory = ({
const ociIdentityAuth = await identityOciAuthDAL.findOne({ identityId }); const ociIdentityAuth = await identityOciAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...ociIdentityAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...ociIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
}; };
@@ -347,27 +362,32 @@ export const identityOciAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have OCI auth" message: "The identity does not have OCI auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -4,7 +4,7 @@ import https from "https";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { JwksClient } from "jwks-rsa"; import { JwksClient } from "jwks-rsa";
import { AccessScope, IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TIdentityOidcAuthsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -25,6 +25,7 @@ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getValueByDot } from "@app/lib/template/dot-access"; import { getValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -43,8 +44,9 @@ import {
} from "./identity-oidc-auth-types"; } from "./identity-oidc-auth-types";
type TIdentityOidcAuthServiceFactoryDep = { type TIdentityOidcAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityOidcAuthDAL: TIdentityOidcAuthDALFactory; identityOidcAuthDAL: TIdentityOidcAuthDALFactory;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -55,6 +57,7 @@ type TIdentityOidcAuthServiceFactoryDep = {
export type TIdentityOidcAuthServiceFactory = ReturnType<typeof identityOidcAuthServiceFactory>; export type TIdentityOidcAuthServiceFactory = ReturnType<typeof identityOidcAuthServiceFactory>;
export const identityOidcAuthServiceFactory = ({ export const identityOidcAuthServiceFactory = ({
identityDAL,
identityOidcAuthDAL, identityOidcAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
permissionService, permissionService,
@@ -69,19 +72,12 @@ export const identityOidcAuthServiceFactory = ({
throw new NotFoundError({ message: "OIDC auth method not found for identity, did you configure OIDC auth?" }); throw new NotFoundError({ message: "OIDC auth method not found for identity, did you configure OIDC auth?" });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityOidcAuth.identityId);
actorIdentityId: identityOidcAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityOidcAuth.identityId}' not found`
});
}
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId orgId: identity.orgId
}); });
let caCert = ""; let caCert = "";
@@ -182,12 +178,9 @@ export const identityOidcAuthServiceFactory = ({
} }
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -226,7 +219,7 @@ export const identityOidcAuthServiceFactory = ({
} }
); );
return { accessToken, identityOidcAuth, identityAccessToken, identityMembershipOrg, oidcTokenData: tokenData }; return { accessToken, identityOidcAuth, identityAccessToken, identity, oidcTokenData: tokenData };
}; };
const attachOidcAuth = async ({ const attachOidcAuth = async ({
@@ -259,6 +252,9 @@ export const identityOidcAuthServiceFactory = ({
if (!identityMembershipOrg) { if (!identityMembershipOrg) {
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to add OIDC Auth to already configured identity" message: "Failed to add OIDC Auth to already configured identity"
@@ -269,13 +265,14 @@ export const identityOidcAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -351,6 +348,9 @@ export const identityOidcAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -367,13 +367,14 @@ export const identityOidcAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
@@ -440,6 +441,9 @@ export const identityOidcAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -447,13 +451,14 @@ export const identityOidcAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId });
@@ -481,6 +486,9 @@ export const identityOidcAuthServiceFactory = ({
if (!identityMembershipOrg) { if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Failed to find identity" }); throw new NotFoundError({ message: "Failed to find identity" });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -488,23 +496,25 @@ export const identityOidcAuthServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -25,11 +25,12 @@ import { buildAuthMethods } from "../identity/identity-fns";
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>; export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
export const identityProjectDALFactory = (db: TDbClient) => { export const identityProjectDALFactory = (db: TDbClient) => {
const findByIdentityId = async (identityId: string, tx?: Knex) => { const findByIdentityId = async (identityId: string, orgId: string, tx?: Knex) => {
try { try {
const docs = await (tx || db.replicaNode())(TableName.Membership) const docs = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.actorIdentityId`, identityId) .where(`${TableName.Membership}.actorIdentityId`, identityId)
.where(`${TableName.Membership}.scope`, AccessScope.Project) .where(`${TableName.Membership}.scope`, AccessScope.Project)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.whereNotNull(`${TableName.Membership}.actorIdentityId`) .whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`) .join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -11,10 +11,17 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -25,12 +32,13 @@ import { TIdentityTlsCertAuthDALFactory } from "./identity-tls-cert-auth-dal";
import { TIdentityTlsCertAuthServiceFactory } from "./identity-tls-cert-auth-types"; import { TIdentityTlsCertAuthServiceFactory } from "./identity-tls-cert-auth-types";
type TIdentityTlsCertAuthServiceFactoryDep = { type TIdentityTlsCertAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityTlsCertAuthDAL: Pick< identityTlsCertAuthDAL: Pick<
TIdentityTlsCertAuthDALFactory, TIdentityTlsCertAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete" "findOne" | "transaction" | "create" | "updateById" | "delete"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
@@ -46,6 +54,7 @@ const parseSubjectDetails = (data: string) => {
}; };
export const identityTlsCertAuthServiceFactory = ({ export const identityTlsCertAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityTlsCertAuthDAL, identityTlsCertAuthDAL,
membershipIdentityDAL, membershipIdentityDAL,
@@ -61,20 +70,12 @@ export const identityTlsCertAuthServiceFactory = ({
}); });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({ const identity = await identityDAL.findById(identityTlsCertAuth.identityId);
actorIdentityId: identityTlsCertAuth.identityId, if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityTlsCertAuth.identityId}' not found`
});
}
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId orgId: identity.orgId
}); });
const caCertificate = decryptor({ const caCertificate = decryptor({
@@ -119,12 +120,9 @@ export const identityTlsCertAuthServiceFactory = ({
// Generate the token // Generate the token
const identityAccessToken = await identityTlsCertAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityTlsCertAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -161,7 +159,7 @@ export const identityTlsCertAuthServiceFactory = ({
identityTlsCertAuth, identityTlsCertAuth,
accessToken, accessToken,
identityAccessToken, identityAccessToken,
identityMembershipOrg identity
}; };
}; };
@@ -189,6 +187,9 @@ export const identityTlsCertAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -200,13 +201,14 @@ export const identityTlsCertAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -271,6 +273,9 @@ export const identityTlsCertAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) {
throw new NotFoundError({ throw new NotFoundError({
@@ -288,13 +293,14 @@ export const identityTlsCertAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -350,6 +356,9 @@ export const identityTlsCertAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -359,13 +368,14 @@ export const identityTlsCertAuthServiceFactory = ({
const identityAuth = await identityTlsCertAuthDAL.findOne({ identityId }); const identityAuth = await identityTlsCertAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
@@ -394,28 +404,32 @@ export const identityTlsCertAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have TLS Certificate auth" message: "The identity does not have TLS Certificate auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission( const { permission: rolePermission, memberships } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem); const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -1,4 +1,4 @@
import { TIdentityAccessTokens, TIdentityTlsCertAuths, TMemberships } from "@app/db/schemas"; import { TIdentities, TIdentityAccessTokens, TIdentityTlsCertAuths } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
export type TLoginTlsCertAuthDTO = { export type TLoginTlsCertAuthDTO = {
@@ -40,7 +40,7 @@ export type TIdentityTlsCertAuthServiceFactory = {
identityTlsCertAuth: TIdentityTlsCertAuths; identityTlsCertAuth: TIdentityTlsCertAuths;
accessToken: string; accessToken: string;
identityAccessToken: TIdentityAccessTokens; identityAccessToken: TIdentityAccessTokens;
identityMembershipOrg: TMemberships; identity: TIdentities;
}>; }>;
attachTlsCertAuth: (dto: TAttachTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>; attachTlsCertAuth: (dto: TAttachTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>;
updateTlsCertAuth: (dto: TUpdateTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>; updateTlsCertAuth: (dto: TUpdateTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>;
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, IdentityAuthMethod, TableName } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -32,11 +39,12 @@ import {
} from "./identity-token-auth-types"; } from "./identity-token-auth-types";
type TIdentityTokenAuthServiceFactoryDep = { type TIdentityTokenAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityTokenAuthDAL: Pick< identityTokenAuthDAL: Pick<
TIdentityTokenAuthDALFactory, TIdentityTokenAuthDALFactory,
"transaction" | "create" | "findOne" | "updateById" | "delete" "transaction" | "create" | "findOne" | "updateById" | "delete"
>; >;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">; membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick< identityAccessTokenDAL: Pick<
TIdentityAccessTokenDALFactory, TIdentityAccessTokenDALFactory,
"create" | "find" | "update" | "findById" | "findOne" | "updateById" | "delete" "create" | "find" | "update" | "findById" | "findOne" | "updateById" | "delete"
@@ -49,8 +57,8 @@ type TIdentityTokenAuthServiceFactoryDep = {
export type TIdentityTokenAuthServiceFactory = ReturnType<typeof identityTokenAuthServiceFactory>; export type TIdentityTokenAuthServiceFactory = ReturnType<typeof identityTokenAuthServiceFactory>;
export const identityTokenAuthServiceFactory = ({ export const identityTokenAuthServiceFactory = ({
identityDAL,
identityTokenAuthDAL, identityTokenAuthDAL,
// identityDAL,
membershipIdentityDAL, membershipIdentityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
@@ -79,6 +87,9 @@ export const identityTokenAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -90,13 +101,14 @@ export const identityTokenAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -155,6 +167,9 @@ export const identityTokenAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -172,13 +187,14 @@ export const identityTokenAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -223,6 +239,9 @@ export const identityTokenAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
@@ -232,13 +251,14 @@ export const identityTokenAuthServiceFactory = ({
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId }); const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityTokenAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...identityTokenAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -262,28 +282,33 @@ export const identityTokenAuthServiceFactory = ({
identityId identityId
}); });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have Token Auth" message: "The identity does not have Token Auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -341,22 +366,26 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth" message: "The identity does not have Token Auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -379,13 +408,13 @@ export const identityTokenAuthServiceFactory = ({
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId }); const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
const identity = await identityDAL.findById(identityTokenAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const identityAccessToken = await identityTokenAuthDAL.transaction(async (tx) => { const identityAccessToken = await identityTokenAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ { lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH, lastLoginTime: new Date() },
lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH,
lastLoginTime: new Date()
},
tx tx
); );
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
@@ -420,7 +449,7 @@ export const identityTokenAuthServiceFactory = ({
} }
); );
return { accessToken, identityTokenAuth, identityAccessToken, identityMembershipOrg }; return { accessToken, identityTokenAuth, identityAccessToken, identity };
}; };
const getTokenAuthTokens = async ({ const getTokenAuthTokens = async ({
@@ -449,13 +478,14 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth" message: "The identity does not have Token Auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const tokens = await identityAccessTokenDAL.find( const tokens = await identityAccessTokenDAL.find(
@@ -501,22 +531,24 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth" message: "The identity does not have Token Auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -580,13 +612,14 @@ export const identityTokenAuthServiceFactory = ({
throw new NotFoundError({ message: `Failed to find identity with ID ${identityAccessToken.identityId}` }); throw new NotFoundError({ message: `Failed to find identity with ID ${identityAccessToken.identityId}` });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityOrgMembership.scopeOrgId, orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const [revokedToken] = await identityAccessTokenDAL.update( const [revokedToken] = await identityAccessTokenDAL.update(
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, IdentityAuthMethod } from "@app/db/schemas"; import { AccessScope, IdentityAuthMethod, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -13,6 +13,7 @@ import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { import {
BadRequestError, BadRequestError,
ForbiddenRequestError,
NotFoundError, NotFoundError,
PermissionBoundaryError, PermissionBoundaryError,
RateLimitError, RateLimitError,
@@ -22,6 +23,7 @@ import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -42,6 +44,7 @@ import {
} from "./identity-ua-types"; } from "./identity-ua-types";
type TIdentityUaServiceFactoryDep = { type TIdentityUaServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityUaDAL: TIdentityUaDALFactory; identityUaDAL: TIdentityUaDALFactory;
identityUaClientSecretDAL: TIdentityUaClientSecretDALFactory; identityUaClientSecretDAL: TIdentityUaClientSecretDALFactory;
identityAccessTokenDAL: TIdentityAccessTokenDALFactory; identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
@@ -70,7 +73,8 @@ export const identityUaServiceFactory = ({
permissionService, permissionService,
licenseService, licenseService,
orgDAL, orgDAL,
keyStore keyStore,
identityDAL
}: TIdentityUaServiceFactoryDep) => { }: TIdentityUaServiceFactoryDep) => {
const login = async (clientId: string, clientSecret: string, ip: string) => { const login = async (clientId: string, clientSecret: string, ip: string) => {
const identityUa = await identityUaDAL.findOne({ clientId }); const identityUa = await identityUaDAL.findOne({ clientId });
@@ -100,16 +104,6 @@ export const identityUaServiceFactory = ({
}); });
} }
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityUa.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new UnauthorizedError({
message: "Invalid credentials"
});
}
const clientSecretPrefix = clientSecret.slice(0, 4); const clientSecretPrefix = clientSecret.slice(0, 4);
const clientSecretInfo = await identityUaClientSecretDAL.find({ const clientSecretInfo = await identityUaClientSecretDAL.find({
identityUAId: identityUa.id, identityUAId: identityUa.id,
@@ -227,10 +221,11 @@ export const identityUaServiceFactory = ({
accessTokenMaxTTL: 1000000000 accessTokenMaxTTL: 1000000000
}; };
const identity = await identityDAL.findById(identityUa.identityId);
const identityAccessToken = await identityUaDAL.transaction(async (tx) => { const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx); const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
await membershipIdentityDAL.updateById( await membershipIdentityDAL.update(
identityMembershipOrg.id, { scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ {
lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH, lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
lastLoginTime: new Date() lastLoginTime: new Date()
@@ -276,7 +271,7 @@ export const identityUaServiceFactory = ({
identityUa, identityUa,
validClientSecretInfo, validClientSecretInfo,
identityAccessToken, identityAccessToken,
identityMembershipOrg, identity,
...accessTokenTTLParams ...accessTokenTTLParams
}; };
}; };
@@ -315,18 +310,23 @@ export const identityUaServiceFactory = ({
message: "Failed to add universal auth to already configured identity" message: "Failed to add universal auth to already configured identity"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -423,6 +423,10 @@ export const identityUaServiceFactory = ({
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
if ( if (
(accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 && (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) (accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL)
@@ -430,13 +434,14 @@ export const identityUaServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId); const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -512,14 +517,18 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...uaIdentityAuth, orgId: identityMembershipOrg.scopeOrgId }; return { ...uaIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
}; };
@@ -545,22 +554,27 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -611,23 +625,28 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -692,23 +711,28 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
const { permission } = await permissionService.getOrgPermission( if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -761,6 +785,9 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const identityUa = await identityUaDAL.findOne({ identityId }); const identityUa = await identityUaDAL.findOne({ identityId });
if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
@@ -768,22 +795,24 @@ export const identityUaServiceFactory = ({
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id }); const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem, shouldUseNewPrivilegeSystem,
@@ -828,6 +857,9 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const identityUa = await identityUaDAL.findOne({ identityId }); const identityUa = await identityUaDAL.findOne({ identityId });
if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
@@ -835,22 +867,24 @@ export const identityUaServiceFactory = ({
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id }); const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission( const { permission: rolePermission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
identityMembershipOrg.identity.id, actorId: identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId); const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
@@ -900,14 +934,18 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth" message: "The identity does not have universal auth"
}); });
} }
if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" });
}
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityMembershipOrg.scopeOrgId, orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const deleted = await keyStore.deleteItems({ const deleted = await keyStore.deleteItems({
@@ -163,7 +163,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
.select( .select(
selectAllTableCols(TableName.Membership), selectAllTableCols(TableName.Membership),
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity) db.ref("hasDeleteProtection").withSchema(TableName.Identity),
db.ref("orgId").withSchema(TableName.Identity)
) )
.where(filter) .where(filter)
.as("paginatedIdentity"); .as("paginatedIdentity");
@@ -257,6 +258,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"), db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
db.ref("scopeOrgId").withSchema("paginatedIdentity").as("orgId"), db.ref("scopeOrgId").withSchema("paginatedIdentity").as("orgId"),
db.ref("lastLoginAuthMethod").withSchema("paginatedIdentity"), db.ref("lastLoginAuthMethod").withSchema("paginatedIdentity"),
db.ref("orgId").withSchema("paginatedIdentity").as("identityOrgId"),
db.ref("lastLoginTime").withSchema("paginatedIdentity"), db.ref("lastLoginTime").withSchema("paginatedIdentity"),
db.ref("createdAt").withSchema("paginatedIdentity"), db.ref("createdAt").withSchema("paginatedIdentity"),
db.ref("updatedAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"),
@@ -309,6 +311,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
roleId, roleId,
id, id,
orgId, orgId,
identityOrgId,
uaId, uaId,
alicloudId, alicloudId,
awsId, awsId,
@@ -348,6 +351,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
id: identityId as string, id: identityId as string,
name: identityName, name: identityName,
hasDeleteProtection, hasDeleteProtection,
orgId: identityOrgId,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
alicloudId, alicloudId,
@@ -515,6 +519,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"), db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity), db.ref("hasDeleteProtection").withSchema(TableName.Identity),
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
@@ -566,6 +571,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
crPermission, crPermission,
crName, crName,
identityId, identityId,
identityOrgId,
identityName, identityName,
hasDeleteProtection, hasDeleteProtection,
role, role,
@@ -611,6 +617,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
id: identityId as string, id: identityId as string,
name: identityName, name: identityName,
hasDeleteProtection, hasDeleteProtection,
orgId: identityOrgId,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
alicloudId, alicloudId,
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, OrgMembershipRole, TableName, TRoles } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole, TableName, TRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
@@ -12,6 +12,7 @@ import { TKeyStoreFactory } from "@app/keystore/keystore";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal"; import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal"; import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TOrgDALFactory } from "../org/org-dal"; import { TOrgDALFactory } from "../org/org-dal";
@@ -40,6 +41,7 @@ type TIdentityServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">; licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">; keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">;
}; };
export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>; export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
@@ -54,7 +56,8 @@ export const identityServiceFactory = ({
keyStore, keyStore,
orgDAL, orgDAL,
membershipIdentityDAL, membershipIdentityDAL,
membershipRoleDAL membershipRoleDAL,
additionalPrivilegeDAL
}: TIdentityServiceFactoryDep) => { }: TIdentityServiceFactoryDep) => {
const createIdentity = async ({ const createIdentity = async ({
name, name,
@@ -67,7 +70,14 @@ export const identityServiceFactory = ({
actorOrgId, actorOrgId,
metadata metadata
}: TCreateIdentityDTO) => { }: TCreateIdentityDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], orgId); const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], orgId);
@@ -104,7 +114,7 @@ export const identityServiceFactory = ({
} }
const identity = await identityDAL.transaction(async (tx) => { const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name, hasDeleteProtection }, tx); const newIdentity = await identityDAL.create({ name, hasDeleteProtection, orgId }, tx);
const membership = await membershipIdentityDAL.create( const membership = await membershipIdentityDAL.create(
{ {
scope: AccessScope.Organization, scope: AccessScope.Organization,
@@ -172,13 +182,14 @@ export const identityServiceFactory = ({
}); });
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` }); if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityOrgMembership.scopeOrgId, orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
let customRole: TRoles | undefined; let customRole: TRoles | undefined;
@@ -208,11 +219,12 @@ export const identityServiceFactory = ({
if (isCustomRole) customRole = rolePermissionDetails?.role; if (isCustomRole) customRole = rolePermissionDetails?.role;
} }
const identityDetails = await identityDAL.findById(id);
const identity = await identityDAL.transaction(async (tx) => { const identity = await identityDAL.transaction(async (tx) => {
const newIdentity = const newIdentity =
name || hasDeleteProtection identityDetails.orgId === actorOrgId && (name || hasDeleteProtection)
? await identityDAL.updateById(id, { name, hasDeleteProtection }, tx) ? await identityDAL.updateById(id, { name, hasDeleteProtection }, tx)
: await identityDAL.findById(id, tx); : identityDetails;
if (role) { if (role) {
await membershipRoleDAL.delete({ membershipId: identityOrgMembership.id }, tx); await membershipRoleDAL.delete({ membershipId: identityOrgMembership.id }, tx);
@@ -264,16 +276,16 @@ export const identityServiceFactory = ({
const identity = doc[0]; const identity = doc[0];
if (!identity) throw new NotFoundError({ message: `Failed to find identity with id ${id}` }); if (!identity) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identity.orgId, orgId: identity.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
// TODO(namespace): check this in identity service
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`); const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`);
const activeLockoutAuthMethods = new Set<string>(); const activeLockoutAuthMethods = new Set<string>();
@@ -314,23 +326,56 @@ export const identityServiceFactory = ({
}); });
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` }); if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityOrgMembership.scopeOrgId, orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
if (identityOrgMembership.identity.hasDeleteProtection) if (identityOrgMembership.identity.hasDeleteProtection)
throw new BadRequestError({ message: "Identity has delete protection" }); throw new BadRequestError({ message: "Identity has delete protection" });
if (identityOrgMembership.identity.identityOrgId === actorOrgId) {
const deletedIdentity = await identityDAL.deleteById(id); const deletedIdentity = await identityDAL.deleteById(id);
await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId); await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId);
return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId };
}
await membershipIdentityDAL.transaction(async (tx) => {
await identityMetadataDAL.delete(
{
identityId: id,
orgId: actorOrgId
},
tx
);
const identityProjectMembership = await membershipIdentityDAL.find(
{
actorIdentityId: id,
scope: AccessScope.Project,
scopeOrgId: actorOrgId
},
{ tx }
);
await additionalPrivilegeDAL.delete(
{
actorIdentityId: id,
$in: {
projectId: identityProjectMembership.map((el) => el.scopeProjectId)
}
},
tx
);
const doc = await membershipIdentityDAL.delete({ actorIdentityId: id, scopeOrgId: actorOrgId }, tx);
return doc;
});
const deletedIdentity = await identityDAL.findById(id);
return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId }; return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId };
}; };
@@ -346,7 +391,14 @@ export const identityServiceFactory = ({
orderDirection, orderDirection,
search search
}: TListOrgIdentitiesByOrgIdDTO) => { }: TListOrgIdentitiesByOrgIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityOrgMembershipDAL.find({ const identityMemberships = await identityOrgMembershipDAL.find({
@@ -379,7 +431,14 @@ export const identityServiceFactory = ({
orderDirection, orderDirection,
searchFilter = {} searchFilter = {}
}: TSearchOrgIdentitiesByOrgIdDTO) => { }: TSearchOrgIdentitiesByOrgIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { totalCount, docs } = await identityOrgMembershipDAL.searchIdentities({ const { totalCount, docs } = await identityOrgMembershipDAL.searchIdentities({
@@ -408,16 +467,17 @@ export const identityServiceFactory = ({
}); });
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` }); if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
identityOrgMembership.scopeOrgId, orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId); const identityMemberships = await identityProjectDAL.findByIdentityId(identityId, actorOrgId);
return identityMemberships; return identityMemberships;
}; };
@@ -112,7 +112,7 @@ export const integrationAuthServiceFactory = ({
}; };
const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => { const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => {
const authorizations = await integrationAuthDAL.getByOrg(actorOrgId as string); const authorizations = await integrationAuthDAL.getByOrg(actorOrgId);
const filteredAuthorizations = await Promise.all( const filteredAuthorizations = await Promise.all(
authorizations.map(async (auth) => { authorizations.map(async (auth) => {
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole } from "@app/db/schemas";
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
constructPermissionErrorMessage, constructPermissionErrorMessage,
@@ -45,13 +45,14 @@ export const newOrgMembershipGroupFactory = ({
}; };
const onUpdateMembershipGroupGuard: TMembershipGroupScopeFactory["onUpdateMembershipGroupGuard"] = async (dto) => { const onUpdateMembershipGroupGuard: TMembershipGroupScopeFactory["onUpdateMembershipGroupGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
const permissionRoles = await permissionService.getOrgPermissionByRoles( const permissionRoles = await permissionService.getOrgPermissionByRoles(
dto.data.roles.map((el) => el.role), dto.data.roles.map((el) => el.role),
@@ -89,26 +90,28 @@ export const newOrgMembershipGroupFactory = ({
}; };
const onListMembershipGroupGuard: TMembershipGroupScopeFactory["onListMembershipGroupGuard"] = async (dto) => { const onListMembershipGroupGuard: TMembershipGroupScopeFactory["onListMembershipGroupGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
}; };
const onGetMembershipGroupByGroupIdGuard: TMembershipGroupScopeFactory["onGetMembershipGroupByGroupIdGuard"] = async ( const onGetMembershipGroupByGroupIdGuard: TMembershipGroupScopeFactory["onGetMembershipGroupByGroupIdGuard"] = async (
dto dto
) => { ) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
}; };
@@ -91,6 +91,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
.select( .select(
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("id").withSchema(TableName.Identity).as("identityId"), db.ref("id").withSchema(TableName.Identity).as("identityId"),
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"), db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"),
db.ref("slug").withSchema(TableName.Role).as("roleSlug"), db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
@@ -132,6 +133,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
parentMapper: (el) => { parentMapper: (el) => {
const { const {
identityId: actorIdentityId, identityId: actorIdentityId,
identityOrgId,
identityHasDeleteProtection, identityHasDeleteProtection,
identityName, identityName,
uaId, uaId,
@@ -153,6 +155,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
name: identityName, name: identityName,
id: actorIdentityId, id: actorIdentityId,
hasDeleteProtection: identityHasDeleteProtection, hasDeleteProtection: identityHasDeleteProtection,
identityOrgId,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
awsId, awsId,
@@ -353,5 +356,34 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
} }
}; };
return { ...orm, findIdentities, getIdentityById }; // this right now only support sub organization
const listAvailableIdentities = async (orgId: string, rootOrgId: string) => {
try {
const usersConnectedToOrg = db
.replicaNode()(TableName.Membership)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.select("actorIdentityId");
const docs = await db
.replicaNode()(TableName.Membership)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where(`${TableName.Membership}.scopeOrgId`, rootOrgId)
.whereNotIn(`${TableName.Membership}.actorIdentityId`, usersConnectedToOrg)
.select(
db.ref("id").withSchema(TableName.Identity),
db.ref("name").withSchema(TableName.Identity),
db.ref("hasDeleteProtection").withSchema(TableName.Identity)
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "ListAvailableIdentities" });
}
};
return { ...orm, findIdentities, getIdentityById, listAvailableIdentities };
}; };
@@ -6,6 +6,7 @@ import { ms } from "@app/lib/ms";
import { SearchResourceOperators } from "@app/lib/search-resource/search"; import { SearchResourceOperators } from "@app/lib/search-resource/search";
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal"; import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal"; import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
import { TOrgDALFactory } from "../org/org-dal"; import { TOrgDALFactory } from "../org/org-dal";
import { TRoleDALFactory } from "../role/role-dal"; import { TRoleDALFactory } from "../role/role-dal";
@@ -31,6 +32,7 @@ type TMembershipIdentityServiceFactoryDep = {
>; >;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">; additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">;
identityDAL: Pick<TIdentityDALFactory, "findById">;
}; };
export type TMembershipIdentityServiceFactory = ReturnType<typeof membershipIdentityServiceFactory>; export type TMembershipIdentityServiceFactory = ReturnType<typeof membershipIdentityServiceFactory>;
@@ -41,12 +43,14 @@ export const membershipIdentityServiceFactory = ({
membershipRoleDAL, membershipRoleDAL,
permissionService, permissionService,
orgDAL, orgDAL,
additionalPrivilegeDAL additionalPrivilegeDAL,
identityDAL
}: TMembershipIdentityServiceFactoryDep) => { }: TMembershipIdentityServiceFactoryDep) => {
const scopeFactory = { const scopeFactory = {
[AccessScope.Organization]: newOrgMembershipIdentityFactory({ [AccessScope.Organization]: newOrgMembershipIdentityFactory({
orgDAL, orgDAL,
permissionService permissionService,
identityDAL
}), }),
[AccessScope.Project]: newProjectMembershipIdentityFactory({ [AccessScope.Project]: newProjectMembershipIdentityFactory({
membershipIdentityDAL, membershipIdentityDAL,
@@ -305,7 +309,7 @@ export const membershipIdentityServiceFactory = ({
[SearchResourceOperators.$contains]: dto.data.identityName [SearchResourceOperators.$contains]: dto.data.identityName
} }
: undefined, : undefined,
role: dto.data.roles.length role: dto.data?.roles?.length
? { ? {
[SearchResourceOperators.$in]: dto.data.roles [SearchResourceOperators.$in]: dto.data.roles
} }
@@ -329,11 +333,29 @@ export const membershipIdentityServiceFactory = ({
return membership; return membership;
}; };
const listAvailableIdentities = async (dto: TListMembershipIdentityDTO) => {
const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onListMembershipIdentityGuard(dto);
const organizationDetails = await orgDAL.findById(dto.scopeData.orgId);
if (!organizationDetails.rootOrgId) return { identities: [] };
const identities = await membershipIdentityDAL.listAvailableIdentities(
organizationDetails.id,
organizationDetails.rootOrgId
);
return { identities };
};
return { return {
createMembership, createMembership,
updateMembership, updateMembership,
deleteMembership, deleteMembership,
listMemberships, listMemberships,
getMembershipByIdentityId getMembershipByIdentityId,
listAvailableIdentities
}; };
}; };
@@ -54,14 +54,11 @@ export type TUpdateMembershipIdentityDTO = {
export type TListMembershipIdentityDTO = { export type TListMembershipIdentityDTO = {
permission: OrgServiceActor; permission: OrgServiceActor;
scopeData: AccessScopeData; scopeData: AccessScopeData;
selector: {
identityId: string;
};
data: { data: {
limit?: number; limit?: number;
offset?: number; offset?: number;
identityName?: string; identityName?: string;
roles: string[]; roles?: string[];
}; };
}; };
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole } from "@app/db/schemas";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { import {
constructPermissionErrorMessage, constructPermissionErrorMessage,
@@ -8,6 +8,7 @@ import {
} from "@app/ee/services/permission/permission-fns"; } from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { isCustomOrgRole } from "@app/services/org/org-role-fns"; import { isCustomOrgRole } from "@app/services/org/org-role-fns";
@@ -16,11 +17,13 @@ import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
type TOrgMembershipIdentityScopeFactoryDep = { type TOrgMembershipIdentityScopeFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRoles">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRoles">;
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
identityDAL: Pick<TIdentityDALFactory, "findById">;
}; };
export const newOrgMembershipIdentityFactory = ({ export const newOrgMembershipIdentityFactory = ({
permissionService, permissionService,
orgDAL orgDAL,
identityDAL
}: TOrgMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => { }: TOrgMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => {
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => { const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) { if (dto.scope === AccessScope.Organization) {
@@ -38,23 +41,66 @@ export const newOrgMembershipIdentityFactory = ({
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role); const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] = const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] = async (
async () => { dto
throw new BadRequestError({ ) => {
message: "Organization membership cannot be created for organization scoped identity" const { permission } = await permissionService.getOrgPermission({
actor: dto.permission.type,
actorId: dto.permission.id,
orgId: dto.permission.orgId,
actorAuthMethod: dto.permission.authMethod,
actorOrgId: dto.permission.orgId,
scope: OrganizationActionScope.ChildOrganization
}); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const identityDetails = await identityDAL.findById(dto.data.identityId);
if (identityDetails.orgId !== dto.permission.rootOrgId) {
throw new BadRequestError({ message: "Only identities from parent organization can be invited" });
}
const permissionRoles = await permissionService.getOrgPermissionByRoles(
dto.data.roles.map((el) => el.role),
dto.permission.orgId
);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
for (const permissionRole of permissionRoles) {
if (permissionRole?.role?.name !== OrgMembershipRole.NoAccess) {
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity,
permission,
permissionRole.permission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update identity org membership",
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
}
}; };
const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] = async ( const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] = async (
dto dto
) => { ) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const permissionRoles = await permissionService.getOrgPermissionByRoles( const permissionRoles = await permissionService.getOrgPermissionByRoles(
dto.data.roles.map((el) => el.role), dto.data.roles.map((el) => el.role),
@@ -85,35 +131,54 @@ export const newOrgMembershipIdentityFactory = ({
} }
}; };
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] = const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] = async (
async () => { dto
throw new BadRequestError({ ) => {
message: "Organization membership cannot be deleted for organization scoped identity" const { permission } = await permissionService.getOrgPermission({
actor: dto.permission.type,
actorId: dto.permission.id,
orgId: dto.permission.orgId,
actorAuthMethod: dto.permission.authMethod,
actorOrgId: dto.permission.orgId,
scope: OrganizationActionScope.ChildOrganization
}); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
const identityDetails = await identityDAL.findById(dto.selector.identityId);
if (identityDetails.orgId !== dto.permission.rootOrgId) {
throw new BadRequestError({ message: "Only identities from parent organization can do this operation" });
}
if (identityDetails.orgId === dto.permission.orgId) {
throw new BadRequestError({ message: "Identity cannot exist as orphan" });
}
}; };
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async ( const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async (
dto dto
) => { ) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
}; };
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] = const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
async (dto) => { async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
}; };
@@ -291,5 +291,37 @@ export const membershipUserDALFactory = (db: TDbClient) => {
} }
}; };
return { ...orm, findUsers, getUserById }; // this right now only support sub organization
const listAvailableUsers = async (orgId: string, rootOrgId: string) => {
try {
const usersConnectedToOrg = db
.replicaNode()(TableName.Membership)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.select("actorUserId");
const docs = await db
.replicaNode()(TableName.Membership)
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.where(`${TableName.Users}.isGhost`, false)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.where(`${TableName.Membership}.scopeOrgId`, rootOrgId)
.whereNotIn(`${TableName.Membership}.actorUserId`, usersConnectedToOrg)
.select(
db.ref("id").withSchema(TableName.Users),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users)
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "ListAvailableUsers" });
}
};
return { ...orm, findUsers, getUserById, listAvailableUsers };
}; };
@@ -40,7 +40,7 @@ import { newProjectMembershipUserFactory } from "./project/project-membership-us
type TMembershipUserServiceFactoryDep = { type TMembershipUserServiceFactoryDep = {
membershipUserDAL: TMembershipUserDALFactory; membershipUserDAL: TMembershipUserDALFactory;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">; membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
orgDAL: Pick<TOrgDALFactory, "findById" | "transaction">; orgDAL: Pick<TOrgDALFactory, "findById" | "transaction" | "find">;
roleDAL: Pick<TRoleDALFactory, "find">; roleDAL: Pick<TRoleDALFactory, "find">;
userDAL: TUserDALFactory; userDAL: TUserDALFactory;
permissionService: Pick< permissionService: Pick<
@@ -83,7 +83,8 @@ export const membershipUserServiceFactory = ({
orgDAL, orgDAL,
tokenService, tokenService,
userDAL, userDAL,
userGroupMembershipDAL userGroupMembershipDAL,
membershipUserDAL
}), }),
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}), [AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
[AccessScope.Project]: newProjectMembershipUserFactory({ [AccessScope.Project]: newProjectMembershipUserFactory({
@@ -404,7 +405,7 @@ export const membershipUserServiceFactory = ({
const membershipDoc = await membershipUserDAL.transaction(async (tx) => { const membershipDoc = await membershipUserDAL.transaction(async (tx) => {
if (dto.scopeData.scope === AccessScope.Organization) { if (dto.scopeData.scope === AccessScope.Organization) {
const [doc] = await deleteOrgMembershipsFn({ const [doc] = await deleteOrgMembershipsFn({
orgMembershipIds: [], orgMembershipIds: [existingMembership.id],
orgId: dto.permission.orgId, orgId: dto.permission.orgId,
orgDAL, orgDAL,
projectKeyDAL, projectKeyDAL,
@@ -471,11 +472,26 @@ export const membershipUserServiceFactory = ({
return membership; return membership;
}; };
// Should only be used for sub organization as of now
const listAvailableUsers = async (dto: TListMembershipUserDTO) => {
const { scopeData } = dto;
const factory = scopeFactory[scopeData.scope];
await factory.onListMembershipUserGuard(dto);
const organizationDetails = await orgDAL.findById(dto.scopeData.orgId);
if (!organizationDetails.rootOrgId) return { users: [] };
const users = await membershipUserDAL.listAvailableUsers(organizationDetails.id, organizationDetails.rootOrgId);
return { users };
};
return { return {
createMembership, createMembership,
updateMembership, updateMembership,
deleteMembership, deleteMembership,
listMemberships, listMemberships,
getMembershipByUserId getMembershipByUserId,
listAvailableUsers
}; };
}; };
@@ -93,3 +93,8 @@ export type TGetMembershipUserByUserIdDTO = {
userId: string; userId: string;
}; };
}; };
export type TListAvailableUsersDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
};
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AccessScope } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope } from "@app/db/schemas";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
@@ -15,6 +15,7 @@ import { isCustomOrgRole } from "@app/services/org/org-role-fns";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { TMembershipUserDALFactory } from "../membership-user-dal";
import { TMembershipUserScopeFactory } from "../membership-user-types"; import { TMembershipUserScopeFactory } from "../membership-user-types";
type TOrgMembershipUserScopeFactoryDep = { type TOrgMembershipUserScopeFactoryDep = {
@@ -25,6 +26,7 @@ type TOrgMembershipUserScopeFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findById">; orgDAL: Pick<TOrgDALFactory, "findById">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">; userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "find">;
}; };
export const newOrgMembershipUserFactory = ({ export const newOrgMembershipUserFactory = ({
@@ -33,7 +35,8 @@ export const newOrgMembershipUserFactory = ({
userDAL, userDAL,
orgDAL, orgDAL,
smtpService, smtpService,
licenseService licenseService,
membershipUserDAL
}: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => { }: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => { const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) { if (dto.scope === AccessScope.Organization) {
@@ -51,14 +54,18 @@ export const newOrgMembershipUserFactory = ({
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role); const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (dto) => { const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (
const { permission } = await permissionService.getOrgPermission( dto,
dto.permission.type, newMembers
dto.permission.id, ) => {
dto.permission.orgId, const { permission } = await permissionService.getOrgPermission({
dto.permission.authMethod, actor: dto.permission.type,
dto.permission.orgId actorId: dto.permission.id,
); orgId: dto.permission.orgId,
actorAuthMethod: dto.permission.authMethod,
actorOrgId: dto.permission.orgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
const plan = await licenseService.getPlan(dto.permission.orgId); const plan = await licenseService.getPlan(dto.permission.orgId);
@@ -77,6 +84,25 @@ export const newOrgMembershipUserFactory = ({
message: "Failed to invite user due to org-level auth enforced for organization" message: "Failed to invite user due to org-level auth enforced for organization"
}); });
} }
if (org.rootOrgId) {
const rootOrgMembership = await membershipUserDAL.find({
scope: AccessScope.Organization,
$in: {
actorUserId: newMembers.map((el) => el.id)
},
scopeOrgId: org.rootOrgId
});
if (rootOrgMembership.length !== newMembers.length) {
const emails = newMembers
.filter((user) => !rootOrgMembership.find((i) => i.actorUserId === user.id))
.map((el) => el.email)
.join(",");
throw new BadRequestError({
message: `Users with email ${emails} doesn't have membership in root organization`
});
}
}
}; };
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async ( const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
@@ -95,7 +121,18 @@ export const newOrgMembershipUserFactory = ({
const signUpTokens: { email: string; link: string }[] = []; const signUpTokens: { email: string; link: string }[] = [];
const orgDetails = await orgDAL.findById(dto.permission.orgId); const orgDetails = await orgDAL.findById(dto.permission.orgId);
if (orgDetails.rootOrgId) {
const emails = newUsers.map((el) => el.email).filter(Boolean);
await smtpService.sendMail({
template: SmtpTemplates.SubOrgInvite,
subjectLine: "Infisical sub-organization invitation",
recipients: emails as string[],
substitutions: {
subOrganizationName: orgDetails.slug,
callback_url: `${appCfg.SITE_URL}/organization/projects?subOrganization=${orgDetails.slug}`
}
});
} else {
await Promise.allSettled( await Promise.allSettled(
newUsers.map(async (el) => { newUsers.map(async (el) => {
const token = await tokenService.createTokenForUser({ const token = await tokenService.createTokenForUser({
@@ -129,53 +166,58 @@ export const newOrgMembershipUserFactory = ({
} }
}) })
); );
}
return { signUpTokens }; return { signUpTokens };
}; };
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => { const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
}; };
const onDeleteMembershipUserGuard: TMembershipUserScopeFactory["onDeleteMembershipUserGuard"] = async (dto) => { const onDeleteMembershipUserGuard: TMembershipUserScopeFactory["onDeleteMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
}; };
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => { const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
}; };
const onGetMembershipUserByUserIdGuard: TMembershipUserScopeFactory["onGetMembershipUserByUserIdGuard"] = async ( const onGetMembershipUserByUserIdGuard: TMembershipUserScopeFactory["onGetMembershipUserByUserIdGuard"] = async (
dto dto
) => { ) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.permission.type, actor: dto.permission.type,
dto.permission.id, actorId: dto.permission.id,
dto.permission.orgId, orgId: dto.permission.orgId,
dto.permission.authMethod, actorAuthMethod: dto.permission.authMethod,
dto.permission.orgId actorOrgId: dto.permission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
}; };
@@ -9,6 +9,7 @@ import {
import { CronJob } from "cron"; import { CronJob } from "cron";
import { FastifyReply, FastifyRequest } from "fastify"; import { FastifyReply, FastifyRequest } from "fastify";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
@@ -208,13 +209,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
microsoftTeamsIntegration.orgId, orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -282,13 +284,14 @@ export const microsoftTeamsServiceFactory = ({
description, description,
redirectUri redirectUri
}: TCreateMicrosoftTeamsIntegrationDTO) => { }: TCreateMicrosoftTeamsIntegrationDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -393,13 +396,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
}; };
const getClientId = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGetClientIdDTO) => { const getClientId = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGetClientIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -427,13 +431,14 @@ export const microsoftTeamsServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod actorAuthMethod
}: TGetMicrosoftTeamsIntegrationByOrgDTO) => { }: TGetMicrosoftTeamsIntegrationByOrgDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -463,13 +468,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
microsoftTeamsIntegration.orgId, orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -495,13 +501,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
microsoftTeamsIntegration.orgId, orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -549,13 +556,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
microsoftTeamsIntegration.orgId, orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
@@ -577,13 +585,14 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
microsoftTeamsIntegration.orgId, orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);

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