diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index abe8c3d2e..13b862343 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -408,7 +408,7 @@ export const samlConfigServiceFactory = ({ }); } } else if (dto.type === "orgSlug") { - const org = await orgDAL.findOne({ slug: dto.orgSlug }); + const org = await orgDAL.findOne({ slug: dto.orgSlug, rootOrgId: null }); if (!org) { throw new NotFoundError({ message: `Organization with slug '${dto.orgSlug}' not found` diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index b1798692d..f8e6c78ee 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -393,7 +393,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { permissions: true, description: true }).optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({ + identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({ authMethods: z.array(z.string()) }) }).array(), diff --git a/backend/src/services/identity-project/identity-project-dal.ts b/backend/src/services/identity-project/identity-project-dal.ts index 3dba6210d..adcdd8be8 100644 --- a/backend/src/services/identity-project/identity-project-dal.ts +++ b/backend/src/services/identity-project/identity-project-dal.ts @@ -25,11 +25,12 @@ import { buildAuthMethods } from "../identity/identity-fns"; export type TIdentityProjectDALFactory = ReturnType; 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`) diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 04c384843..898040458 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -519,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), @@ -570,6 +571,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { crPermission, crName, identityId, + identityOrgId, identityName, hasDeleteProtection, role, @@ -615,6 +617,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { id: identityId as string, name: identityName, hasDeleteProtection, + orgId: identityOrgId, authMethods: buildAuthMethods({ uaId, alicloudId, diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index f2caeb053..f6ec60e9e 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -477,7 +477,7 @@ export const identityServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); - const identityMemberships = await identityProjectDAL.findByIdentityId(identityId); + const identityMemberships = await identityProjectDAL.findByIdentityId(identityId, actorOrgId); return identityMemberships; }; diff --git a/frontend/src/context/OrganizationContext/OrganizationContext.tsx b/frontend/src/context/OrganizationContext/OrganizationContext.tsx index 5a18ec741..79c004e1f 100644 --- a/frontend/src/context/OrganizationContext/OrganizationContext.tsx +++ b/frontend/src/context/OrganizationContext/OrganizationContext.tsx @@ -1,5 +1,5 @@ import { useSuspenseQuery } from "@tanstack/react-query"; -import { useRouteContext } from "@tanstack/react-router"; +import { useRouteContext, useSearch } from "@tanstack/react-router"; import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries"; @@ -9,8 +9,13 @@ export const useOrganization = () => { select: (el) => el.organizationId }); + const subOrganization = useSearch({ + strict: false, + select: (el) => el?.subOrganization + }); + const { data: currentOrg } = useSuspenseQuery({ - queryKey: organizationKeys.getOrgById(organizationId), + queryKey: organizationKeys.getOrgById(organizationId, subOrganization), queryFn: () => fetchOrganizationById(organizationId), staleTime: Infinity }); diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 36c0fd1fb..bbf73dd25 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -42,7 +42,7 @@ export const organizationKeys = { [...organizationKeys.getOrgIdentityMemberships(orgId), params] as const, getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const, getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const, - getOrgById: (orgId: string) => ["organization", { orgId }], + getOrgById: (orgId: string, subOrg?: string) => ["organization", { orgId, subOrg }], getAvailableIdentities: () => ["available-identities"], getAvailableUsers: () => ["available-users"] }; 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 b632318e6..2705593e5 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 @@ -2,6 +2,7 @@ import { useCallback, useState } from "react"; import { faArrowDown, faArrowUp, + faBuilding, faCheckCircle, faChevronRight, faEdit, @@ -78,7 +79,7 @@ type Filter = { export const IdentityTable = ({ handlePopUpOpen }: Props) => { const navigate = useNavigate(); - const { currentOrg } = useOrganization(); + const { currentOrg, isSubOrganization } = useOrganization(); const { offset, @@ -286,15 +287,18 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { + {isSubOrganization && Managed By} {isFetching ? : null} - {isPending && } + {isPending && ( + + )} {!isPending && data?.identities?.map( ({ - identity: { id, name }, + identity: { id, name, orgId }, role, customRole, lastLoginAuthMethod, @@ -362,6 +366,14 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { }} + {isSubOrganization && ( + +

+ + {currentOrg.id === orgId ? "Organization" : "Root Organization"} +

+ + )} diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 0a853070b..98668d6ca 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -91,7 +91,11 @@ const Page = () => {
- + {!isAuthHidden && ( ({ initialState: "Copy ID to clipboard" }); + const { isSubOrganization } = useOrganization(); const { data } = useGetIdentityById(identityId); return data ? ( @@ -142,6 +143,14 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent

Name

{data.identity.name}

+ {isSubOrganization && ( +
+

Manage By

+

+ {isOrgIdentity ? "Organization" : "Root Organization"} +

+
+ )} {isOrgIdentity && (

Last Login Auth Method

diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index 54627888b..109c02fbc 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -22,6 +22,7 @@ import { import { useTimedReset } from "@app/hooks"; import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api"; import { SecretSharingAccessType } from "@app/hooks/api/secretSharing"; +import { useSearch } from "@tanstack/react-router"; // values in ms const expiresInOptions = [ @@ -87,6 +88,10 @@ export const ShareSecretForm = ({ const [, isCopyingSecret, setCopyTextSecret] = useTimedReset({ initialState: "Copy to clipboard" }); + const subOrganization = useSearch({ + strict: false, + select: (el) => el?.subOrganization + }); const publicSharedSecretCreator = useCreatePublicSharedSecret(); const privateSharedSecretCreator = useCreateSharedSecret(); @@ -148,11 +153,14 @@ export const ShareSecretForm = ({ type: "success" }); } else { - const link = `${window.location.origin}/shared/secret/${id}`; + const link = new URL(`${window.location.origin}/shared/secret/${id}`); + if (subOrganization) { + link.searchParams.set("subOrganization", subOrganization); + } - setSecretLink(link); + setSecretLink(link.toString()); - navigator.clipboard.writeText(link); + navigator.clipboard.writeText(link.toString()); setCopyTextSecret("secret"); createNotification({