diff --git a/backend/src/services/identity-v2/identity-service.ts b/backend/src/services/identity-v2/identity-service.ts index d3c72bc29..8d161794b 100644 --- a/backend/src/services/identity-v2/identity-service.ts +++ b/backend/src/services/identity-v2/identity-service.ts @@ -230,7 +230,7 @@ export const identityV2ServiceFactory = ({ const { scopeData } = dto; const factory = scopeFactory[scopeData.scope]; - await factory.onListIdentityGuard(dto); + const isIdentityAccessible = await factory.onListIdentityGuard(dto); const identities = await identityDAL.listIdentities(dto.scopeData, { search: dto.data.search, @@ -238,7 +238,7 @@ export const identityV2ServiceFactory = ({ limit: dto.data.limit }); - return identities; + return { ...identities, docs: identities.docs.filter((el) => isIdentityAccessible({ identityId: el.id })) }; }; return { diff --git a/backend/src/services/identity-v2/identity-types.ts b/backend/src/services/identity-v2/identity-types.ts index 1fcfe2d8f..26bff3a97 100644 --- a/backend/src/services/identity-v2/identity-types.ts +++ b/backend/src/services/identity-v2/identity-types.ts @@ -5,7 +5,7 @@ export interface TIdentityV2Factory { onCreateIdentityGuard: (arg: TCreateIdentityV2DTO) => Promise; onUpdateIdentityGuard: (arg: TUpdateIdentityV2DTO) => Promise; onDeleteIdentityGuard: (arg: TDeleteIdentityV2DTO) => Promise; - onListIdentityGuard: (arg: TListIdentityV2DTO) => Promise; + onListIdentityGuard: (arg: TListIdentityV2DTO) => Promise<(arg: { identityId: string }) => boolean>; onGetIdentityByIdGuard: (arg: TGetIdentityByIdV2DTO) => Promise; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; } diff --git a/backend/src/services/identity-v2/org/org-identity-factory.ts b/backend/src/services/identity-v2/org/org-identity-factory.ts index 770c0ae7d..264bdca82 100644 --- a/backend/src/services/identity-v2/org/org-identity-factory.ts +++ b/backend/src/services/identity-v2/org/org-identity-factory.ts @@ -65,6 +65,8 @@ export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactory scope: OrganizationActionScope.Any }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + + return () => true; }; const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => { diff --git a/backend/src/services/identity-v2/project/project-identity-factory.ts b/backend/src/services/identity-v2/project/project-identity-factory.ts index a87f80d2b..4d242fe02 100644 --- a/backend/src/services/identity-v2/project/project-identity-factory.ts +++ b/backend/src/services/identity-v2/project/project-identity-factory.ts @@ -81,6 +81,12 @@ export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentit ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity ); + + return (arg) => + permission.can( + ProjectPermissionIdentityActions.Read, + subject(ProjectPermissionSub.Identity, { identityId: arg.identityId }) + ); }; const onGetIdentityByIdGuard: TIdentityV2Factory["onGetIdentityByIdGuard"] = async (dto) => { diff --git a/backend/src/services/membership-identity/membership-identity-types.ts b/backend/src/services/membership-identity/membership-identity-types.ts index 78923cb14..365a83db4 100644 --- a/backend/src/services/membership-identity/membership-identity-types.ts +++ b/backend/src/services/membership-identity/membership-identity-types.ts @@ -6,7 +6,7 @@ export interface TMembershipIdentityScopeFactory { onUpdateMembershipIdentityGuard: (arg: TUpdateMembershipIdentityDTO) => Promise; onDeleteMembershipIdentityGuard: (arg: TDeleteMembershipIdentityDTO) => Promise; - onListMembershipIdentityGuard: (arg: TListMembershipIdentityDTO) => Promise; + onListMembershipIdentityGuard: (arg: TListMembershipIdentityDTO) => Promise<(arg: { identityId: string }) => boolean>; onGetMembershipIdentityByIdentityIdGuard: (arg: TGetMembershipIdentityByIdentityIdDTO) => Promise; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; getScopeDatabaseFields: (scope: AccessScopeData) => { diff --git a/backend/src/services/membership-identity/org/org-membership-identity-factory.ts b/backend/src/services/membership-identity/org/org-membership-identity-factory.ts index 39bb32f8b..8a95a55f4 100644 --- a/backend/src/services/membership-identity/org/org-membership-identity-factory.ts +++ b/backend/src/services/membership-identity/org/org-membership-identity-factory.ts @@ -180,6 +180,8 @@ export const newOrgMembershipIdentityFactory = ({ scope: OrganizationActionScope.Any }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + + return () => true; }; const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] = diff --git a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts index 9be970f14..82cfd0777 100644 --- a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts +++ b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts @@ -194,6 +194,12 @@ export const newProjectMembershipIdentityFactory = ({ ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity ); + + return (arg) => + permission.can( + ProjectPermissionIdentityActions.Read, + subject(ProjectPermissionSub.Identity, { identityId: arg.identityId }) + ); }; const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] = diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index 9d590bb3a..b7860d584 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -423,7 +423,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { isDisabled={!isAllowed} icon={} > - Delete Identity + {orgId !== currentOrg.id + ? "Remove From Sub-organization" + : "Delete Identity"} )} diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index ad5f1839c..62f21ffa4 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -7,9 +7,14 @@ import { Link, useNavigate, useParams } from "@tanstack/react-router"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; -import { DeleteActionModal, Modal, ModalContent, PageHeader } from "@app/components/v2"; +import { Button, DeleteActionModal, Modal, ModalContent, PageHeader } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { + OrgPermissionActions, + OrgPermissionIdentityActions, + OrgPermissionSubjects, + useOrganization +} from "@app/context"; import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal"; @@ -32,7 +37,7 @@ const Page = () => { const { currentOrg, isSubOrganization } = useOrganization(); const orgId = currentOrg?.id || ""; const { data } = useGetOrgIdentityMembershipById(identityId); - const { mutateAsync: deleteIdentity } = useDeleteOrgIdentity(); + const { mutateAsync: deleteIdentity, isPending: isDeletingIdentity } = useDeleteOrgIdentity(); const isAuthHidden = orgId !== data?.identity?.orgId; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -81,7 +86,36 @@ const Page = () => { scope={isSubOrganization ? "namespace" : "org"} description={`${isSubOrganization ? "Sub-" : ""}Organization Identity`} title={data.identity.name} - /> + > +
+ {isSubOrganization && data.identity.orgId !== currentOrg.id && ( + + {(isAllowed) => ( + + )} + + )} +
+
- Edit Identity + {isOrgIdentity ? "Edit Identity" : "Edit Identity Role"} )} @@ -110,7 +110,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent icon={} disabled={!isAllowed} > - Delete Identity + {!isOrgIdentity ? "Unlink Identity" : "Delete Identity"} )}