mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-02-02 00:24:16 +00:00
Added hooks to evaluate reader role access
This commit is contained in:
@@ -10,15 +10,34 @@ export interface ArmEntity {
|
|||||||
resourceGroup?: string;
|
resourceGroup?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseAccountUserAssignedIdentity {
|
||||||
|
[key: string]: {
|
||||||
|
principalId: string;
|
||||||
|
clientId: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseAccountIdentity {
|
||||||
|
type: string;
|
||||||
|
principalId?: string;
|
||||||
|
tenantId?: string;
|
||||||
|
userAssignedIdentities?: DatabaseAccountUserAssignedIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DatabaseAccount extends ArmEntity {
|
export interface DatabaseAccount extends ArmEntity {
|
||||||
properties: DatabaseAccountExtendedProperties;
|
properties: DatabaseAccountExtendedProperties;
|
||||||
systemData?: DatabaseAccountSystemData;
|
systemData?: DatabaseAccountSystemData;
|
||||||
|
identity?: DatabaseAccountIdentity | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountSystemData {
|
export interface DatabaseAccountSystemData {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseAccountBackupPolicy {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountExtendedProperties {
|
export interface DatabaseAccountExtendedProperties {
|
||||||
documentEndpoint?: string;
|
documentEndpoint?: string;
|
||||||
disableLocalAuth?: boolean;
|
disableLocalAuth?: boolean;
|
||||||
@@ -29,6 +48,8 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
capabilities?: Capability[];
|
capabilities?: Capability[];
|
||||||
enableMultipleWriteLocations?: boolean;
|
enableMultipleWriteLocations?: boolean;
|
||||||
mongoEndpoint?: string;
|
mongoEndpoint?: string;
|
||||||
|
backupPolicy?: DatabaseAccountBackupPolicy;
|
||||||
|
defaultIdentity?: string;
|
||||||
readLocations?: DatabaseAccountResponseLocation[];
|
readLocations?: DatabaseAccountResponseLocation[];
|
||||||
writeLocations?: DatabaseAccountResponseLocation[];
|
writeLocations?: DatabaseAccountResponseLocation[];
|
||||||
enableFreeTier?: boolean;
|
enableFreeTier?: boolean;
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
|||||||
|
|
||||||
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
|
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { isLoggedIn, armToken } = useAADAuth(config);
|
const { isLoggedIn, armToken, account } = useAADAuth(config);
|
||||||
|
const principalId = account?.localAccountId ?? "";
|
||||||
|
|
||||||
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
|
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
|
||||||
const [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
|
const [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CopyJobContext.Provider value={{ armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
<CopyJobContext.Provider value={{ principalId, armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</CopyJobContext.Provider>
|
</CopyJobContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
import { useRoleAssignments } from "hooks/useRoleAssignments";
|
||||||
|
import { RoleDefinitionType, useRoleDefinitions } from "hooks/useRoleDefinition";
|
||||||
|
import { useMemo } from "react";
|
||||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||||
import { CopyJobMigrationType } from "../../../../Enums";
|
import {
|
||||||
|
BackupPolicyType,
|
||||||
|
CopyJobMigrationType,
|
||||||
|
DefaultIdentityType,
|
||||||
|
IdentityType
|
||||||
|
} from "../../../../Enums";
|
||||||
import { CopyJobContextState } from "../../../../Types";
|
import { CopyJobContextState } from "../../../../Types";
|
||||||
import AddManagedIdentity from "../AddManagedIdentity";
|
import AddManagedIdentity from "../AddManagedIdentity";
|
||||||
import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
|
import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
|
||||||
@@ -7,28 +15,36 @@ import DefaultManagedIdentity from "../DefaultManagedIdentity";
|
|||||||
import OnlineCopyEnabled from "../OnlineCopyEnabled";
|
import OnlineCopyEnabled from "../OnlineCopyEnabled";
|
||||||
import PointInTimeRestore from "../PointInTimeRestore";
|
import PointInTimeRestore from "../PointInTimeRestore";
|
||||||
|
|
||||||
// Define a typed config for permission sections
|
|
||||||
export interface PermissionSectionConfig {
|
export interface PermissionSectionConfig {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
Component: React.FC;
|
Component: React.FC;
|
||||||
shouldShow?: (state: CopyJobContextState) => boolean; // optional conditional rendering
|
disabled?: boolean;
|
||||||
|
completed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base permission sections with dynamic visibility logic
|
// Section IDs for maintainability
|
||||||
|
const SECTION_IDS = {
|
||||||
|
addManagedIdentity: "addManagedIdentity",
|
||||||
|
defaultManagedIdentity: "defaultManagedIdentity",
|
||||||
|
readPermissionAssigned: "readPermissionAssigned",
|
||||||
|
pointInTimeRestore: "pointInTimeRestore",
|
||||||
|
onlineCopyEnabled: "onlineCopyEnabled"
|
||||||
|
} as const;
|
||||||
|
|
||||||
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||||
{
|
{
|
||||||
id: "addManagedIdentity",
|
id: SECTION_IDS.addManagedIdentity,
|
||||||
title: ContainerCopyMessages.addManagedIdentity.title,
|
title: ContainerCopyMessages.addManagedIdentity.title,
|
||||||
Component: AddManagedIdentity,
|
Component: AddManagedIdentity,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "defaultManagedIdentity",
|
id: SECTION_IDS.defaultManagedIdentity,
|
||||||
title: ContainerCopyMessages.defaultManagedIdentity.title,
|
title: ContainerCopyMessages.defaultManagedIdentity.title,
|
||||||
Component: DefaultManagedIdentity,
|
Component: DefaultManagedIdentity,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "readPermissionAssigned",
|
id: SECTION_IDS.readPermissionAssigned,
|
||||||
title: ContainerCopyMessages.readPermissionAssigned.title,
|
title: ContainerCopyMessages.readPermissionAssigned.title,
|
||||||
Component: AddReadPermissionToDefaultIdentity,
|
Component: AddReadPermissionToDefaultIdentity,
|
||||||
}
|
}
|
||||||
@@ -36,22 +52,139 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
|||||||
|
|
||||||
const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
||||||
{
|
{
|
||||||
id: "pointInTimeRestore",
|
id: SECTION_IDS.pointInTimeRestore,
|
||||||
title: ContainerCopyMessages.pointInTimeRestore.title,
|
title: ContainerCopyMessages.pointInTimeRestore.title,
|
||||||
Component: PointInTimeRestore,
|
Component: PointInTimeRestore,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "onlineCopyEnabled",
|
id: SECTION_IDS.onlineCopyEnabled,
|
||||||
title: ContainerCopyMessages.onlineCopyEnabled.title,
|
title: ContainerCopyMessages.onlineCopyEnabled.title,
|
||||||
Component: OnlineCopyEnabled,
|
Component: OnlineCopyEnabled,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => {
|
|
||||||
return [
|
/**
|
||||||
...PERMISSION_SECTIONS_CONFIG,
|
* Checks if the user has the Reader role based on role definitions.
|
||||||
...(state.migrationType !== CopyJobMigrationType.Offline ? PERMISSION_SECTIONS_FOR_ONLINE_JOBS : []),
|
*/
|
||||||
];
|
export function checkUserHasReaderRole(roleDefinitions: RoleDefinitionType[]): boolean {
|
||||||
|
return roleDefinitions?.some(
|
||||||
|
role =>
|
||||||
|
role.name === "00000000-0000-0000-0000-000000000001" ||
|
||||||
|
role.permissions.some(
|
||||||
|
permission =>
|
||||||
|
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") &&
|
||||||
|
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the permission sections configuration for the Assign Permissions screen.
|
||||||
|
* Memoizes derived values for performance and decouples logic for testability.
|
||||||
|
*/
|
||||||
|
const usePermissionSections = (
|
||||||
|
state: CopyJobContextState,
|
||||||
|
armToken: string,
|
||||||
|
principalId: string
|
||||||
|
): PermissionSectionConfig[] => {
|
||||||
|
const { source, target } = state;
|
||||||
|
|
||||||
|
// Memoize identity types and backup policy
|
||||||
|
const targetAccountIdentityType = useMemo(
|
||||||
|
() => (target?.account?.identity?.type ?? "").toLowerCase(),
|
||||||
|
[target?.account?.identity?.type]
|
||||||
|
);
|
||||||
|
const targetAccountDefaultIdentityType = useMemo(
|
||||||
|
() => (target?.account?.properties?.defaultIdentity ?? "").toLowerCase(),
|
||||||
|
[target?.account?.properties?.defaultIdentity]
|
||||||
|
);
|
||||||
|
const sourceAccountBackupPolicy = useMemo(
|
||||||
|
() => source?.account?.properties?.backupPolicy?.type ?? "",
|
||||||
|
[source?.account?.properties?.backupPolicy?.type]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch role assignments and definitions
|
||||||
|
const roleAssigned = useRoleAssignments(
|
||||||
|
armToken,
|
||||||
|
source?.subscription?.subscriptionId,
|
||||||
|
source?.account?.resourceGroup,
|
||||||
|
source?.account?.name,
|
||||||
|
principalId
|
||||||
|
);
|
||||||
|
|
||||||
|
const roleDefinitions = useRoleDefinitions(
|
||||||
|
armToken,
|
||||||
|
roleAssigned ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasReaderRole = useMemo(
|
||||||
|
() => checkUserHasReaderRole(roleDefinitions ?? []),
|
||||||
|
[roleDefinitions]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decouple section state logic for testability
|
||||||
|
const getBaseSections = useMemo(() => {
|
||||||
|
return PERMISSION_SECTIONS_CONFIG.map(section => {
|
||||||
|
if (
|
||||||
|
section.id === SECTION_IDS.addManagedIdentity &&
|
||||||
|
(targetAccountIdentityType === IdentityType.SystemAssigned ||
|
||||||
|
targetAccountIdentityType === IdentityType.UserAssigned)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
disabled: true,
|
||||||
|
completed: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
section.id === SECTION_IDS.defaultManagedIdentity &&
|
||||||
|
targetAccountDefaultIdentityType === DefaultIdentityType.SystemAssignedIdentity
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
disabled: true,
|
||||||
|
completed: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
section.id === SECTION_IDS.readPermissionAssigned &&
|
||||||
|
hasReaderRole
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
disabled: true,
|
||||||
|
completed: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
});
|
||||||
|
}, [targetAccountIdentityType, targetAccountDefaultIdentityType, hasReaderRole]);
|
||||||
|
|
||||||
|
const getOnlineSections = useMemo(() => {
|
||||||
|
if (state.migrationType !== CopyJobMigrationType.Online) return [];
|
||||||
|
return PERMISSION_SECTIONS_FOR_ONLINE_JOBS.map(section => {
|
||||||
|
if (
|
||||||
|
section.id === SECTION_IDS.pointInTimeRestore &&
|
||||||
|
sourceAccountBackupPolicy === BackupPolicyType.Continuous
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
disabled: true,
|
||||||
|
completed: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
});
|
||||||
|
}, [state.migrationType, sourceAccountBackupPolicy]);
|
||||||
|
|
||||||
|
// Combine and memoize final sections
|
||||||
|
const permissionSections = useMemo(
|
||||||
|
() => [...getBaseSections, ...getOnlineSections],
|
||||||
|
[getBaseSections, getOnlineSections]
|
||||||
|
);
|
||||||
|
|
||||||
|
return permissionSections;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePermissionSections;
|
export default usePermissionSections;
|
||||||
@@ -1,26 +1,39 @@
|
|||||||
import { Image, Stack, Text } from "@fluentui/react";
|
import { Image, Stack, Text } from "@fluentui/react";
|
||||||
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components";
|
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import CheckmarkIcon from "../../../../../../images/successfulPopup.svg";
|
||||||
import WarningIcon from "../../../../../../images/warning.svg";
|
import WarningIcon from "../../../../../../images/warning.svg";
|
||||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||||
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||||
|
|
||||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component }) => (
|
const PermissionSection: React.FC<PermissionSectionConfig> = ({
|
||||||
<AccordionItem key={id} value={id}>
|
id,
|
||||||
|
title,
|
||||||
|
Component,
|
||||||
|
completed,
|
||||||
|
disabled
|
||||||
|
}) => (
|
||||||
|
<AccordionItem key={id} value={id} disabled={disabled}>
|
||||||
<AccordionHeader className="accordionHeader">
|
<AccordionHeader className="accordionHeader">
|
||||||
<Text className="accordionHeaderText" variant="medium">{title}</Text>
|
<Text className="accordionHeaderText" variant="medium">{title}</Text>
|
||||||
<Image className="statusIcon" src={WarningIcon} alt="Warning icon" width={24} height={24} />
|
<Image
|
||||||
|
className="statusIcon"
|
||||||
|
src={completed ? CheckmarkIcon : WarningIcon}
|
||||||
|
alt={completed ? "Checkmark icon" : "Warning icon"}
|
||||||
|
width={completed ? 20 : 24}
|
||||||
|
height={completed ? 20 : 24}
|
||||||
|
/>
|
||||||
</AccordionHeader>
|
</AccordionHeader>
|
||||||
<AccordionPanel>
|
<AccordionPanel aria-disabled={disabled} className="accordionPanel" >
|
||||||
<Component />
|
<Component />
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AssignPermissions = () => {
|
const AssignPermissions = () => {
|
||||||
const { copyJobState } = useCopyJobContext();
|
const { armToken, principalId, copyJobState } = useCopyJobContext();
|
||||||
const permissionSections = usePermissionSections(copyJobState);
|
const permissionSections = usePermissionSections(copyJobState, armToken, principalId);
|
||||||
return (
|
return (
|
||||||
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
|
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -3,6 +3,22 @@ export enum CopyJobMigrationType {
|
|||||||
Online = "online",
|
Online = "online",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all checks will happen
|
||||||
|
export enum IdentityType {
|
||||||
|
SystemAssigned = "systemassigned", // "SystemAssigned"
|
||||||
|
UserAssigned = "userassigned", // "UserAssigned"
|
||||||
|
None = "none", // "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DefaultIdentityType {
|
||||||
|
SystemAssignedIdentity = "systemassignedidentity", // "SystemAssignedIdentity"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BackupPolicyType {
|
||||||
|
Continuous = "Continuous",
|
||||||
|
Periodic = "Periodic",
|
||||||
|
}
|
||||||
|
|
||||||
export enum CopyJobMigrationStatus {
|
export enum CopyJobMigrationStatus {
|
||||||
Pause = "Pause",
|
Pause = "Pause",
|
||||||
Resume = "Resume",
|
Resume = "Resume",
|
||||||
|
|||||||
@@ -27,18 +27,6 @@ export type DropdownOptionType = {
|
|||||||
data: any
|
data: any
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FetchDatabasesListParams = {
|
|
||||||
armToken: string;
|
|
||||||
subscriptionId: string;
|
|
||||||
resourceGroupName: string;
|
|
||||||
accountName: string;
|
|
||||||
apiType?: ApiType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface FetchDataContainersListParams extends FetchDatabasesListParams {
|
|
||||||
databaseName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DatabaseParams = [
|
export type DatabaseParams = [
|
||||||
string,
|
string,
|
||||||
string | undefined,
|
string | undefined,
|
||||||
@@ -91,6 +79,7 @@ export interface CopyJobFlowType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CopyJobContextProviderType {
|
export interface CopyJobContextProviderType {
|
||||||
|
principalId: string;
|
||||||
armToken: string;
|
armToken: string;
|
||||||
flow: CopyJobFlowType;
|
flow: CopyJobFlowType;
|
||||||
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
|
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ import { DatabaseModel } from "Contracts/DataModels";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { getCollectionEndpoint, getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
import { getCollectionEndpoint, getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { FetchDataContainersListParams } from "../Explorer/ContainerCopy/Types";
|
|
||||||
import { ApiType } from "../UserContext";
|
import { ApiType } from "../UserContext";
|
||||||
|
|
||||||
const apiVersion = "2023-09-15";
|
const apiVersion = "2023-09-15";
|
||||||
|
export interface FetchDataContainersListParams {
|
||||||
|
armToken: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
resourceGroupName: string;
|
||||||
|
databaseName: string;
|
||||||
|
accountName: string;
|
||||||
|
apiType?: ApiType;
|
||||||
|
}
|
||||||
|
|
||||||
const buildReadDataContainersListUrl = (params: FetchDataContainersListParams): string => {
|
const buildReadDataContainersListUrl = (params: FetchDataContainersListParams): string => {
|
||||||
const { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params;
|
const { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params;
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ import { DatabaseModel } from "Contracts/DataModels";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
import { getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import { FetchDatabasesListParams } from "../Explorer/ContainerCopy/Types";
|
|
||||||
import { ApiType } from "../UserContext";
|
import { ApiType } from "../UserContext";
|
||||||
|
|
||||||
const apiVersion = "2023-09-15";
|
const apiVersion = "2023-09-15";
|
||||||
|
export interface FetchDatabasesListParams {
|
||||||
|
armToken: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
resourceGroupName: string;
|
||||||
|
accountName: string;
|
||||||
|
apiType?: ApiType;
|
||||||
|
}
|
||||||
|
|
||||||
const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string => {
|
const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string => {
|
||||||
const { subscriptionId, resourceGroupName, accountName, apiType } = params;
|
const { subscriptionId, resourceGroupName, accountName, apiType } = params;
|
||||||
const databaseEndpoint = getDatabaseEndpoint(apiType);
|
const databaseEndpoint = getDatabaseEndpoint(apiType);
|
||||||
|
|||||||
93
src/hooks/useRoleAssignments.tsx
Normal file
93
src/hooks/useRoleAssignments.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
|
|
||||||
|
const apiVersion = "2025-04-15";
|
||||||
|
|
||||||
|
export type FetchAccountDetailsParams = {
|
||||||
|
armToken: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
resourceGroupName: string;
|
||||||
|
accountName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoleAssignmentPropertiesType = {
|
||||||
|
roleDefinitionId: string;
|
||||||
|
principalId: string;
|
||||||
|
scope: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoleAssignmentType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
properties: RoleAssignmentPropertiesType;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildRoleAssignmentsListUrl = (params: FetchAccountDetailsParams): string => {
|
||||||
|
const { subscriptionId, resourceGroupName, accountName } = params;
|
||||||
|
|
||||||
|
let armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
|
if (armEndpoint.endsWith("/")) {
|
||||||
|
armEndpoint = armEndpoint.slice(0, -1);
|
||||||
|
}
|
||||||
|
return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlRoleAssignments?api-version=${apiVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchRoleAssignments = async (
|
||||||
|
armToken: string,
|
||||||
|
subscriptionId: string,
|
||||||
|
resourceGroupName: string,
|
||||||
|
accountName: string,
|
||||||
|
principalId: string
|
||||||
|
): Promise<RoleAssignmentType[]> => {
|
||||||
|
const uri = buildRoleAssignmentsListUrl({
|
||||||
|
armToken,
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroupName,
|
||||||
|
accountName
|
||||||
|
});
|
||||||
|
const headers = new Headers();
|
||||||
|
const bearer = `Bearer ${armToken}`;
|
||||||
|
headers.append("Authorization", bearer);
|
||||||
|
headers.append("Content-Type", "application/json");
|
||||||
|
|
||||||
|
const response = await fetch(uri, {
|
||||||
|
method: "GET",
|
||||||
|
headers: headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch containers");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const assignments = data.value;
|
||||||
|
const rolesAssignedToLoggedinUser = assignments.filter((assignment: RoleAssignmentType) => assignment?.properties?.principalId === principalId);
|
||||||
|
return rolesAssignedToLoggedinUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useRoleAssignments(
|
||||||
|
armToken: string,
|
||||||
|
subscriptionId: string,
|
||||||
|
resourceGroupName: string,
|
||||||
|
accountName: string,
|
||||||
|
principalId: string
|
||||||
|
): RoleAssignmentType[] | undefined {
|
||||||
|
const { data } = useSWR(
|
||||||
|
() => (
|
||||||
|
armToken && subscriptionId && resourceGroupName && accountName && principalId ? [
|
||||||
|
"fetchRoleAssignmentsLinkedToAccount",
|
||||||
|
armToken, subscriptionId, resourceGroupName, accountName, principalId
|
||||||
|
] : undefined
|
||||||
|
),
|
||||||
|
(_, armToken, subscriptionId, resourceGroupName, accountName, principalId) => fetchRoleAssignments(
|
||||||
|
armToken,
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroupName,
|
||||||
|
accountName,
|
||||||
|
principalId
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
68
src/hooks/useRoleDefinition.tsx
Normal file
68
src/hooks/useRoleDefinition.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
|
import { RoleAssignmentType } from "./useRoleAssignments";
|
||||||
|
|
||||||
|
type RoleDefinitionDataActions = {
|
||||||
|
dataActions: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoleDefinitionType = {
|
||||||
|
assignableScopes: string[];
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
permissions: RoleDefinitionDataActions[];
|
||||||
|
resourceGroup: string;
|
||||||
|
roleName: string;
|
||||||
|
type: string;
|
||||||
|
typePropertiesType: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiVersion = "2025-04-15";
|
||||||
|
const buildRoleDefinitionUrl = (roleDefinitionId: string): string => {
|
||||||
|
let armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
|
if (armEndpoint.endsWith("/")) {
|
||||||
|
armEndpoint = armEndpoint.slice(0, -1);
|
||||||
|
}
|
||||||
|
return `${armEndpoint}${roleDefinitionId}?api-version=${apiVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchRoleDefinitions = async (
|
||||||
|
armToken: string,
|
||||||
|
roleAssignments: RoleAssignmentType[],
|
||||||
|
): Promise<RoleDefinitionType[]> => {
|
||||||
|
const roleDefinitionIds = roleAssignments.map(assignment => assignment.properties.roleDefinitionId);
|
||||||
|
const uniqueRoleDefinitionIds = Array.from(new Set(roleDefinitionIds));
|
||||||
|
|
||||||
|
const roleDefinitionUris = uniqueRoleDefinitionIds.map(roleDefinitionId => buildRoleDefinitionUrl(roleDefinitionId));
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${armToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
|
const promises = roleDefinitionUris.map(uri => fetch(uri, { method: "GET", headers }));
|
||||||
|
const responses = await Promise.all(promises);
|
||||||
|
for (const response of responses) {
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch role definitions");
|
||||||
|
}
|
||||||
|
const roleDefinitions = await Promise.all(responses.map(r => r.json()));
|
||||||
|
return roleDefinitions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useRoleDefinitions(
|
||||||
|
armToken: string,
|
||||||
|
roleAssignments: RoleAssignmentType[],
|
||||||
|
): RoleDefinitionType[] | undefined {
|
||||||
|
const { data } = useSWR(
|
||||||
|
() => (
|
||||||
|
armToken && roleAssignments?.length ? [
|
||||||
|
"fetchRoleDefinitionsForTheAssignments",
|
||||||
|
armToken, roleAssignments
|
||||||
|
] : undefined
|
||||||
|
),
|
||||||
|
(_, armToken, roleAssignments) => fetchRoleDefinitions(
|
||||||
|
armToken,
|
||||||
|
roleAssignments
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user