mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-29 22:02:01 +00:00
Added hooks to evaluate reader role access
This commit is contained in:
@@ -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>>;
|
||||
|
||||
Reference in New Issue
Block a user