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
@@ -1,6 +1,6 @@
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 { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
@@ -215,13 +215,14 @@ export const appConnectionServiceFactory = ({
)
);
} else {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
@@ -268,13 +269,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId })
);
} else {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
@@ -318,13 +320,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
);
} else {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
@@ -342,13 +345,14 @@ export const appConnectionServiceFactory = ({
{ method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO,
actor: OrgServiceActor
) => {
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { permission: orgPermission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (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."
);
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission: orgPermission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (appConnection.projectId) {
const { permission } = await permissionService.getProjectPermission({
@@ -635,13 +640,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId })
);
} else {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Delete,
@@ -704,13 +710,14 @@ export const appConnectionServiceFactory = ({
subject(ProjectPermissionSub.AppConnections, { connectionId })
);
} else {
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission: orgPermission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionAppConnectionActions.Connect,
@@ -747,13 +754,14 @@ export const appConnectionServiceFactory = ({
};
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId?: string) => {
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { permission: orgPermission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
let availableProjectConnections: TAppConnections[] = [];
@@ -805,13 +813,14 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
appConnection.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: appConnection.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAppConnectionActions.Read,
@@ -3,10 +3,11 @@ import { Knex } from "knex";
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal";
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
@@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "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 { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@@ -194,7 +196,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
};
// 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({
id: token.tokenVersionId,
userId: token.userId
@@ -207,22 +209,56 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TA
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
let orgId = "";
let rootOrgId = "";
let parentOrgId = "";
if (token.organizationId) {
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,
scopeOrgId: token.organizationId,
scope: AccessScope.Organization
});
if (subOrganizationSelector) {
const subOrganization = await orgDAL.findOne({
rootOrgId: token.organizationId,
slug: subOrganizationSelector
});
if (!subOrganization)
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
if (!orgMembership) {
throw new ForbiddenRequestError({ message: "User not member of organization" });
}
if (!orgMembership.isActive) {
throw new ForbiddenRequestError({ message: "User organization membership is inactive" });
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({
actorUserId: user.id,
scopeOrgId: token.organizationId,
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 = 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 {
@@ -258,7 +258,13 @@ export const authSignupServiceFactory = ({
let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME;
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) {
tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration;
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -26,13 +27,14 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
roleDAL
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actor: actor.type,
actorId: actor.id,
orgId: actor.orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
scope: OrganizationActionScope.ParentOrganization
});
// TODO: will need to change if we add support for ldap, oidc, etc.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
@@ -48,13 +50,14 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
dto: TSyncExternalGroupOrgMembershipRoleMappingsDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getOrgPermission({
actor: actor.type,
actorId: actor.id,
orgId: actor.orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
scope: OrganizationActionScope.ParentOrganization
});
// TODO: will need to change if we add support for ldap, oidc, etc.
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 {
AuditLogInfo,
EventType,
@@ -89,13 +89,14 @@ export const externalMigrationServiceFactory = ({
throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." });
}
const { hasRole } = await permissionService.getOrgPermission(
actor,
const { hasRole } = await permissionService.getOrgPermission({
actorId,
actor,
orgId: actorOrgId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" });
}
@@ -136,13 +137,14 @@ export const externalMigrationServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TImportVaultDataDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
actor,
const { hasRole } = await permissionService.getOrgPermission({
actorId,
actor,
orgId: actorOrgId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" });
@@ -192,13 +194,14 @@ export const externalMigrationServiceFactory = ({
actorAuthMethod,
provider
}: THasCustomVaultMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
actor,
const { hasRole } = await permissionService.getOrgPermission({
actorId,
actor,
orgId: actorOrgId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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 { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can configure vault external migration" });
@@ -298,13 +302,14 @@ export const externalMigrationServiceFactory = ({
connectionId,
actor
}: TUpdateVaultExternalMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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 { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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 { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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 { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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 { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault mounts" });
@@ -472,13 +481,14 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault secret paths" });
@@ -531,13 +541,14 @@ export const externalMigrationServiceFactory = ({
vaultSecretPath: string;
auditLogInfo: AuditLogInfo;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import vault secrets" });
@@ -617,13 +628,14 @@ export const externalMigrationServiceFactory = ({
};
const deleteVaultExternalMigration = async ({ id, actor }: TDeleteVaultExternalMigrationDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can delete vault external migration configs" });
@@ -653,13 +665,14 @@ export const externalMigrationServiceFactory = ({
namespace: string;
authType?: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can view vault auth mounts" });
@@ -704,13 +717,14 @@ export const externalMigrationServiceFactory = ({
namespace: string;
mountPath: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
const { hasRole } = await permissionService.getOrgPermission({
actorId: actor.id,
actor: actor.type,
orgId: actor.orgId,
actorOrgId: actor.orgId,
actorAuthMethod: actor.authMethod,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
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`)
.select(selectAllTableCols(TableName.IdentityAccessToken))
.select(db.ref("name").withSchema(TableName.Identity))
.select(db.ref("orgId").withSchema(TableName.Identity).as("identityScopeOrgId"))
.first();
return doc;
@@ -8,6 +8,7 @@ import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-to
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types";
@@ -19,6 +20,7 @@ type TIdentityAccessTokenServiceFactoryDep = {
"updateIdentityAccessTokenStatus" | "getIdentityTokenDetailsInCache"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">;
orgDAL: Pick<TOrgDALFactory, "findOne">;
};
export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>;
@@ -27,7 +29,8 @@ export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL,
accessTokenQueue,
identityDAL,
membershipIdentityDAL
membershipIdentityDAL,
orgDAL
}: TIdentityAccessTokenServiceFactoryDep) => {
const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const {
@@ -181,7 +184,11 @@ export const identityAccessTokenServiceFactory = ({
return { revokedToken };
};
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
const fnValidateIdentityAccessToken = async (
token: TIdentityAccessTokenJwtPayload,
subOrganizationSelector?: string,
ipAddress?: string
) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
isAccessTokenRevoked: false
@@ -202,13 +209,40 @@ export const identityAccessTokenServiceFactory = ({
trustedIps: trustedIps as TIp[]
});
}
const identityOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId
});
let orgId = "";
let parentOrgId = "";
const identityOrgDetails = await orgDAL.findOne({ id: identityAccessToken.identityScopeOrgId });
const rootOrgId = identityOrgDetails.rootOrgId || identityOrgDetails.id;
if (!identityOrgMembership) {
throw new BadRequestError({ message: "Identity does not belong to any organization" });
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({
scope: AccessScope.Organization,
actorIdentityId: identityAccessToken.identityId,
scopeOrgId: subOrganization.id
});
if (!identityOrgMembership) {
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;
@@ -219,7 +253,7 @@ export const identityAccessTokenServiceFactory = ({
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
await accessTokenQueue.updateIdentityAccessTokenStatus(identityAccessToken.id, Number(accessTokenNumUses) + 1);
return { ...identityAccessToken, orgId: identityOrgMembership.scopeOrgId };
return { ...identityAccessToken, orgId, rootOrgId, parentOrgId };
};
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability";
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -13,11 +13,18 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
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 { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -34,12 +41,13 @@ import {
} from "./identity-alicloud-auth-types";
type TIdentityAliCloudAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityAliCloudAuthDAL: Pick<
TIdentityAliCloudAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -48,6 +56,7 @@ type TIdentityAliCloudAuthServiceFactoryDep = {
export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>;
export const identityAliCloudAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL,
identityAliCloudAuthDAL,
membershipIdentityDAL,
@@ -63,12 +72,8 @@ export const identityAliCloudAuthServiceFactory = ({
});
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityAliCloudAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const identity = await identityDAL.findById(identityAliCloudAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const requestUrl = new URL("https://sts.aliyuncs.com");
@@ -93,8 +98,8 @@ export const identityAliCloudAuthServiceFactory = ({
// Generate the token
const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{
lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH,
lastLoginTime: new Date()
@@ -135,7 +140,7 @@ export const identityAliCloudAuthServiceFactory = ({
identityAliCloudAuth,
accessToken,
identityAccessToken,
identityMembershipOrg
identity
};
};
@@ -162,6 +167,9 @@ export const identityAliCloudAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -173,13 +181,14 @@ export const identityAliCloudAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -238,6 +247,9 @@ export const identityAliCloudAuthServiceFactory = ({
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)) {
throw new NotFoundError({
@@ -255,13 +267,14 @@ export const identityAliCloudAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -304,6 +317,9 @@ export const identityAliCloudAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -313,13 +329,14 @@ export const identityAliCloudAuthServiceFactory = ({
const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
};
@@ -339,27 +356,32 @@ export const identityAliCloudAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have Alibaba Cloud auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import axios from "axios";
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -13,10 +13,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
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 { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -35,9 +42,10 @@ import {
} from "./identity-aws-auth-types";
type TIdentityAwsAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "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">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -80,6 +88,7 @@ function isValidAwsRegion(region: string | null): boolean {
}
export const identityAwsAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL,
identityAwsAuthDAL,
membershipIdentityDAL,
@@ -93,11 +102,8 @@ export const identityAwsAuthServiceFactory = ({
throw new NotFoundError({ message: "AWS auth method not found for identity, did you configure AWS auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityAwsAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const identity = await identityDAL.findById(identityAwsAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString();
@@ -159,8 +165,8 @@ export const identityAwsAuthServiceFactory = ({
}
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{
lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH,
lastLoginTime: new Date()
@@ -212,7 +218,7 @@ export const identityAwsAuthServiceFactory = ({
}
);
return { accessToken, identityAwsAuth, identityAccessToken, identityMembershipOrg };
return { accessToken, identityAwsAuth, identityAccessToken, identity };
};
const attachAwsAuth = async ({
@@ -240,6 +246,9 @@ export const identityAwsAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -251,13 +260,14 @@ export const identityAwsAuthServiceFactory = ({
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -320,6 +330,9 @@ export const identityAwsAuthServiceFactory = ({
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)) {
throw new NotFoundError({
@@ -336,13 +349,14 @@ export const identityAwsAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -387,6 +401,9 @@ export const identityAwsAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -396,13 +413,14 @@ export const identityAwsAuthServiceFactory = ({
const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
};
@@ -422,27 +440,32 @@ export const identityAwsAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have aws auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
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 { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -30,11 +37,12 @@ import {
} from "./identity-azure-auth-types";
type TIdentityAzureAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAzureAuthDAL: Pick<
TIdentityAzureAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -44,6 +52,7 @@ type TIdentityAzureAuthServiceFactoryDep = {
export type TIdentityAzureAuthServiceFactory = ReturnType<typeof identityAzureAuthServiceFactory>;
export const identityAzureAuthServiceFactory = ({
identityDAL,
identityAzureAuthDAL,
membershipIdentityDAL,
identityAccessTokenDAL,
@@ -57,11 +66,8 @@ export const identityAzureAuthServiceFactory = ({
throw new NotFoundError({ message: "Azure auth method not found for identity, did you configure Azure Auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityAzureAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const identity = await identityDAL.findById(identityAzureAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const azureIdentity = await validateAzureIdentity({
tenantId: identityAzureAuth.tenantId,
@@ -86,8 +92,8 @@ export const identityAzureAuthServiceFactory = ({
}
const identityAccessToken = await identityAzureAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{
lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH,
lastLoginTime: new Date()
@@ -125,7 +131,7 @@ export const identityAzureAuthServiceFactory = ({
}
);
return { accessToken, identityAzureAuth, identityAccessToken, identityMembershipOrg };
return { accessToken, identityAzureAuth, identityAccessToken, identity };
};
const attachAzureAuth = async ({
@@ -153,6 +159,9 @@ export const identityAzureAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -163,13 +172,14 @@ export const identityAzureAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -232,6 +242,9 @@ export const identityAzureAuthServiceFactory = ({
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)) {
throw new BadRequestError({
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" });
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -301,6 +315,9 @@ export const identityAzureAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have Azure Auth attached"
@@ -309,13 +326,14 @@ export const identityAzureAuthServiceFactory = ({
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityAzureAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -336,27 +354,32 @@ export const identityAzureAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have azure auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
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 { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -31,8 +38,9 @@ import {
} from "./identity-gcp-auth-types";
type TIdentityGcpAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
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">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -42,6 +50,7 @@ type TIdentityGcpAuthServiceFactoryDep = {
export type TIdentityGcpAuthServiceFactory = ReturnType<typeof identityGcpAuthServiceFactory>;
export const identityGcpAuthServiceFactory = ({
identityDAL,
identityGcpAuthDAL,
membershipIdentityDAL,
identityAccessTokenDAL,
@@ -55,13 +64,8 @@ export const identityGcpAuthServiceFactory = ({
throw new NotFoundError({ message: "GCP auth method not found for identity, did you configure GCP auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityGcpAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
}
const identity = await identityDAL.findById(identityGcpAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
let gcpIdentityDetails: TGcpIdentityDetails;
switch (identityGcpAuth.type) {
@@ -125,8 +129,8 @@ export const identityGcpAuthServiceFactory = ({
}
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{
lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH,
lastLoginTime: new Date()
@@ -164,7 +168,7 @@ export const identityGcpAuthServiceFactory = ({
}
);
return { accessToken, identityGcpAuth, identityAccessToken, identityMembershipOrg };
return { accessToken, identityGcpAuth, identityAccessToken, identity };
};
const attachGcpAuth = async ({
@@ -193,6 +197,9 @@ export const identityGcpAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -204,13 +211,14 @@ export const identityGcpAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -274,6 +282,9 @@ export const identityGcpAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -290,13 +301,14 @@ export const identityGcpAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -345,6 +357,9 @@ export const identityGcpAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -354,13 +369,14 @@ export const identityGcpAuthServiceFactory = ({
const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityGcpAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -381,28 +397,33 @@ export const identityGcpAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have gcp auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -3,7 +3,7 @@ import https from "https";
import jwt from "jsonwebtoken";
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -24,6 +24,7 @@ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -43,8 +44,9 @@ import {
} from "./identity-jwt-auth-types";
type TIdentityJwtAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityJwtAuthDAL: TIdentityJwtAuthDALFactory;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -55,6 +57,7 @@ type TIdentityJwtAuthServiceFactoryDep = {
export type TIdentityJwtAuthServiceFactory = ReturnType<typeof identityJwtAuthServiceFactory>;
export const identityJwtAuthServiceFactory = ({
identityDAL,
identityJwtAuthDAL,
membershipIdentityDAL,
permissionService,
@@ -69,19 +72,12 @@ export const identityJwtAuthServiceFactory = ({
throw new NotFoundError({ message: "JWT auth method not found for identity, did you configure JWT auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityJwtAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityJwtAuth.identityId}' not found`
});
}
const identity = await identityDAL.findById(identityJwtAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId
orgId: identity.orgId
});
const decodedToken = crypto.jwt().decode(jwtValue, { complete: true });
@@ -211,12 +207,9 @@ export const identityJwtAuthServiceFactory = ({
}
const identityAccessToken = await identityJwtAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH, lastLoginTime: new Date() },
tx
);
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 ({
@@ -284,6 +277,9 @@ export const identityJwtAuthServiceFactory = ({
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)) {
throw new BadRequestError({
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" });
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -387,6 +384,9 @@ export const identityJwtAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -403,13 +403,14 @@ export const identityJwtAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
@@ -491,6 +492,9 @@ export const identityJwtAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -498,13 +502,14 @@ export const identityJwtAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
@@ -539,6 +544,9 @@ export const identityJwtAuthServiceFactory = ({
if (!identityMembershipOrg) {
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)) {
throw new BadRequestError({
@@ -546,23 +554,25 @@ export const identityJwtAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -3,7 +3,12 @@ import axios, { AxiosError } from "axios";
import https from "https";
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 { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
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 { getConfig } from "@app/lib/config/env";
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 { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -48,12 +60,13 @@ import {
} from "./identity-kubernetes-auth-types";
type TIdentityKubernetesAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityKubernetesAuthDAL: Pick<
TIdentityKubernetesAuthDALFactory,
"create" | "findOne" | "transaction" | "updateById" | "delete"
>;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
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";
export const identityKubernetesAuthServiceFactory = ({
identityDAL,
identityKubernetesAuthDAL,
membershipIdentityDAL,
identityAccessTokenDAL,
@@ -175,19 +189,12 @@ export const identityKubernetesAuthServiceFactory = ({
});
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityKubernetesAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityKubernetesAuth.identityId}' not found`
});
}
const identity = await identityDAL.findById(identityKubernetesAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId
orgId: identity.orgId
});
let caCert = "";
@@ -430,12 +437,9 @@ export const identityKubernetesAuthServiceFactory = ({
}
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH, lastLoginTime: new Date() },
tx
);
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 ({
@@ -508,6 +512,9 @@ export const identityKubernetesAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -519,13 +526,14 @@ export const identityKubernetesAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -560,13 +568,14 @@ export const identityKubernetesAuthServiceFactory = ({
isGatewayV1 = false;
}
const { permission: orgPermission } = await permissionService.getOrgPermission(
const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
@@ -633,6 +642,9 @@ export const identityKubernetesAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -650,13 +662,14 @@ export const identityKubernetesAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -692,13 +705,14 @@ export const identityKubernetesAuthServiceFactory = ({
isGatewayV1 = false;
}
const { permission: orgPermission } = await permissionService.getOrgPermission(
const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
@@ -779,6 +793,9 @@ export const identityKubernetesAuthServiceFactory = ({
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 });
if (!identityKubernetesAuth) {
@@ -791,13 +808,14 @@ export const identityKubernetesAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -841,28 +859,33 @@ export const identityKubernetesAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have kubernetes auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability";
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 { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
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 {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
RateLimitError,
@@ -56,11 +57,11 @@ type TIdentityLdapAuthServiceFactoryDep = {
TIdentityLdapAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: TKmsServiceFactory;
identityDAL: TIdentityDALFactory;
identityDAL: Pick<TIdentityDALFactory, "findById" | "findOne">;
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
keyStore: Pick<
TKeyStoreFactory,
@@ -150,17 +151,6 @@ export const identityLdapAuthServiceFactory = ({
};
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 });
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) {
throw new BadRequestError({
message:
@@ -178,12 +171,9 @@ export const identityLdapAuthServiceFactory = ({
}
const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH, lastLoginTime: new Date() },
tx
);
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 ({
@@ -254,6 +244,9 @@ export const identityLdapAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -265,13 +258,14 @@ export const identityLdapAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
if (templateId) {
@@ -425,6 +419,9 @@ export const identityLdapAuthServiceFactory = ({
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)) {
throw new NotFoundError({
@@ -441,13 +438,14 @@ export const identityLdapAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
if (templateId) {
@@ -588,6 +586,9 @@ export const identityLdapAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -597,13 +598,14 @@ export const identityLdapAuthServiceFactory = ({
const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@@ -635,27 +637,32 @@ export const identityLdapAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have LDAP Auth attached"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -785,13 +792,14 @@ export const identityLdapAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const deleted = await keyStore.deleteItems({
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios";
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -14,11 +14,18 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
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 { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -35,9 +42,10 @@ import {
} from "./identity-oci-auth-types";
type TIdentityOciAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "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">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
@@ -46,6 +54,7 @@ type TIdentityOciAuthServiceFactoryDep = {
export type TIdentityOciAuthServiceFactory = ReturnType<typeof identityOciAuthServiceFactory>;
export const identityOciAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL,
identityOciAuthDAL,
membershipIdentityDAL,
@@ -59,11 +68,8 @@ export const identityOciAuthServiceFactory = ({
throw new NotFoundError({ message: "OCI auth method not found for identity, did you configure OCI auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityOciAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const identity = await identityDAL.findById(identityOciAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
// 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)) {
@@ -98,12 +104,9 @@ export const identityOciAuthServiceFactory = ({
// Generate the token
const identityAccessToken = await identityOciAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH, lastLoginTime: new Date() },
tx
);
const newToken = await identityAccessTokenDAL.create(
@@ -140,7 +143,7 @@ export const identityOciAuthServiceFactory = ({
identityOciAuth,
accessToken,
identityAccessToken,
identityMembershipOrg
identity
};
};
@@ -168,6 +171,9 @@ export const identityOciAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -179,13 +185,14 @@ export const identityOciAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -246,6 +253,9 @@ export const identityOciAuthServiceFactory = ({
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)) {
throw new NotFoundError({
@@ -262,13 +272,14 @@ export const identityOciAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -312,6 +323,9 @@ export const identityOciAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -321,13 +335,14 @@ export const identityOciAuthServiceFactory = ({
const ociIdentityAuth = await identityOciAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...ociIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
};
@@ -347,27 +362,32 @@ export const identityOciAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have OCI auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -4,7 +4,7 @@ import https from "https";
import jwt from "jsonwebtoken";
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -25,6 +25,7 @@ import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -43,8 +44,9 @@ import {
} from "./identity-oidc-auth-types";
type TIdentityOidcAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityOidcAuthDAL: TIdentityOidcAuthDALFactory;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -55,6 +57,7 @@ type TIdentityOidcAuthServiceFactoryDep = {
export type TIdentityOidcAuthServiceFactory = ReturnType<typeof identityOidcAuthServiceFactory>;
export const identityOidcAuthServiceFactory = ({
identityDAL,
identityOidcAuthDAL,
membershipIdentityDAL,
permissionService,
@@ -69,19 +72,12 @@ export const identityOidcAuthServiceFactory = ({
throw new NotFoundError({ message: "OIDC auth method not found for identity, did you configure OIDC auth?" });
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityOidcAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityOidcAuth.identityId}' not found`
});
}
const identity = await identityDAL.findById(identityOidcAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId
orgId: identity.orgId
});
let caCert = "";
@@ -182,12 +178,9 @@ export const identityOidcAuthServiceFactory = ({
}
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH, lastLoginTime: new Date() },
tx
);
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 ({
@@ -259,6 +252,9 @@ export const identityOidcAuthServiceFactory = ({
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)) {
throw new BadRequestError({
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" });
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
@@ -351,6 +348,9 @@ export const identityOidcAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -367,13 +367,14 @@ export const identityOidcAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
@@ -440,6 +441,9 @@ export const identityOidcAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -447,13 +451,14 @@ export const identityOidcAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId });
@@ -481,6 +486,9 @@ export const identityOidcAuthServiceFactory = ({
if (!identityMembershipOrg) {
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)) {
throw new BadRequestError({
@@ -488,23 +496,25 @@ export const identityOidcAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -25,11 +25,12 @@ import { buildAuthMethods } from "../identity/identity-fns";
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
export const identityProjectDALFactory = (db: TDbClient) => {
const findByIdentityId = async (identityId: string, tx?: Knex) => {
const findByIdentityId = async (identityId: string, orgId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.actorIdentityId`, identityId)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -11,10 +11,17 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
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 { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
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";
type TIdentityTlsCertAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityTlsCertAuthDAL: Pick<
TIdentityTlsCertAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
@@ -46,6 +54,7 @@ const parseSubjectDetails = (data: string) => {
};
export const identityTlsCertAuthServiceFactory = ({
identityDAL,
identityAccessTokenDAL,
identityTlsCertAuthDAL,
membershipIdentityDAL,
@@ -61,20 +70,12 @@ export const identityTlsCertAuthServiceFactory = ({
});
}
const identityMembershipOrg = await membershipIdentityDAL.findOne({
actorIdentityId: identityTlsCertAuth.identityId,
scope: AccessScope.Organization
});
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityTlsCertAuth.identityId}' not found`
});
}
const identity = await identityDAL.findById(identityTlsCertAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.scopeOrgId
orgId: identity.orgId
});
const caCertificate = decryptor({
@@ -119,12 +120,9 @@ export const identityTlsCertAuthServiceFactory = ({
// Generate the token
const identityAccessToken = await identityTlsCertAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH, lastLoginTime: new Date() },
tx
);
const newToken = await identityAccessTokenDAL.create(
@@ -161,7 +159,7 @@ export const identityTlsCertAuthServiceFactory = ({
identityTlsCertAuth,
accessToken,
identityAccessToken,
identityMembershipOrg
identity
};
};
@@ -189,6 +187,9 @@ export const identityTlsCertAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -200,13 +201,14 @@ export const identityTlsCertAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -271,6 +273,9 @@ export const identityTlsCertAuthServiceFactory = ({
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)) {
throw new NotFoundError({
@@ -288,13 +293,14 @@ export const identityTlsCertAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -350,6 +356,9 @@ export const identityTlsCertAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -359,13 +368,14 @@ export const identityTlsCertAuthServiceFactory = ({
const identityAuth = await identityTlsCertAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@@ -394,28 +404,32 @@ export const identityTlsCertAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have TLS Certificate auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem);
const permissionBoundary = validatePrivilegeChangeOperation(
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";
export type TLoginTlsCertAuthDTO = {
@@ -40,7 +40,7 @@ export type TIdentityTlsCertAuthServiceFactory = {
identityTlsCertAuth: TIdentityTlsCertAuths;
accessToken: string;
identityAccessToken: TIdentityAccessTokens;
identityMembershipOrg: TMemberships;
identity: TIdentities;
}>;
attachTlsCertAuth: (dto: TAttachTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>;
updateTlsCertAuth: (dto: TUpdateTlsCertAuthDTO) => Promise<TIdentityTlsCertAuths>;
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -10,10 +10,17 @@ import {
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
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 { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -32,11 +39,12 @@ import {
} from "./identity-token-auth-types";
type TIdentityTokenAuthServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityTokenAuthDAL: Pick<
TIdentityTokenAuthDALFactory,
"transaction" | "create" | "findOne" | "updateById" | "delete"
>;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "updateById" | "getIdentityById">;
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
identityAccessTokenDAL: Pick<
TIdentityAccessTokenDALFactory,
"create" | "find" | "update" | "findById" | "findOne" | "updateById" | "delete"
@@ -49,8 +57,8 @@ type TIdentityTokenAuthServiceFactoryDep = {
export type TIdentityTokenAuthServiceFactory = ReturnType<typeof identityTokenAuthServiceFactory>;
export const identityTokenAuthServiceFactory = ({
identityDAL,
identityTokenAuthDAL,
// identityDAL,
membershipIdentityDAL,
identityAccessTokenDAL,
permissionService,
@@ -79,6 +87,9 @@ export const identityTokenAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -90,13 +101,14 @@ export const identityTokenAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -155,6 +167,9 @@ export const identityTokenAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -172,13 +187,14 @@ export const identityTokenAuthServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -223,6 +239,9 @@ export const identityTokenAuthServiceFactory = ({
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)) {
throw new BadRequestError({
@@ -232,13 +251,14 @@ export const identityTokenAuthServiceFactory = ({
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityTokenAuth, orgId: identityMembershipOrg.scopeOrgId };
@@ -262,28 +282,33 @@ export const identityTokenAuthServiceFactory = ({
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)) {
throw new BadRequestError({
message: "The identity does not have Token Auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -341,22 +366,26 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -379,13 +408,13 @@ export const identityTokenAuthServiceFactory = ({
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) => {
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
{
lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH,
lastLoginTime: new Date()
},
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{ lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH, lastLoginTime: new Date() },
tx
);
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 ({
@@ -449,13 +478,14 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const tokens = await identityAccessTokenDAL.find(
@@ -501,22 +531,24 @@ export const identityTokenAuthServiceFactory = ({
message: "The identity does not have Token Auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -580,13 +612,14 @@ export const identityTokenAuthServiceFactory = ({
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,
actorId,
identityOrgMembership.scopeOrgId,
orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const [revokedToken] = await identityAccessTokenDAL.update(
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -13,6 +13,7 @@ import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
RateLimitError,
@@ -22,6 +23,7 @@ import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from
import { logger } from "@app/lib/logger";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
@@ -42,6 +44,7 @@ import {
} from "./identity-ua-types";
type TIdentityUaServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "findById">;
identityUaDAL: TIdentityUaDALFactory;
identityUaClientSecretDAL: TIdentityUaClientSecretDALFactory;
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
@@ -70,7 +73,8 @@ export const identityUaServiceFactory = ({
permissionService,
licenseService,
orgDAL,
keyStore
keyStore,
identityDAL
}: TIdentityUaServiceFactoryDep) => {
const login = async (clientId: string, clientSecret: string, ip: string) => {
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 clientSecretInfo = await identityUaClientSecretDAL.find({
identityUAId: identityUa.id,
@@ -227,10 +221,11 @@ export const identityUaServiceFactory = ({
accessTokenMaxTTL: 1000000000
};
const identity = await identityDAL.findById(identityUa.identityId);
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
await membershipIdentityDAL.updateById(
identityMembershipOrg.id,
await membershipIdentityDAL.update(
{ scope: AccessScope.Organization, scopeOrgId: identity.orgId, actorIdentityId: identity.id },
{
lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
lastLoginTime: new Date()
@@ -276,7 +271,7 @@ export const identityUaServiceFactory = ({
identityUa,
validClientSecretInfo,
identityAccessToken,
identityMembershipOrg,
identity,
...accessTokenTTLParams
};
};
@@ -315,18 +310,23 @@ export const identityUaServiceFactory = ({
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) {
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
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 (
(accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 &&
(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" });
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.scopeOrgId);
@@ -512,14 +517,18 @@ export const identityUaServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...uaIdentityAuth, orgId: identityMembershipOrg.scopeOrgId };
};
@@ -545,22 +554,27 @@ export const identityUaServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -611,23 +625,28 @@ export const identityUaServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -692,23 +711,28 @@ export const identityUaServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -761,6 +785,9 @@ export const identityUaServiceFactory = ({
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 });
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 });
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
@@ -828,6 +857,9 @@ export const identityUaServiceFactory = ({
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 });
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 });
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
const { permission: rolePermission } = await permissionService.getOrgPermission({
actor: ActorType.IDENTITY,
actorId: identityMembershipOrg.identity.id,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const permissionBoundary = validatePrivilegeChangeOperation(
@@ -900,14 +934,18 @@ export const identityUaServiceFactory = ({
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,
actorId,
identityMembershipOrg.scopeOrgId,
orgId: identityMembershipOrg.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const deleted = await keyStore.deleteItems({
@@ -163,7 +163,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
.select(
selectAllTableCols(TableName.Membership),
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)
.as("paginatedIdentity");
@@ -257,6 +258,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
db.ref("scopeOrgId").withSchema("paginatedIdentity").as("orgId"),
db.ref("lastLoginAuthMethod").withSchema("paginatedIdentity"),
db.ref("orgId").withSchema("paginatedIdentity").as("identityOrgId"),
db.ref("lastLoginTime").withSchema("paginatedIdentity"),
db.ref("createdAt").withSchema("paginatedIdentity"),
db.ref("updatedAt").withSchema("paginatedIdentity"),
@@ -309,6 +311,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
roleId,
id,
orgId,
identityOrgId,
uaId,
alicloudId,
awsId,
@@ -348,6 +351,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
id: identityId as string,
name: identityName,
hasDeleteProtection,
orgId: identityOrgId,
authMethods: buildAuthMethods({
uaId,
alicloudId,
@@ -515,6 +519,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"),
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("gcpId").withSchema(TableName.IdentityGcpAuth),
@@ -566,6 +571,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
crPermission,
crName,
identityId,
identityOrgId,
identityName,
hasDeleteProtection,
role,
@@ -611,6 +617,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
id: identityId as string,
name: identityName,
hasDeleteProtection,
orgId: identityOrgId,
authMethods: buildAuthMethods({
uaId,
alicloudId,
@@ -1,6 +1,6 @@
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 { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
@@ -12,6 +12,7 @@ import { TKeyStoreFactory } from "@app/keystore/keystore";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
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 { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TOrgDALFactory } from "../org/org-dal";
@@ -40,6 +41,7 @@ type TIdentityServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
keyStore: Pick<TKeyStoreFactory, "getKeysByPattern" | "getItem">;
orgDAL: Pick<TOrgDALFactory, "findById">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">;
};
export type TIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
@@ -54,7 +56,8 @@ export const identityServiceFactory = ({
keyStore,
orgDAL,
membershipIdentityDAL,
membershipRoleDAL
membershipRoleDAL,
additionalPrivilegeDAL
}: TIdentityServiceFactoryDep) => {
const createIdentity = async ({
name,
@@ -67,7 +70,14 @@ export const identityServiceFactory = ({
actorOrgId,
metadata
}: 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);
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], orgId);
@@ -104,7 +114,7 @@ export const identityServiceFactory = ({
}
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(
{
scope: AccessScope.Organization,
@@ -172,13 +182,14 @@ export const identityServiceFactory = ({
});
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,
actorId,
identityOrgMembership.scopeOrgId,
orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
let customRole: TRoles | undefined;
@@ -208,11 +219,12 @@ export const identityServiceFactory = ({
if (isCustomRole) customRole = rolePermissionDetails?.role;
}
const identityDetails = await identityDAL.findById(id);
const identity = await identityDAL.transaction(async (tx) => {
const newIdentity =
name || hasDeleteProtection
identityDetails.orgId === actorOrgId && (name || hasDeleteProtection)
? await identityDAL.updateById(id, { name, hasDeleteProtection }, tx)
: await identityDAL.findById(id, tx);
: identityDetails;
if (role) {
await membershipRoleDAL.delete({ membershipId: identityOrgMembership.id }, tx);
@@ -264,16 +276,16 @@ export const identityServiceFactory = ({
const identity = doc[0];
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,
actorId,
identity.orgId,
orgId: identity.orgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
// TODO(namespace): check this in identity service
const activeLockouts = await keyStore.getKeysByPattern(`lockout:identity:${id}:*`);
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}` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityOrgMembership.scopeOrgId,
orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
if (identityOrgMembership.identity.hasDeleteProtection)
throw new BadRequestError({ message: "Identity has delete protection" });
const deletedIdentity = await identityDAL.deleteById(id);
if (identityOrgMembership.identity.identityOrgId === actorOrgId) {
const deletedIdentity = await identityDAL.deleteById(id);
await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId);
return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId };
}
await licenseService.updateSubscriptionOrgMemberCount(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 };
};
@@ -346,7 +391,14 @@ export const identityServiceFactory = ({
orderDirection,
search
}: 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);
const identityMemberships = await identityOrgMembershipDAL.find({
@@ -379,7 +431,14 @@ export const identityServiceFactory = ({
orderDirection,
searchFilter = {}
}: 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);
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}` });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
identityOrgMembership.scopeOrgId,
orgId: identityOrgMembership.scopeOrgId,
actorAuthMethod,
actorOrgId
);
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId);
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId, actorOrgId);
return identityMemberships;
};
@@ -112,7 +112,7 @@ export const integrationAuthServiceFactory = ({
};
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(
authorizations.map(async (auth) => {
@@ -1,6 +1,6 @@
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 {
constructPermissionErrorMessage,
@@ -45,13 +45,14 @@ export const newOrgMembershipGroupFactory = ({
};
const onUpdateMembershipGroupGuard: TMembershipGroupScopeFactory["onUpdateMembershipGroupGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
const permissionRoles = await permissionService.getOrgPermissionByRoles(
dto.data.roles.map((el) => el.role),
@@ -89,26 +90,28 @@ export const newOrgMembershipGroupFactory = ({
};
const onListMembershipGroupGuard: TMembershipGroupScopeFactory["onListMembershipGroupGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
};
const onGetMembershipGroupByGroupIdGuard: TMembershipGroupScopeFactory["onGetMembershipGroupByGroupIdGuard"] = async (
dto
) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
};
@@ -91,6 +91,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
.select(
db.ref("name").withSchema(TableName.Identity).as("identityName"),
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("slug").withSchema(TableName.Role).as("roleSlug"),
@@ -132,6 +133,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
parentMapper: (el) => {
const {
identityId: actorIdentityId,
identityOrgId,
identityHasDeleteProtection,
identityName,
uaId,
@@ -153,6 +155,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
name: identityName,
id: actorIdentityId,
hasDeleteProtection: identityHasDeleteProtection,
identityOrgId,
authMethods: buildAuthMethods({
uaId,
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 { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TRoleDALFactory } from "../role/role-dal";
@@ -31,6 +32,7 @@ type TMembershipIdentityServiceFactoryDep = {
>;
orgDAL: Pick<TOrgDALFactory, "findById">;
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "delete">;
identityDAL: Pick<TIdentityDALFactory, "findById">;
};
export type TMembershipIdentityServiceFactory = ReturnType<typeof membershipIdentityServiceFactory>;
@@ -41,12 +43,14 @@ export const membershipIdentityServiceFactory = ({
membershipRoleDAL,
permissionService,
orgDAL,
additionalPrivilegeDAL
additionalPrivilegeDAL,
identityDAL
}: TMembershipIdentityServiceFactoryDep) => {
const scopeFactory = {
[AccessScope.Organization]: newOrgMembershipIdentityFactory({
orgDAL,
permissionService
permissionService,
identityDAL
}),
[AccessScope.Project]: newProjectMembershipIdentityFactory({
membershipIdentityDAL,
@@ -305,7 +309,7 @@ export const membershipIdentityServiceFactory = ({
[SearchResourceOperators.$contains]: dto.data.identityName
}
: undefined,
role: dto.data.roles.length
role: dto.data?.roles?.length
? {
[SearchResourceOperators.$in]: dto.data.roles
}
@@ -329,11 +333,29 @@ export const membershipIdentityServiceFactory = ({
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 {
createMembership,
updateMembership,
deleteMembership,
listMemberships,
getMembershipByIdentityId
getMembershipByIdentityId,
listAvailableIdentities
};
};
@@ -54,14 +54,11 @@ export type TUpdateMembershipIdentityDTO = {
export type TListMembershipIdentityDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
selector: {
identityId: string;
};
data: {
limit?: number;
offset?: number;
identityName?: string;
roles: string[];
roles?: string[];
};
};
@@ -1,6 +1,6 @@
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 {
constructPermissionErrorMessage,
@@ -8,6 +8,7 @@ import {
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
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 { isCustomOrgRole } from "@app/services/org/org-role-fns";
@@ -16,11 +17,13 @@ import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
type TOrgMembershipIdentityScopeFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRoles">;
orgDAL: Pick<TOrgDALFactory, "findById">;
identityDAL: Pick<TIdentityDALFactory, "findById">;
};
export const newOrgMembershipIdentityFactory = ({
permissionService,
orgDAL
orgDAL,
identityDAL
}: TOrgMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => {
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) {
@@ -38,23 +41,66 @@ export const newOrgMembershipIdentityFactory = ({
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] =
async () => {
throw new BadRequestError({
message: "Organization membership cannot be created for organization scoped identity"
});
};
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] = async (
dto
) => {
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 (
dto
) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const permissionRoles = await permissionService.getOrgPermissionByRoles(
dto.data.roles.map((el) => el.role),
@@ -85,35 +131,54 @@ export const newOrgMembershipIdentityFactory = ({
}
};
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] =
async () => {
throw new BadRequestError({
message: "Organization membership cannot be deleted for organization scoped identity"
});
};
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] = async (
dto
) => {
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 (
dto
) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
};
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
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 = {
membershipUserDAL: TMembershipUserDALFactory;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
orgDAL: Pick<TOrgDALFactory, "findById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findById" | "transaction" | "find">;
roleDAL: Pick<TRoleDALFactory, "find">;
userDAL: TUserDALFactory;
permissionService: Pick<
@@ -83,7 +83,8 @@ export const membershipUserServiceFactory = ({
orgDAL,
tokenService,
userDAL,
userGroupMembershipDAL
userGroupMembershipDAL,
membershipUserDAL
}),
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
[AccessScope.Project]: newProjectMembershipUserFactory({
@@ -404,7 +405,7 @@ export const membershipUserServiceFactory = ({
const membershipDoc = await membershipUserDAL.transaction(async (tx) => {
if (dto.scopeData.scope === AccessScope.Organization) {
const [doc] = await deleteOrgMembershipsFn({
orgMembershipIds: [],
orgMembershipIds: [existingMembership.id],
orgId: dto.permission.orgId,
orgDAL,
projectKeyDAL,
@@ -471,11 +472,26 @@ export const membershipUserServiceFactory = ({
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 {
createMembership,
updateMembership,
deleteMembership,
listMemberships,
getMembershipByUserId
getMembershipByUserId,
listAvailableUsers
};
};
@@ -93,3 +93,8 @@ export type TGetMembershipUserByUserIdDTO = {
userId: string;
};
};
export type TListAvailableUsersDTO = {
permission: OrgServiceActor;
scopeData: AccessScopeData;
};
@@ -1,6 +1,6 @@
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 { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
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 { TUserDALFactory } from "@app/services/user/user-dal";
import { TMembershipUserDALFactory } from "../membership-user-dal";
import { TMembershipUserScopeFactory } from "../membership-user-types";
type TOrgMembershipUserScopeFactoryDep = {
@@ -25,6 +26,7 @@ type TOrgMembershipUserScopeFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findById">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "find">;
};
export const newOrgMembershipUserFactory = ({
@@ -33,7 +35,8 @@ export const newOrgMembershipUserFactory = ({
userDAL,
orgDAL,
smtpService,
licenseService
licenseService,
membershipUserDAL
}: TOrgMembershipUserScopeFactoryDep): TMembershipUserScopeFactory => {
const getScopeField: TMembershipUserScopeFactory["getScopeField"] = (dto) => {
if (dto.scope === AccessScope.Organization) {
@@ -51,14 +54,18 @@ export const newOrgMembershipUserFactory = ({
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (
dto,
newMembers
) => {
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
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"
});
}
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 (
@@ -95,87 +121,103 @@ export const newOrgMembershipUserFactory = ({
const signUpTokens: { email: string; link: string }[] = [];
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(
newUsers.map(async (el) => {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
userId: el.id,
orgId: dto.permission.orgId
});
await Promise.allSettled(
newUsers.map(async (el) => {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
userId: el.id,
orgId: dto.permission.orgId
});
if (el.email) {
if (!appCfg.isSmtpConfigured) {
signUpTokens.push({
email: el.email,
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${dto.permission.orgId}`
});
}
if (el.email) {
if (!appCfg.isSmtpConfigured) {
signUpTokens.push({
email: el.email,
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${dto.permission.orgId}`
await smtpService.sendMail({
template: SmtpTemplates.OrgInvite,
subjectLine: "Infisical organization invitation",
recipients: [el.email],
substitutions: {
inviterFirstName: actorDetails?.firstName,
inviterUsername: actorDetails?.email,
organizationName: orgDetails?.name,
email: el.email,
organizationId: orgDetails?.id.toString(),
token,
callback_url: `${appCfg.SITE_URL}/signupinvite`
}
});
}
await smtpService.sendMail({
template: SmtpTemplates.OrgInvite,
subjectLine: "Infisical organization invitation",
recipients: [el.email],
substitutions: {
inviterFirstName: actorDetails?.firstName,
inviterUsername: actorDetails?.email,
organizationName: orgDetails?.name,
email: el.email,
organizationId: orgDetails?.id.toString(),
token,
callback_url: `${appCfg.SITE_URL}/signupinvite`
}
});
}
})
);
})
);
}
return { signUpTokens };
};
const onUpdateMembershipUserGuard: TMembershipUserScopeFactory["onUpdateMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
};
const onDeleteMembershipUserGuard: TMembershipUserScopeFactory["onDeleteMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
};
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
};
const onGetMembershipUserByUserIdGuard: TMembershipUserScopeFactory["onGetMembershipUserByUserIdGuard"] = async (
dto
) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
};
@@ -9,6 +9,7 @@ import {
import { CronJob } from "cron";
import { FastifyReply, FastifyRequest } from "fastify";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
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,
actorId,
microsoftTeamsIntegration.orgId,
orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -282,13 +284,14 @@ export const microsoftTeamsServiceFactory = ({
description,
redirectUri
}: TCreateMicrosoftTeamsIntegrationDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -393,13 +396,14 @@ export const microsoftTeamsServiceFactory = ({
});
};
const getClientId = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGetClientIdDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -427,13 +431,14 @@ export const microsoftTeamsServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TGetMicrosoftTeamsIntegrationByOrgDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
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,
actorId,
microsoftTeamsIntegration.orgId,
orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
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,
actorId,
microsoftTeamsIntegration.orgId,
orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
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,
actorId,
microsoftTeamsIntegration.orgId,
orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
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,
actorId,
microsoftTeamsIntegration.orgId,
orgId: microsoftTeamsIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope, ProjectMembershipRole, ProjectVersion } from "@app/db/schemas";
import { AccessScope, OrganizationActionScope, ProjectMembershipRole, ProjectVersion } from "@app/db/schemas";
import { OrgPermissionAdminConsoleAction, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -44,13 +44,14 @@ export const orgAdminServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TListOrgProjectsDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAdminConsoleAction.AccessAllProjects,
OrgPermissionSubjects.AdminConsole
@@ -76,13 +77,14 @@ export const orgAdminServiceFactory = ({
actorAuthMethod,
projectId
}: TAccessProjectDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAdminConsoleAction.AccessAllProjects,
OrgPermissionSubjects.AdminConsole
+66 -1
View File
@@ -26,6 +26,7 @@ import {
} from "@app/lib/knex";
import { generateKnexQueryFromScim } from "@app/lib/knex/scim";
import { ActorType } from "../auth/auth-type";
import { OrgAuthMethod } from "./org-types";
export type TOrgDALFactory = ReturnType<typeof orgDALFactory>;
@@ -64,6 +65,7 @@ export const orgDALFactory = (db: TDbClient) => {
const buildBaseQuery = (orgIdSubquery: Knex.QueryBuilder) => {
return db
.replicaNode()(TableName.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.whereIn(`${TableName.Organization}.id`, orgIdSubquery)
.leftJoin(TableName.Project, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
.leftJoin(TableName.Membership, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
@@ -154,11 +156,49 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const listSubOrganizations = async (dto: {
actorId: string;
actorType: ActorType;
orgId: string;
isAccessible?: boolean;
limit?: number;
offset?: number;
}) => {
try {
// TODO(sub-org:group): check this when implement group support
const query = db
.replicaNode()(TableName.Organization)
.where(`${TableName.Organization}.rootOrgId`, dto.orgId)
.select(selectAllTableCols(TableName.Organization));
if (dto.isAccessible) {
void query
.leftJoin(`${TableName.Membership}`, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
.where((qb) => {
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
if (dto.actorType === ActorType.IDENTITY) {
void qb.andWhere(`${TableName.Membership}.actorIdentityId`, dto.actorId);
} else {
void qb.andWhere(`${TableName.Membership}.actorUserId`, dto.actorId);
}
});
}
if (dto.limit) void query.limit(dto.limit);
if (dto.offset) void query.offset(dto.offset);
const orgs = await query;
return orgs;
} catch (error) {
throw new DatabaseError({ error, name: "List sub organization" });
}
};
const findOrgById = async (orgId: string) => {
try {
const org = (await db
.replicaNode()(TableName.Organization)
.where({ [`${TableName.Organization}.id` as "id"]: orgId })
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
`${TableName.SamlConfig}.isActive`,
@@ -195,6 +235,7 @@ export const orgDALFactory = (db: TDbClient) => {
try {
const org = (await db
.replicaNode()(TableName.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.where({ [`${TableName.Organization}.slug` as "slug"]: orgSlug })
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
@@ -240,6 +281,7 @@ export const orgDALFactory = (db: TDbClient) => {
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.SamlConfig, (qb) => {
qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
`${TableName.SamlConfig}.isActive`,
@@ -337,6 +379,7 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
// TODO(sub-org): updated this logic later
const countAllOrgMembers = async (orgId: string) => {
try {
interface CountResult {
@@ -610,6 +653,7 @@ export const orgDALFactory = (db: TDbClient) => {
})
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.whereNull(`${TableName.Organization}.rootOrgId`)
.leftJoin(TableName.UserAliases, function joinUserAlias() {
this.on(`${TableName.UserAliases}.userId`, "=", `${TableName.Membership}.actorUserId`)
.andOn(`${TableName.UserAliases}.orgId`, "=", `${TableName.Membership}.scopeOrgId`)
@@ -648,6 +692,7 @@ export const orgDALFactory = (db: TDbClient) => {
.replicaNode()(TableName.Membership)
.where({ actorIdentityId: identityId })
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNull(`${TableName.Organization}.rootOrgId`)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
@@ -662,11 +707,30 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const findRootOrgDetails = async (orgId: string, tx?: Knex): Promise<TOrganizations | undefined> => {
try {
const org = await (tx ?? db.replicaNode())(TableName.Organization)
.select(selectAllTableCols(TableName.Organization))
.where(
"id",
db(TableName.Organization)
.select(db.raw(`CASE WHEN "rootOrgId" IS NULL THEN id ELSE "rootOrgId" END`))
.where("id", orgId)
)
.first();
return org;
} catch (error) {
throw new DatabaseError({ error, name: "FindRootOrgDetails" });
}
};
return withTransaction(db, {
...orgOrm,
findOrgByProjectId,
findAllOrgMembers,
countAllOrgMembers,
listSubOrganizations,
findOrgById,
findOrgBySlug,
findAllOrgsByUserId,
@@ -684,6 +748,7 @@ export const orgDALFactory = (db: TDbClient) => {
deleteMembershipById,
deleteMembershipsById,
updateMembership,
findIdentityOrganization
findIdentityOrganization,
findRootOrgDetails
});
};
+6 -13
View File
@@ -13,7 +13,7 @@ import { TMembershipUserDALFactory } from "../membership-user/membership-user-da
type TDeleteOrgMemberships = {
orgMembershipIds: string[];
orgId: string;
orgDAL: Pick<TOrgDALFactory, "transaction">;
orgDAL: Pick<TOrgDALFactory, "transaction" | "find">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "delete">;
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete" | "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "delete">;
@@ -34,19 +34,9 @@ export const deleteOrgMembershipsFn = async ({
userId,
membershipUserDAL,
userGroupMembershipDAL,
membershipRoleDAL,
additionalPrivilegeDAL
}: TDeleteOrgMemberships) => {
const deletedMemberships = await orgDAL.transaction(async (tx) => {
await membershipRoleDAL.delete(
{
$in: {
membershipId: orgMembershipIds
}
},
tx
);
const orgMemberships = await membershipUserDAL.delete(
{
scopeOrgId: orgId,
@@ -83,12 +73,13 @@ export const deleteOrgMembershipsFn = async ({
);
// Get all the project memberships of the users in the organization
const childOrgs = await orgDAL.find({ rootOrgId: orgId }, { tx });
// Delete all the project memberships of the users in the organization
const otherMemberships = await membershipUserDAL.delete(
{
scopeOrgId: orgId,
$in: {
scopeOrgId: [orgId].concat(childOrgs.map((el) => el.id)),
actorUserId: membershipUserIds
}
},
@@ -96,7 +87,9 @@ export const deleteOrgMembershipsFn = async ({
);
const orgGroups = await membershipUserDAL.find({
scopeOrgId: orgId,
$in: {
scopeOrgId: [orgId].concat(childOrgs.map((el) => el.id))
},
$notNull: ["actorGroupId"]
});
+134 -61
View File
@@ -4,6 +4,7 @@ import { Knex } from "knex";
import {
AccessScope,
OrganizationActionScope,
OrgMembershipRole,
OrgMembershipStatus,
TableName,
@@ -156,16 +157,31 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
rootOrgId: string,
actorOrgId: string
) => {
await permissionService.getOrgPermission(ActorType.USER, userId, orgId, actorAuthMethod, actorOrgId);
await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId: rootOrgId,
scope: OrganizationActionScope.Any
});
const appCfg = getConfig();
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME };
const hasSubOrg = actorOrgId !== rootOrgId;
let subOrg;
if (hasSubOrg) {
subOrg = await orgDAL.findOne({ rootOrgId, id: actorOrgId });
}
return org;
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME, subOrganization: subOrg };
}
return { ...org, subOrganization: subOrg };
};
/*
* Get all organization a user part of
@@ -192,15 +208,16 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findAllOrgMembers(orgId);
@@ -208,7 +225,14 @@ export const orgServiceFactory = ({
};
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const groups = await groupDAL.findByOrgId(orgId);
return groups;
@@ -222,7 +246,14 @@ export const orgServiceFactory = ({
orgId,
emails
}: TFindOrgMembersByEmailDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const members = await orgDAL.findOrgMembersByUsername(orgId, emails);
@@ -309,13 +340,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
orgId
}: TUpgradePrivilegeSystemDTO) => {
const { hasRole } = await permissionService.getOrgPermission(
ActorType.USER,
const { hasRole } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({
@@ -380,7 +412,14 @@ export const orgServiceFactory = ({
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
if (allowSecretSharingOutsideOrganization !== undefined) {
@@ -477,6 +516,12 @@ export const orgServiceFactory = ({
}
}
if (slug) {
const existingOrg = await orgDAL.findOne({ slug, rootOrgId: null });
if (existingOrg && existingOrg?.id !== orgId)
throw new BadRequestError({ message: `Organization with slug ${slug} already exist` });
}
if (googleSsoAuthEnforced) {
if (googleSsoAuthEnforced && currentOrg.authEnforced) {
throw new BadRequestError({
@@ -634,7 +679,7 @@ export const orgServiceFactory = ({
const organization = await (trx ? createOrg(trx) : orgDAL.transaction(createOrg));
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
await licenseService.updateSubscriptionOrgMemberCount(organization.id, trx);
return organization;
};
@@ -656,15 +701,16 @@ export const orgServiceFactory = ({
ipAddress: string;
orgId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
actorOrgId: string;
}) => {
const { hasRole } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { hasRole } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({
name: "DeleteOrganizationById",
@@ -744,13 +790,14 @@ export const orgServiceFactory = ({
actorOrgId,
metadata
}: TUpdateOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
const foundMembership = await membershipUserDAL.findOne({
@@ -831,7 +878,14 @@ export const orgServiceFactory = ({
membershipId
}: TResendOrgMemberInvitationDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
@@ -967,7 +1021,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TGetOrgMembershipDTO) => {
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.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(membershipId);
@@ -988,13 +1049,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
const [deletedMembership] = await deleteOrgMembershipsFn({
@@ -1021,13 +1083,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteOrgMembershipsDTO) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
if (membershipIds.includes(userId)) {
@@ -1059,7 +1122,14 @@ export const orgServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TListProjectMembershipsByOrgMembershipIdDTO) => {
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.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(orgMembershipId);
@@ -1080,15 +1150,16 @@ export const orgServiceFactory = ({
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
const incidentContacts = await incidentContactDAL.findByOrgId(orgId);
return incidentContacts;
@@ -1099,15 +1170,16 @@ export const orgServiceFactory = ({
orgId: string,
email: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email });
if (doesIncidentContactExist) {
@@ -1126,15 +1198,16 @@ export const orgServiceFactory = ({
orgId: string,
id: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
actorOrgId: string
) => {
const { permission } = await permissionService.getOrgPermission(
ActorType.USER,
userId,
const { permission } = await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount);
const incidentContact = await incidentContactDAL.deleteById(id, orgId);
+5 -5
View File
@@ -8,7 +8,7 @@ export type TUpdateOrgMembershipDTO = {
membershipId: string;
role?: string;
isActive?: boolean;
actorOrgId: string | undefined;
actorOrgId: string;
metadata?: { key: string; value: string }[];
actorAuthMethod: ActorAuthMethod;
};
@@ -21,7 +21,7 @@ export type TDeleteOrgMembershipDTO = {
userId: string;
orgId: string;
membershipId: string;
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
};
@@ -29,7 +29,7 @@ export type TDeleteOrgMembershipsDTO = {
userId: string;
orgId: string;
membershipIds: string[];
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
};
@@ -54,7 +54,7 @@ export type TVerifyUserToOrgDTO = {
export type TFindOrgMembersByEmailDTO = {
actor: ActorType;
actorOrgId: string | undefined;
actorOrgId: string;
actorId: string;
actorAuthMethod: ActorAuthMethod;
orgId: string;
@@ -64,7 +64,7 @@ export type TFindOrgMembersByEmailDTO = {
export type TFindAllWorkspacesDTO = {
actor: ActorType;
actorId: string;
actorOrgId: string | undefined;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
orgId: string;
};
+3 -1
View File
@@ -413,10 +413,12 @@ export const projectDALFactory = (db: TDbClient) => {
const countOfOrgProjects = async (orgId: string | null, tx?: Knex) => {
try {
const subOrgProjects = db.replicaNode()(TableName.Organization).where({ rootOrgId: orgId }).select("id");
const doc = await (tx || db.replicaNode())(TableName.Project)
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
void bd.where({ orgId }).orWhereIn("orgId", subOrgProjects);
}
})
.count();
+25 -20
View File
@@ -5,6 +5,7 @@ import slugify from "@sindresorhus/slugify";
import {
AccessScope,
ActionProjectType,
OrganizationActionScope,
ProjectMembershipRole,
ProjectType,
ProjectVersion,
@@ -245,13 +246,14 @@ export const projectServiceFactory = ({
type = ProjectType.SecretManager
}: TCreateProjectDTO) => {
const organization = await orgDAL.findOne({ id: actorOrgId });
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
organization.id,
orgId: organization.id,
actorAuthMethod,
actorOrgId
);
});
if (
permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace) &&
@@ -513,13 +515,14 @@ export const projectServiceFactory = ({
: await projectDAL.findUserProjects(actorId, actorOrgId, type);
if (includeRoles) {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
});
// `includeRoles` is specifically used by organization admins when inviting new users to the organizations to avoid looping redundant api calls.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
@@ -1822,13 +1825,14 @@ export const projectServiceFactory = ({
projectIds
}: TSearchProjectsDTO) => {
// check user belong to org
await permissionService.getOrgPermission(
permission.type,
permission.id,
permission.orgId,
permission.authMethod,
permission.orgId
);
await permissionService.getOrgPermission({
actor: permission.type,
actorId: permission.id,
orgId: permission.orgId,
actorAuthMethod: permission.authMethod,
scope: OrganizationActionScope.Any,
actorOrgId: permission.orgId
});
return projectDAL.searchProjects({
limit,
@@ -1846,13 +1850,14 @@ export const projectServiceFactory = ({
const requestProjectAccess = async ({ permission, comment, projectId }: TProjectAccessRequestDTO) => {
// check user belong to org
await permissionService.getOrgPermission(
permission.type,
permission.id,
permission.orgId,
permission.authMethod,
permission.orgId
);
await permissionService.getOrgPermission({
actor: permission.type,
actorId: permission.id,
orgId: permission.orgId,
actorAuthMethod: permission.authMethod,
actorOrgId: permission.orgId,
scope: OrganizationActionScope.Any
});
const projectMember = await permissionService
.getProjectPermission({
@@ -41,7 +41,7 @@ export type TCreateProjectDTO = {
actor: ActorType;
actorAuthMethod: ActorAuthMethod;
actorId: string;
actorOrgId?: string;
actorOrgId: string;
projectName: string;
projectDescription?: string;
slug?: string;
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope } from "@app/db/schemas";
import { AccessScope, OrganizationActionScope } from "@app/db/schemas";
import {
orgAdminPermissions,
orgMemberPermissions,
@@ -34,35 +34,38 @@ export const newOrgRoleFactory = ({
const isCustomRole: TRoleScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
const onCreateRoleGuard: TRoleScopeFactory["onCreateRoleGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
};
const onUpdateRoleGuard: TRoleScopeFactory["onUpdateRoleGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role);
};
const onDeleteRoleGuard: TRoleScopeFactory["onDeleteRoleGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
const externalGroupMapping = await externalGroupOrgRoleMappingDAL.findOne({
@@ -78,35 +81,38 @@ export const newOrgRoleFactory = ({
};
const onListRoleGuard: TRoleScopeFactory["onListRoleGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
};
const onGetRoleByIdGuard: TRoleScopeFactory["onGetRoleByIdGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
};
const onGetRoleBySlugGuard: TRoleScopeFactory["onGetRoleBySlugGuard"] = async (dto) => {
const { permission } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
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.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
};
+9 -8
View File
@@ -1,7 +1,7 @@
import { packRules } from "@casl/ability/extra";
import { requestContext } from "@fastify/request-context";
import { AccessScope, ActionProjectType, TableName } from "@app/db/schemas";
import { AccessScope, ActionProjectType, OrganizationActionScope, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -214,13 +214,14 @@ export const roleServiceFactory = ({
const getUserPermission = async (dto: TGetUserPermissionDTO) => {
if (dto.scopeData.scope === AccessScope.Organization) {
const { permission, memberships } = await permissionService.getOrgPermission(
dto.permission.type,
dto.permission.id,
dto.permission.orgId,
dto.permission.authMethod,
dto.permission.orgId
);
const { permission, memberships } = await permissionService.getOrgPermission({
actorId: dto.permission.id,
actor: dto.permission.type,
orgId: dto.permission.orgId,
actorOrgId: dto.permission.orgId,
actorAuthMethod: dto.permission.authMethod,
scope: OrganizationActionScope.Any
});
return { permissions: packRules(permission.rules), memberships, assumedPrivilegeDetails: undefined };
}
@@ -1,4 +1,4 @@
import { TSecretSharing } from "@app/db/schemas";
import { OrganizationActionScope, TSecretSharing } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
@@ -81,12 +81,21 @@ export const secretSharingServiceFactory = ({
}: TCreateSharedSecretDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId,
scope: OrganizationActionScope.Any
});
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
$validateSharedSecretExpiry(expiresAt);
const org = await orgDAL.findOrgById(orgId);
if (!org.allowSecretSharingOutsideOrganization && accessType === SecretSharingAccessType.Anyone) {
const rootOrg = await orgDAL.findRootOrgDetails(orgId);
if (!rootOrg) throw new BadRequestError({ message: `Organization with id ${orgId} not found` });
if (!rootOrg.allowSecretSharingOutsideOrganization && accessType === SecretSharingAccessType.Anyone) {
throw new BadRequestError({
message: "Organization does not allow sharing secrets to members outside of this organization"
});
@@ -100,13 +109,16 @@ export const secretSharingServiceFactory = ({
const expiresAtTimestamp = new Date(expiresAt).getTime();
const lifetime = expiresAtTimestamp - new Date().getTime();
// org.maxSharedSecretLifetime is in seconds
if (org.maxSharedSecretLifetime && lifetime / 1000 > org.maxSharedSecretLifetime) {
// rootOrg.maxSharedSecretLifetime is in seconds
if (rootOrg.maxSharedSecretLifetime && lifetime / 1000 > rootOrg.maxSharedSecretLifetime) {
throw new BadRequestError({ message: "Secret lifetime exceeds organization limit" });
}
// Check max view count is within org allowance
if (org.maxSharedSecretViewLimit && (!expiresAfterViews || expiresAfterViews > org.maxSharedSecretViewLimit)) {
if (
rootOrg.maxSharedSecretViewLimit &&
(!expiresAfterViews || expiresAfterViews > rootOrg.maxSharedSecretViewLimit)
) {
throw new BadRequestError({ message: "Secret max views parameter exceeds organization limit" });
}
@@ -122,7 +134,10 @@ export const secretSharingServiceFactory = ({
if (allOrgMembers.some((v) => v.user.email === email)) {
orgEmails.push(email);
// If the email is not part of the org, but access type / org settings require it
} else if (!org.allowSecretSharingOutsideOrganization || accessType === SecretSharingAccessType.Organization) {
} else if (
!rootOrg.allowSecretSharingOutsideOrganization ||
accessType === SecretSharingAccessType.Organization
) {
throw new BadRequestError({
message: "Organization does not allow sharing secrets to members outside of this organization"
});
@@ -196,7 +211,14 @@ export const secretSharingServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TCreateSecretRequestDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
$validateSharedSecretExpiry(expiresAt);
@@ -228,7 +250,14 @@ export const secretSharingServiceFactory = ({
throw new NotFoundError({ message: `Secret request with ID '${id}' not found` });
}
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
if (secretRequest.userId !== actorId || secretRequest.orgId !== orgId) {
@@ -267,13 +296,14 @@ export const secretSharingServiceFactory = ({
throw new UnauthorizedError();
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
secretRequest.orgId,
orgId: secretRequest.orgId,
actorAuthMethod,
actorOrgId
);
});
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
}
@@ -316,13 +346,14 @@ export const secretSharingServiceFactory = ({
throw new UnauthorizedError();
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
secretRequest.orgId,
orgId: secretRequest.orgId,
actorAuthMethod,
actorOrgId
);
});
if (!permission) throw new ForbiddenRequestError({ name: "User is not a part of the specified organization" });
const user = await userDAL.findById(actorId);
@@ -415,13 +446,14 @@ export const secretSharingServiceFactory = ({
}: TGetSharedSecretsDTO) => {
if (!actorOrgId) throw new ForbiddenRequestError();
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
});
if (!permission) throw new ForbiddenRequestError({ name: "User does not belong to the specified organization" });
const secrets = await secretSharingDAL.find(
@@ -563,7 +595,14 @@ export const secretSharingServiceFactory = ({
const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => {
const { actor, actorId, orgId, actorAuthMethod, actorOrgId, sharedSecretId } = deleteSharedSecretInput;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
if (!permission) throw new ForbiddenRequestError({ name: "User does not belong to the specified organization" });
const sharedSecret = isUuidV4(sharedSecretId)
@@ -14,6 +14,7 @@ import { logger } from "@app/lib/logger";
import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-token-queue";
import { ActorType } from "../auth/auth-type";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
@@ -29,6 +30,7 @@ import {
type TServiceTokenServiceFactoryDep = {
serviceTokenDAL: TServiceTokenDALFactory;
userDAL: TUserDALFactory;
orgDAL: Pick<TOrgDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findBySlugs">;
projectDAL: Pick<TProjectDALFactory, "findById">;
@@ -45,7 +47,8 @@ export const serviceTokenServiceFactory = ({
projectEnvDAL,
projectDAL,
accessTokenQueue,
smtpService
smtpService,
orgDAL
}: TServiceTokenServiceFactoryDep) => {
const createServiceToken = async ({
iv,
@@ -184,7 +187,15 @@ export const serviceTokenServiceFactory = ({
if (!isMatch) throw new UnauthorizedError({ message: "Invalid service token" });
await accessTokenQueue.updateServiceTokenStatus(serviceToken.id);
return { ...serviceToken, lastUsed: new Date(), orgId: project.orgId };
const serviceTokenOrgDetails = await orgDAL.findById(project.orgId);
return {
...serviceToken,
lastUsed: new Date(),
orgId: project.orgId,
parentOrgId: serviceTokenOrgDetails.parentOrgId || serviceTokenOrgDetails.id,
rootOrgId: serviceTokenOrgDetails.rootOrgId || serviceTokenOrgDetails.id
};
};
const notifyExpiringTokens = async () => {
+36 -28
View File
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { InstallProvider } from "@slack/oauth";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
@@ -230,13 +231,14 @@ export const slackServiceFactory = ({
}: TGetSlackInstallUrlDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -264,13 +266,14 @@ export const slackServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
slackIntegration.orgId,
orgId: slackIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -293,13 +296,14 @@ export const slackServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TGetSlackIntegrationByOrgDTO) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -324,13 +328,14 @@ export const slackServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
slackIntegration.orgId,
orgId: slackIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -351,13 +356,14 @@ export const slackServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
slackIntegration.orgId,
orgId: slackIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -389,13 +395,14 @@ export const slackServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
slackIntegration.orgId,
orgId: slackIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -432,13 +439,14 @@ export const slackServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
slackIntegration.orgId,
orgId: slackIntegration.orgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
@@ -0,0 +1,50 @@
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SubOrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
callback_url: string;
subOrganizationName: string;
}
export const SubOrganizationInvitationTemplate = ({
callback_url,
subOrganizationName,
siteUrl
}: SubOrganizationInvitationTemplateProps) => {
return (
<BaseEmailWrapper
title="Sub-Organization Invitation"
preview="You've been invited to join a sub-organization on Infisical."
siteUrl={siteUrl}
>
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
You've been invited to join a sub-organization on Infisical
</Heading>
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
You've been invited to join the sub-organization <strong>{subOrganizationName}</strong>.
</Text>
</Section>
<Section className="text-center">
<BaseButton href={callback_url}>Join Sub-Organization</BaseButton>
</Section>
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
<Text className="mb-[0px]">
<strong>About Infisical:</strong> Infisical is an all-in-one platform to securely manage application secrets,
certificates, SSH keys, and configurations across your team and infrastructure.
</Text>
</Section>
</BaseEmailWrapper>
);
};
export default SubOrganizationInvitationTemplate;
SubOrganizationInvitationTemplate.PreviewProps = {
subOrganizationName: "Example Project",
siteUrl: "https://infisical.com",
callback_url: "https://app.infisical.com"
} as SubOrganizationInvitationTemplateProps;
@@ -31,4 +31,5 @@ export * from "./SecretScanningSecretsDetectedTemplate";
export * from "./SecretSyncFailedTemplate";
export * from "./ServiceTokenExpiryNoticeTemplate";
export * from "./SignupEmailVerificationTemplate";
export * from "./SubOrganizationInvitationTemplate";
export * from "./UnlockAccountTemplate";
@@ -40,6 +40,7 @@ import {
SecretSyncFailedTemplate,
ServiceTokenExpiryNoticeTemplate,
SignupEmailVerificationTemplate,
SubOrganizationInvitationTemplate,
UnlockAccountTemplate
} from "./emails";
@@ -65,6 +66,7 @@ export enum SmtpTemplates {
// HistoricalSecretList = "historicalSecretLeakIncident", not used anymore?
NewDeviceJoin = "newDevice",
OrgInvite = "organizationInvitation",
SubOrgInvite = "subOrganizationInvitation",
OrgAssignment = "organizationAssignment",
OAuthPasswordReset = "oAuthPasswordReset",
ResetPassword = "passwordReset",
@@ -102,6 +104,7 @@ export enum SmtpHost {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
[SmtpTemplates.OrgInvite]: OrganizationInvitationTemplate,
[SmtpTemplates.SubOrgInvite]: SubOrganizationInvitationTemplate,
[SmtpTemplates.OrgAssignment]: OrganizationAssignmentTemplate,
[SmtpTemplates.NewDeviceJoin]: NewDeviceLoginTemplate,
[SmtpTemplates.SignupEmailVerification]: SignupEmailVerificationTemplate,
@@ -592,7 +592,7 @@ export const superAdminServiceFactory = ({
});
const { identity, credentials } = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name: "Instance Admin Identity" }, tx);
const newIdentity = await identityDAL.create({ name: "Instance Admin Identity", orgId: organization.id }, tx);
const membership = await membershipIdentityDAL.create(
{
actorIdentityId: newIdentity.id,
+6 -5
View File
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import { Knex } from "knex";
import { AccessScope } from "@app/db/schemas";
import { AccessScope, OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { crypto } from "@app/lib/crypto";
@@ -458,13 +458,14 @@ export const userServiceFactory = ({
// This makes it so the user can always read information about themselves, but no one else if they don't have the Members Read permission.
if (user.id !== actorId) {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
}
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -23,13 +24,14 @@ export const workflowIntegrationServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TGetWorkflowIntegrationsByOrg) => {
const { permission } = await permissionService.getOrgPermission(
const { permission } = await permissionService.getOrgPermission({
actor,
actorId,
actorOrgId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);