mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-27 12:51:41 +00:00
Added hooks to evaluate reader role access
This commit is contained in:
@@ -10,15 +10,34 @@ export interface ArmEntity {
|
||||
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 {
|
||||
properties: DatabaseAccountExtendedProperties;
|
||||
systemData?: DatabaseAccountSystemData;
|
||||
identity?: DatabaseAccountIdentity | null;
|
||||
}
|
||||
|
||||
export interface DatabaseAccountSystemData {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface DatabaseAccountBackupPolicy {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface DatabaseAccountExtendedProperties {
|
||||
documentEndpoint?: string;
|
||||
disableLocalAuth?: boolean;
|
||||
@@ -29,6 +48,8 @@ export interface DatabaseAccountExtendedProperties {
|
||||
capabilities?: Capability[];
|
||||
enableMultipleWriteLocations?: boolean;
|
||||
mongoEndpoint?: string;
|
||||
backupPolicy?: DatabaseAccountBackupPolicy;
|
||||
defaultIdentity?: string;
|
||||
readLocations?: DatabaseAccountResponseLocation[];
|
||||
writeLocations?: DatabaseAccountResponseLocation[];
|
||||
enableFreeTier?: boolean;
|
||||
|
||||
@@ -38,7 +38,9 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
||||
|
||||
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
|
||||
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 [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
|
||||
|
||||
@@ -52,7 +54,7 @@ const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) =>
|
||||
}
|
||||
|
||||
return (
|
||||
<CopyJobContext.Provider value={{ armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
||||
<CopyJobContext.Provider value={{ principalId, armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
||||
{props.children}
|
||||
</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 { CopyJobMigrationType } from "../../../../Enums";
|
||||
import {
|
||||
BackupPolicyType,
|
||||
CopyJobMigrationType,
|
||||
DefaultIdentityType,
|
||||
IdentityType
|
||||
} from "../../../../Enums";
|
||||
import { CopyJobContextState } from "../../../../Types";
|
||||
import AddManagedIdentity from "../AddManagedIdentity";
|
||||
import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
|
||||
@@ -7,28 +15,36 @@ import DefaultManagedIdentity from "../DefaultManagedIdentity";
|
||||
import OnlineCopyEnabled from "../OnlineCopyEnabled";
|
||||
import PointInTimeRestore from "../PointInTimeRestore";
|
||||
|
||||
// Define a typed config for permission sections
|
||||
export interface PermissionSectionConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
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[] = [
|
||||
{
|
||||
id: "addManagedIdentity",
|
||||
id: SECTION_IDS.addManagedIdentity,
|
||||
title: ContainerCopyMessages.addManagedIdentity.title,
|
||||
Component: AddManagedIdentity,
|
||||
},
|
||||
{
|
||||
id: "defaultManagedIdentity",
|
||||
id: SECTION_IDS.defaultManagedIdentity,
|
||||
title: ContainerCopyMessages.defaultManagedIdentity.title,
|
||||
Component: DefaultManagedIdentity,
|
||||
},
|
||||
{
|
||||
id: "readPermissionAssigned",
|
||||
id: SECTION_IDS.readPermissionAssigned,
|
||||
title: ContainerCopyMessages.readPermissionAssigned.title,
|
||||
Component: AddReadPermissionToDefaultIdentity,
|
||||
}
|
||||
@@ -36,22 +52,139 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||
|
||||
const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
||||
{
|
||||
id: "pointInTimeRestore",
|
||||
id: SECTION_IDS.pointInTimeRestore,
|
||||
title: ContainerCopyMessages.pointInTimeRestore.title,
|
||||
Component: PointInTimeRestore,
|
||||
},
|
||||
{
|
||||
id: "onlineCopyEnabled",
|
||||
id: SECTION_IDS.onlineCopyEnabled,
|
||||
title: ContainerCopyMessages.onlineCopyEnabled.title,
|
||||
Component: OnlineCopyEnabled,
|
||||
}
|
||||
];
|
||||
|
||||
const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => {
|
||||
return [
|
||||
...PERMISSION_SECTIONS_CONFIG,
|
||||
...(state.migrationType !== CopyJobMigrationType.Offline ? PERMISSION_SECTIONS_FOR_ONLINE_JOBS : []),
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks if the user has the Reader role based on role definitions.
|
||||
*/
|
||||
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;
|
||||
@@ -1,26 +1,39 @@
|
||||
import { Image, Stack, Text } from "@fluentui/react";
|
||||
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components";
|
||||
import React from "react";
|
||||
import CheckmarkIcon from "../../../../../../images/successfulPopup.svg";
|
||||
import WarningIcon from "../../../../../../images/warning.svg";
|
||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||
import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||
|
||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component }) => (
|
||||
<AccordionItem key={id} value={id}>
|
||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({
|
||||
id,
|
||||
title,
|
||||
Component,
|
||||
completed,
|
||||
disabled
|
||||
}) => (
|
||||
<AccordionItem key={id} value={id} disabled={disabled}>
|
||||
<AccordionHeader className="accordionHeader">
|
||||
<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>
|
||||
<AccordionPanel>
|
||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel" >
|
||||
<Component />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
|
||||
const AssignPermissions = () => {
|
||||
const { copyJobState } = useCopyJobContext();
|
||||
const permissionSections = usePermissionSections(copyJobState);
|
||||
const { armToken, principalId, copyJobState } = useCopyJobContext();
|
||||
const permissionSections = usePermissionSections(copyJobState, armToken, principalId);
|
||||
return (
|
||||
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 15 }}>
|
||||
<span>
|
||||
|
||||
@@ -3,6 +3,22 @@ export enum CopyJobMigrationType {
|
||||
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 {
|
||||
Pause = "Pause",
|
||||
Resume = "Resume",
|
||||
|
||||
@@ -27,18 +27,6 @@ export type DropdownOptionType = {
|
||||
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 = [
|
||||
string,
|
||||
string | undefined,
|
||||
@@ -91,6 +79,7 @@ export interface CopyJobFlowType {
|
||||
}
|
||||
|
||||
export interface CopyJobContextProviderType {
|
||||
principalId: string;
|
||||
armToken: string;
|
||||
flow: CopyJobFlowType;
|
||||
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
|
||||
|
||||
@@ -2,10 +2,17 @@ import { DatabaseModel } from "Contracts/DataModels";
|
||||
import useSWR from "swr";
|
||||
import { getCollectionEndpoint, getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import { FetchDataContainersListParams } from "../Explorer/ContainerCopy/Types";
|
||||
import { ApiType } from "../UserContext";
|
||||
|
||||
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 { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params;
|
||||
|
||||
@@ -2,10 +2,17 @@ import { DatabaseModel } from "Contracts/DataModels";
|
||||
import useSWR from "swr";
|
||||
import { getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import { FetchDatabasesListParams } from "../Explorer/ContainerCopy/Types";
|
||||
import { ApiType } from "../UserContext";
|
||||
|
||||
const apiVersion = "2023-09-15";
|
||||
export interface FetchDatabasesListParams {
|
||||
armToken: string;
|
||||
subscriptionId: string;
|
||||
resourceGroupName: string;
|
||||
accountName: string;
|
||||
apiType?: ApiType;
|
||||
}
|
||||
|
||||
const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string => {
|
||||
const { subscriptionId, resourceGroupName, accountName, apiType } = params;
|
||||
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