mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 02:25:07 +00:00
feat: added root org identity link functionality
This commit is contained in:
@@ -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 nwo 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: "ListAvailableUsers" });
|
||||
}
|
||||
};
|
||||
|
||||
return { ...orm, findIdentities, getIdentityById, listAvailableIdentities };
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { newNamespaceMembershipIdentityFactory } from "./namespace/namespace-membership-identity-factory";
|
||||
import { newOrgMembershipIdentityFactory } from "./org/org-membership-identity-factory";
|
||||
import { newProjectMembershipIdentityFactory } from "./project/project-membership-identity-factory";
|
||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||
|
||||
type TMembershipIdentityServiceFactoryDep = {
|
||||
membershipIdentityDAL: TMembershipIdentityDALFactory;
|
||||
@@ -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[];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -12,15 +12,18 @@ import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||
|
||||
import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
|
||||
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
||||
|
||||
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,12 +41,53 @@ 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 identites 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
|
||||
@@ -87,12 +131,29 @@ 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.Create, OrgPermissionSubjects.Identity);
|
||||
|
||||
const identityDetails = await identityDAL.findById(dto.selector.identityId);
|
||||
if (identityDetails.orgId !== dto.permission.rootOrgId) {
|
||||
throw new BadRequestError({ message: "Only identites 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
|
||||
|
||||
Reference in New Issue
Block a user