mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 01:12:59 +00:00
feat: added root org identity link functionality
This commit is contained in:
@@ -236,7 +236,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
|
||||
}
|
||||
orgId = subOrganization.id;
|
||||
rootOrgId = token.organizationId;
|
||||
parentOrgId = subOrganization.parentOrgId;
|
||||
parentOrgId = subOrganization.parentOrgId as string;
|
||||
} else {
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
|
||||
@@ -216,11 +216,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);
|
||||
@@ -282,7 +283,6 @@ export const identityServiceFactory = ({
|
||||
});
|
||||
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>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -291,13 +291,37 @@ export const membershipUserDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// const listAvailableUsers = async (scopeData: AccessScopeData) => {
|
||||
// try {
|
||||
// const query = await db.replicaNode()(TableName.Membership).where(`${TableName.Membership}.scopeOrgId`);
|
||||
// } catch (error) {
|
||||
// throw new DatabaseError({ error, name: "ListAvailableUsers" });
|
||||
// }
|
||||
// };
|
||||
// this right nwo 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");
|
||||
|
||||
return { ...orm, findUsers, getUserById };
|
||||
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)
|
||||
.whereNot(`${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 };
|
||||
};
|
||||
|
||||
@@ -83,7 +83,8 @@ export const membershipUserServiceFactory = ({
|
||||
orgDAL,
|
||||
tokenService,
|
||||
userDAL,
|
||||
userGroupMembershipDAL
|
||||
userGroupMembershipDAL,
|
||||
membershipUserDAL
|
||||
}),
|
||||
[AccessScope.Namespace]: newNamespaceMembershipUserFactory({}),
|
||||
[AccessScope.Project]: newProjectMembershipUserFactory({
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TMembershipUserScopeFactory } from "../membership-user-types";
|
||||
import { TMembershipUserDALFactory } from "../membership-user-dal";
|
||||
|
||||
type TOrgMembershipUserScopeFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
@@ -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,7 +54,10 @@ export const newOrgMembershipUserFactory = ({
|
||||
|
||||
const isCustomRole: TMembershipUserScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
|
||||
|
||||
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (dto) => {
|
||||
const onCreateMembershipUserGuard: TMembershipUserScopeFactory["onCreateMembershipUserGuard"] = async (
|
||||
dto,
|
||||
newMembers
|
||||
) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
@@ -78,6 +84,19 @@ 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)
|
||||
throw new BadRequestError({
|
||||
message: "User doesn't have membership in root organization"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onCreateMembershipComplete: TMembershipUserScopeFactory["onCreateMembershipComplete"] = async (
|
||||
|
||||
Reference in New Issue
Block a user