mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-04-19 04:48:59 +01:00
upgrade RBAC permissions from read only to read-write
This commit is contained in:
committed by
BChoudhury-ms
parent
8698c6a3e2
commit
3f1819f22a
@@ -66,7 +66,7 @@ export default {
|
|||||||
// Assign Permissions Screen
|
// Assign Permissions Screen
|
||||||
assignPermissions: {
|
assignPermissions: {
|
||||||
crossAccountDescription:
|
crossAccountDescription:
|
||||||
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.",
|
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.",
|
||||||
intraAccountOnlineDescription: (accountName: string) =>
|
intraAccountOnlineDescription: (accountName: string) =>
|
||||||
`Follow the steps below to enable online copy on your "${accountName}" account.`,
|
`Follow the steps below to enable online copy on your "${accountName}" account.`,
|
||||||
crossAccountConfiguration: {
|
crossAccountConfiguration: {
|
||||||
@@ -119,18 +119,18 @@ export default {
|
|||||||
popoverDescription: (accountName: string) =>
|
popoverDescription: (accountName: string) =>
|
||||||
`Assign the system-assigned managed identity as the default for "${accountName}". To confirm, click the "Yes" button. `,
|
`Assign the system-assigned managed identity as the default for "${accountName}". To confirm, click the "Yes" button. `,
|
||||||
},
|
},
|
||||||
readPermissionAssigned: {
|
readWritePermissionAssigned: {
|
||||||
title: "Read permissions assigned to the default identity.",
|
title: "Read-write permissions assigned to the default identity.",
|
||||||
description:
|
description:
|
||||||
"To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.",
|
"To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.",
|
||||||
tooltip: {
|
tooltip: {
|
||||||
content: "Learn more about",
|
content: "Learn more about",
|
||||||
hrefText: "Read permissions.",
|
hrefText: "Read-write permissions.",
|
||||||
href: "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
|
href: "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
|
||||||
},
|
},
|
||||||
popoverTitle: "Read permissions assigned to default identity.",
|
popoverTitle: "Assign read-write permissions to default identity.",
|
||||||
popoverDescription:
|
popoverDescription:
|
||||||
"Assign read permissions of the source account to the default identity of the destination account. To confirm click the “Yes” button.",
|
'Assign read-write permissions on the source account to the default identity of the destination account. To confirm, click the "Yes" button.',
|
||||||
},
|
},
|
||||||
pointInTimeRestore: {
|
pointInTimeRestore: {
|
||||||
title: "Point In Time Restore enabled",
|
title: "Point In Time Restore enabled",
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ describe("CopyJobContext", () => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
});
|
});
|
||||||
expect(contextValue.flow).toBeNull();
|
expect(contextValue.flow).toBeNull();
|
||||||
expect(contextValue.contextError).toBeNull();
|
expect(contextValue.contextError).toBeNull();
|
||||||
@@ -620,7 +620,7 @@ describe("CopyJobContext", () => {
|
|||||||
expect(contextValue.copyJobState.target.account).toBeNull();
|
expect(contextValue.copyJobState.target.account).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize sourceReadAccessFromTarget as false", () => {
|
it("should initialize sourceReadWriteAccessFromTarget as false", () => {
|
||||||
let contextValue: any;
|
let contextValue: any;
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@@ -634,7 +634,7 @@ describe("CopyJobContext", () => {
|
|||||||
</CopyJobContextProvider>,
|
</CopyJobContextProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(contextValue.copyJobState.sourceReadAccessFromTarget).toBe(false);
|
expect(contextValue.copyJobState.sourceReadWriteAccessFromTarget).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize with empty database and container ids", () => {
|
it("should initialize with empty database and container ids", () => {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe("AddManagedIdentity", () => {
|
|||||||
databaseId: "target-db",
|
databaseId: "target-db",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockContextValue = {
|
const mockContextValue = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React from "react";
|
|||||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||||
import { CopyJobContext } from "../../../Context/CopyJobContext";
|
import { CopyJobContext } from "../../../Context/CopyJobContext";
|
||||||
import { CopyJobContextProviderType } from "../../../Types/CopyJobTypes";
|
import { CopyJobContextProviderType } from "../../../Types/CopyJobTypes";
|
||||||
import AddReadPermissionToDefaultIdentity from "./AddReadPermissionToDefaultIdentity";
|
import AddReadWritePermissionToDefaultIdentity from "./AddReadWritePermissionToDefaultIdentity";
|
||||||
|
|
||||||
jest.mock("../../../../../Common/Logger", () => ({
|
jest.mock("../../../../../Common/Logger", () => ({
|
||||||
logError: jest.fn(),
|
logError: jest.fn(),
|
||||||
@@ -73,7 +73,7 @@ import { assignRole, RoleAssignmentType } from "../../../../../Utils/arm/RbacUti
|
|||||||
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
|
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
|
||||||
import useToggle from "./hooks/useToggle";
|
import useToggle from "./hooks/useToggle";
|
||||||
|
|
||||||
describe("AddReadPermissionToDefaultIdentity Component", () => {
|
describe("AddReadWritePermissionToDefaultIdentity Component", () => {
|
||||||
const mockUseToggle = useToggle as jest.MockedFunction<typeof useToggle>;
|
const mockUseToggle = useToggle as jest.MockedFunction<typeof useToggle>;
|
||||||
const mockAssignRole = assignRole as jest.MockedFunction<typeof assignRole>;
|
const mockAssignRole = assignRole as jest.MockedFunction<typeof assignRole>;
|
||||||
const mockGetAccountDetailsFromResourceId = getAccountDetailsFromResourceId as jest.MockedFunction<
|
const mockGetAccountDetailsFromResourceId = getAccountDetailsFromResourceId as jest.MockedFunction<
|
||||||
@@ -119,7 +119,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
databaseId: "target-db",
|
databaseId: "target-db",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
},
|
},
|
||||||
setCopyJobState: jest.fn(),
|
setCopyJobState: jest.fn(),
|
||||||
setContextError: jest.fn(),
|
setContextError: jest.fn(),
|
||||||
@@ -133,7 +133,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
const renderComponent = (contextValue = mockContextValue) => {
|
const renderComponent = (contextValue = mockContextValue) => {
|
||||||
return render(
|
return render(
|
||||||
<CopyJobContext.Provider value={contextValue}>
|
<CopyJobContext.Provider value={contextValue}>
|
||||||
<AddReadPermissionToDefaultIdentity />
|
<AddReadWritePermissionToDefaultIdentity />
|
||||||
</CopyJobContext.Provider>,
|
</CopyJobContext.Provider>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -164,12 +164,12 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render correctly when sourceReadAccessFromTarget is true", () => {
|
it("should render correctly when sourceReadWriteAccessFromTarget is true", () => {
|
||||||
const contextWithAccess = {
|
const contextWithAccess = {
|
||||||
...mockContextValue,
|
...mockContextValue,
|
||||||
copyJobState: {
|
copyJobState: {
|
||||||
...mockContextValue.copyJobState,
|
...mockContextValue.copyJobState,
|
||||||
sourceReadAccessFromTarget: true,
|
sourceReadWriteAccessFromTarget: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { container } = renderComponent(contextWithAccess);
|
const { container } = renderComponent(contextWithAccess);
|
||||||
@@ -180,7 +180,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
describe("Component Structure", () => {
|
describe("Component Structure", () => {
|
||||||
it("should display the description text", () => {
|
it("should display the description text", () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
expect(screen.getByText(ContainerCopyMessages.readPermissionAssigned.description)).toBeInTheDocument();
|
expect(screen.getByText(ContainerCopyMessages.readWritePermissionAssigned.description)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display the info tooltip", () => {
|
it("should display the info tooltip", () => {
|
||||||
@@ -212,10 +212,10 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
|
|
||||||
expect(screen.getByTestId("popover-message")).toBeInTheDocument();
|
expect(screen.getByTestId("popover-message")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("popover-title")).toHaveTextContent(
|
expect(screen.getByTestId("popover-title")).toHaveTextContent(
|
||||||
ContainerCopyMessages.readPermissionAssigned.popoverTitle,
|
ContainerCopyMessages.readWritePermissionAssigned.popoverTitle,
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId("popover-content")).toHaveTextContent(
|
expect(screen.getByTestId("popover-content")).toHaveTextContent(
|
||||||
ContainerCopyMessages.readPermissionAssigned.popoverDescription,
|
ContainerCopyMessages.readWritePermissionAssigned.popoverDescription,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
expect(mockOnToggle).toHaveBeenCalledWith(null, false);
|
expect(mockOnToggle).toHaveBeenCalledWith(null, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call handleAddReadPermission when primary button is clicked", async () => {
|
it("should call handleAddReadWritePermission when primary button is clicked", async () => {
|
||||||
mockGetAccountDetailsFromResourceId.mockReturnValue({
|
mockGetAccountDetailsFromResourceId.mockReturnValue({
|
||||||
subscriptionId: "source-sub-id",
|
subscriptionId: "source-sub-id",
|
||||||
resourceGroup: "source-rg",
|
resourceGroup: "source-rg",
|
||||||
@@ -264,7 +264,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("handleAddReadPermission Function", () => {
|
describe("handleAddReadWritePermission Function", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockUseToggle.mockReturnValue([true, jest.fn()]);
|
mockUseToggle.mockReturnValue([true, jest.fn()]);
|
||||||
});
|
});
|
||||||
@@ -312,7 +312,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockLogError).toHaveBeenCalledWith(
|
expect(mockLogError).toHaveBeenCalledWith(
|
||||||
"Permission denied",
|
"Permission denied",
|
||||||
"CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission",
|
"CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -336,14 +336,14 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockLogError).toHaveBeenCalledWith(
|
expect(mockLogError).toHaveBeenCalledWith(
|
||||||
"Error assigning read permission to default identity. Please try again later.",
|
"Error assigning read-write permission to default identity. Please try again later.",
|
||||||
"CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission",
|
"CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockContextValue.setContextError).toHaveBeenCalledWith(
|
expect(mockContextValue.setContextError).toHaveBeenCalledWith(
|
||||||
"Error assigning read permission to default identity. Please try again later.",
|
"Error assigning read-write permission to default identity. Please try again later.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -496,7 +496,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
|
|||||||
|
|
||||||
expect(updatedState).toEqual({
|
expect(updatedState).toEqual({
|
||||||
...mockContextValue.copyJobState,
|
...mockContextValue.copyJobState,
|
||||||
sourceReadAccessFromTarget: true,
|
sourceReadWriteAccessFromTarget: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,27 +12,29 @@ import useToggle from "./hooks/useToggle";
|
|||||||
|
|
||||||
const TooltipContent = (
|
const TooltipContent = (
|
||||||
<Text>
|
<Text>
|
||||||
{ContainerCopyMessages.readPermissionAssigned.tooltip.content}
|
{ContainerCopyMessages.readWritePermissionAssigned.tooltip.content}
|
||||||
<Link
|
<Link
|
||||||
style={{ color: "var(--colorBrandForeground1)" }}
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
href={ContainerCopyMessages.readPermissionAssigned.tooltip.href}
|
href={ContainerCopyMessages.readWritePermissionAssigned.tooltip.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText}
|
{ContainerCopyMessages.readWritePermissionAssigned.tooltip.hrefText}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
type AddReadPermissionToDefaultIdentityProps = Partial<PermissionSectionConfig>;
|
|
||||||
|
|
||||||
const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIdentityProps> = () => {
|
type AddReadWritePermissionToDefaultIdentityProps = Partial<PermissionSectionConfig>;
|
||||||
|
|
||||||
|
const AddReadWritePermissionToDefaultIdentity: React.FC<AddReadWritePermissionToDefaultIdentityProps> = () => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
|
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
|
||||||
const [readPermissionAssigned, onToggle] = useToggle(false);
|
const [readWritePermissionAssigned, onToggle] = useToggle(copyJobState.sourceReadWriteAccessFromTarget ?? false);
|
||||||
|
|
||||||
const handleAddReadPermission = async () => {
|
const handleAddReadWritePermission = async () => {
|
||||||
const { source, target } = copyJobState;
|
const { source, target } = copyJobState;
|
||||||
const selectedSourceAccount = source?.account;
|
const selectedSourceAccount = source?.account;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
subscriptionId: sourceSubscriptionId,
|
subscriptionId: sourceSubscriptionId,
|
||||||
@@ -47,16 +49,17 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
|
|||||||
sourceAccountName,
|
sourceAccountName,
|
||||||
target?.account?.identity?.principalId ?? "",
|
target?.account?.identity?.principalId ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (assignedRole) {
|
if (assignedRole) {
|
||||||
setCopyJobState((prevState) => ({
|
setCopyJobState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
sourceReadAccessFromTarget: true,
|
sourceReadWriteAccessFromTarget: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error.message || "Error assigning read permission to default identity. Please try again later.";
|
error.message || "Error assigning read-write permission to default identity. Please try again later.";
|
||||||
logError(errorMessage, "CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission");
|
logError(errorMessage, "CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission");
|
||||||
setContextError(errorMessage);
|
setContextError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -66,12 +69,12 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
|
|||||||
return (
|
return (
|
||||||
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||||
<Text className="toggle-label">
|
<Text className="toggle-label">
|
||||||
{ContainerCopyMessages.readPermissionAssigned.description} 
|
{ContainerCopyMessages.readWritePermissionAssigned.description} 
|
||||||
<InfoTooltip content={TooltipContent} />
|
<InfoTooltip content={TooltipContent} />
|
||||||
</Text>
|
</Text>
|
||||||
<Toggle
|
<Toggle
|
||||||
data-test="btn-toggle"
|
data-test="btn-toggle"
|
||||||
checked={readPermissionAssigned}
|
checked={readWritePermissionAssigned}
|
||||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||||
onChange={onToggle}
|
onChange={onToggle}
|
||||||
@@ -83,15 +86,15 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
|
|||||||
/>
|
/>
|
||||||
<PopoverMessage
|
<PopoverMessage
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
visible={readPermissionAssigned}
|
visible={readWritePermissionAssigned}
|
||||||
title={ContainerCopyMessages.readPermissionAssigned.popoverTitle}
|
title={ContainerCopyMessages.readWritePermissionAssigned.popoverTitle}
|
||||||
onCancel={() => onToggle(null, false)}
|
onCancel={() => onToggle(null, false)}
|
||||||
onPrimary={handleAddReadPermission}
|
onPrimary={handleAddReadWritePermission}
|
||||||
>
|
>
|
||||||
{ContainerCopyMessages.readPermissionAssigned.popoverDescription}
|
{ContainerCopyMessages.readWritePermissionAssigned.popoverDescription}
|
||||||
</PopoverMessage>
|
</PopoverMessage>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddReadPermissionToDefaultIdentity;
|
export default AddReadWritePermissionToDefaultIdentity;
|
||||||
@@ -43,12 +43,12 @@ jest.mock("./AddManagedIdentity", () => {
|
|||||||
return MockAddManagedIdentity;
|
return MockAddManagedIdentity;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("./AddReadPermissionToDefaultIdentity", () => {
|
jest.mock("./AddReadWritePermissionToDefaultIdentity", () => {
|
||||||
const MockAddReadPermissionToDefaultIdentity = () => {
|
const MockAddReadWritePermissionToDefaultIdentity = () => {
|
||||||
return <div data-testid="add-read-permission">Add Read Permission Component</div>;
|
return <div data-testid="add-read-write-permission">Add Read-Write Permission Component</div>;
|
||||||
};
|
};
|
||||||
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
|
MockAddReadWritePermissionToDefaultIdentity.displayName = "MockAddReadWritePermissionToDefaultIdentity";
|
||||||
return MockAddReadPermissionToDefaultIdentity;
|
return MockAddReadWritePermissionToDefaultIdentity;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("./DefaultManagedIdentity", () => {
|
jest.mock("./DefaultManagedIdentity", () => {
|
||||||
@@ -96,7 +96,7 @@ describe("AssignPermissions Component", () => {
|
|||||||
databaseId: "target-db",
|
databaseId: "target-db",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
...overrides,
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ describe("AssignPermissions Component", () => {
|
|||||||
completed: true,
|
completed: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "readPermissionAssigned",
|
id: "readWritePermissionAssigned",
|
||||||
title: "Read Permission Assigned",
|
title: "Read Permission Assigned",
|
||||||
Component: () => <div data-testid="add-read-permission">Add Read Permission Component</div>,
|
Component: () => <div data-testid="add-read-permission">Add Read Permission Component</div>,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe("PointInTimeRestore", () => {
|
|||||||
databaseId: "target-db",
|
databaseId: "target-db",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
} as CopyJobContextState;
|
} as CopyJobContextState;
|
||||||
|
|
||||||
const mockSetCopyJobState = jest.fn();
|
const mockSetCopyJobState = jest.fn();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle missing source account 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Edge Cases should handle missing source account 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -8,7 +8,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -24,7 +24,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +63,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle missing target account identity 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Edge Cases should handle missing target account identity 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -71,7 +71,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -87,7 +87,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +126,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly when sourceReadAccessFromTarget is true 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly when sourceReadWriteAccessFromTarget is true 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -134,7 +134,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -150,7 +150,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,7 +189,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly when toggle is on 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly when toggle is on 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -197,7 +197,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -213,7 +213,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,12 +255,12 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
<div
|
<div
|
||||||
data-testid="popover-title"
|
data-testid="popover-title"
|
||||||
>
|
>
|
||||||
Read permissions assigned to default identity.
|
Assign read-write permissions to default identity.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-testid="popover-content"
|
data-testid="popover-content"
|
||||||
>
|
>
|
||||||
Assign read permissions of the source account to the default identity of the destination account. To confirm click the “Yes” button.
|
Assign read-write permissions on the source account to the default identity of the destination account. To confirm, click the "Yes" button.
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
data-testid="popover-cancel"
|
data-testid="popover-cancel"
|
||||||
@@ -277,7 +277,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly with default state 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly with default state 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -285,7 +285,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -301,7 +301,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -340,7 +340,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly with different context states 1`] = `
|
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly with different context states 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack defaultManagedIdentityContainer css-109"
|
class="ms-Stack defaultManagedIdentityContainer css-109"
|
||||||
@@ -348,7 +348,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
<span
|
<span
|
||||||
class="toggle-label css-110"
|
class="toggle-label css-110"
|
||||||
>
|
>
|
||||||
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
|
To allow data copy from source to the destination container, provide read-write access on the source account to the default identity of the destination account.
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="info-tooltip"
|
data-testid="info-tooltip"
|
||||||
@@ -364,7 +364,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Read permissions.
|
Read-write permissions.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-111"
|
class="ms-Stack css-111"
|
||||||
@@ -212,7 +212,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-111"
|
class="ms-Stack css-111"
|
||||||
@@ -618,7 +618,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-111"
|
class="ms-Stack css-111"
|
||||||
@@ -1153,7 +1153,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-111"
|
class="ms-Stack css-111"
|
||||||
@@ -1307,7 +1307,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
data-testid="shimmer-tree"
|
data-testid="shimmer-tree"
|
||||||
@@ -1329,7 +1329,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
|||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
>
|
>
|
||||||
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
|
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read-write access to the source account by completing the following steps.
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
data-testid="shimmer-tree"
|
data-testid="shimmer-tree"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
||||||
import * as CopyJobPrerequisitesCacheModule from "../../../Utils/useCopyJobPrerequisitesCache";
|
import * as CopyJobPrerequisitesCacheModule from "../../../Utils/useCopyJobPrerequisitesCache";
|
||||||
import usePermissionSections, {
|
import usePermissionSections, {
|
||||||
checkTargetHasReaderRoleOnSource,
|
checkTargetHasReadWriteRoleOnSource,
|
||||||
PermissionGroupConfig,
|
PermissionGroupConfig,
|
||||||
SECTION_IDS,
|
SECTION_IDS,
|
||||||
} from "./usePermissionsSection";
|
} from "./usePermissionsSection";
|
||||||
@@ -40,12 +40,12 @@ jest.mock("../AddManagedIdentity", () => {
|
|||||||
return MockAddManagedIdentity;
|
return MockAddManagedIdentity;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("../AddReadPermissionToDefaultIdentity", () => {
|
jest.mock("../AddReadWritePermissionToDefaultIdentity", () => {
|
||||||
const MockAddReadPermissionToDefaultIdentity = () => {
|
const MockAddReadWritePermissionToDefaultIdentity = () => {
|
||||||
return <div data-testid="add-read-permission">AddReadPermissionToDefaultIdentity</div>;
|
return <div data-testid="add-read-write-permission">AddReadWritePermissionToDefaultIdentity</div>;
|
||||||
};
|
};
|
||||||
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
|
MockAddReadWritePermissionToDefaultIdentity.displayName = "MockAddReadWritePermissionToDefaultIdentity";
|
||||||
return MockAddReadPermissionToDefaultIdentity;
|
return MockAddReadWritePermissionToDefaultIdentity;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("../DefaultManagedIdentity", () => {
|
jest.mock("../DefaultManagedIdentity", () => {
|
||||||
@@ -193,7 +193,7 @@ describe("usePermissionsSection", () => {
|
|||||||
expect(capturedResult[0].sections.map((s) => s.id)).toEqual([
|
expect(capturedResult[0].sections.map((s) => s.id)).toEqual([
|
||||||
SECTION_IDS.addManagedIdentity,
|
SECTION_IDS.addManagedIdentity,
|
||||||
SECTION_IDS.defaultManagedIdentity,
|
SECTION_IDS.defaultManagedIdentity,
|
||||||
SECTION_IDS.readPermissionAssigned,
|
SECTION_IDS.readWritePermissionAssigned,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -358,16 +358,17 @@ describe("usePermissionsSection", () => {
|
|||||||
expect(defaultManagedIdentitySection?.completed).toBe(true);
|
expect(defaultManagedIdentitySection?.completed).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should validate readPermissionAssigned section with reader role", async () => {
|
it("should validate readWritePermissionAssigned section with contributor role", async () => {
|
||||||
const mockRoleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
const mockRoleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
||||||
{
|
{
|
||||||
id: "role-1",
|
id: "role-1",
|
||||||
name: "Custom Role",
|
name: "00000000-0000-0000-0000-000000000002",
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
dataActions: [
|
dataActions: [
|
||||||
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
|
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
|
||||||
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
|
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
|
||||||
|
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -407,7 +408,9 @@ describe("usePermissionsSection", () => {
|
|||||||
render(<TestWrapper state={state} onResult={noop} />);
|
render(<TestWrapper state={state} onResult={noop} />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId(`section-${SECTION_IDS.readPermissionAssigned}-completed`)).toHaveTextContent("true");
|
expect(screen.getByTestId(`section-${SECTION_IDS.readWritePermissionAssigned}-completed`)).toHaveTextContent(
|
||||||
|
"true",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockedRbacUtils.fetchRoleAssignments).toHaveBeenCalledWith(
|
expect(mockedRbacUtils.fetchRoleAssignments).toHaveBeenCalledWith(
|
||||||
@@ -568,12 +571,12 @@ describe("usePermissionsSection", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("checkTargetHasReaderRoleOnSource", () => {
|
describe("checkTargetHasReadWriteRoleOnSource", () => {
|
||||||
it("should return true for built-in Reader role", () => {
|
it("should return true for built-in Contributor role", () => {
|
||||||
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
||||||
{
|
{
|
||||||
id: "role-1",
|
id: "role-1",
|
||||||
name: "00000000-0000-0000-0000-000000000001",
|
name: "00000000-0000-0000-0000-000000000002",
|
||||||
permissions: [],
|
permissions: [],
|
||||||
assignableScopes: [],
|
assignableScopes: [],
|
||||||
resourceGroup: "",
|
resourceGroup: "",
|
||||||
@@ -583,20 +586,21 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
|
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return true for custom role with required data actions", () => {
|
it("should return true for custom role with read-write data actions", () => {
|
||||||
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
||||||
{
|
{
|
||||||
id: "role-1",
|
id: "role-1",
|
||||||
name: "Custom Reader Role",
|
name: "Custom Contributor Role",
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
dataActions: [
|
dataActions: [
|
||||||
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
|
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
|
||||||
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
|
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
|
||||||
|
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -608,7 +612,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
|
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -630,12 +634,12 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
|
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false for empty role definitions", () => {
|
it("should return false for empty role definitions", () => {
|
||||||
const result = checkTargetHasReaderRoleOnSource([]);
|
const result = checkTargetHasReadWriteRoleOnSource([]);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -653,11 +657,11 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
|
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle multiple roles and return true if any has sufficient permissions", () => {
|
it("should handle multiple roles and return true if any has sufficient read-write permissions", () => {
|
||||||
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
|
||||||
{
|
{
|
||||||
id: "role-1",
|
id: "role-1",
|
||||||
@@ -675,7 +679,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "role-2",
|
id: "role-2",
|
||||||
name: "00000000-0000-0000-0000-000000000001",
|
name: "00000000-0000-0000-0000-000000000002",
|
||||||
permissions: [],
|
permissions: [],
|
||||||
assignableScopes: [],
|
assignableScopes: [],
|
||||||
resourceGroup: "",
|
resourceGroup: "",
|
||||||
@@ -685,7 +689,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
|
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
||||||
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
|
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
|
||||||
import AddManagedIdentity from "../AddManagedIdentity";
|
import AddManagedIdentity from "../AddManagedIdentity";
|
||||||
import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
|
import AddReadWritePermissionToDefaultIdentity from "../AddReadWritePermissionToDefaultIdentity";
|
||||||
import DefaultManagedIdentity from "../DefaultManagedIdentity";
|
import DefaultManagedIdentity from "../DefaultManagedIdentity";
|
||||||
import OnlineCopyEnabled from "../OnlineCopyEnabled";
|
import OnlineCopyEnabled from "../OnlineCopyEnabled";
|
||||||
import PointInTimeRestore from "../PointInTimeRestore";
|
import PointInTimeRestore from "../PointInTimeRestore";
|
||||||
@@ -36,11 +36,13 @@ export interface PermissionGroupConfig {
|
|||||||
export const SECTION_IDS = {
|
export const SECTION_IDS = {
|
||||||
addManagedIdentity: "addManagedIdentity",
|
addManagedIdentity: "addManagedIdentity",
|
||||||
defaultManagedIdentity: "defaultManagedIdentity",
|
defaultManagedIdentity: "defaultManagedIdentity",
|
||||||
readPermissionAssigned: "readPermissionAssigned",
|
readWritePermissionAssigned: "readWritePermissionAssigned",
|
||||||
pointInTimeRestore: "pointInTimeRestore",
|
pointInTimeRestore: "pointInTimeRestore",
|
||||||
onlineCopyEnabled: "onlineCopyEnabled",
|
onlineCopyEnabled: "onlineCopyEnabled",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const COSMOS_DB_BUILT_IN_DATA_CONTRIBUTOR_ROLE_ID = "00000000-0000-0000-0000-000000000002";
|
||||||
|
|
||||||
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
||||||
{
|
{
|
||||||
id: SECTION_IDS.addManagedIdentity,
|
id: SECTION_IDS.addManagedIdentity,
|
||||||
@@ -66,9 +68,9 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: SECTION_IDS.readPermissionAssigned,
|
id: SECTION_IDS.readWritePermissionAssigned,
|
||||||
title: ContainerCopyMessages.readPermissionAssigned.title,
|
title: ContainerCopyMessages.readWritePermissionAssigned.title,
|
||||||
Component: AddReadPermissionToDefaultIdentity,
|
Component: AddReadWritePermissionToDefaultIdentity,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
validate: async (state: CopyJobContextState) => {
|
validate: async (state: CopyJobContextState) => {
|
||||||
const principalId = state?.target?.account?.identity?.principalId;
|
const principalId = state?.target?.account?.identity?.principalId;
|
||||||
@@ -87,7 +89,7 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
|
|||||||
);
|
);
|
||||||
|
|
||||||
const roleDefinitions = await fetchRoleDefinitions(rolesAssigned ?? []);
|
const roleDefinitions = await fetchRoleDefinitions(rolesAssigned ?? []);
|
||||||
return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []);
|
return checkTargetHasReadWriteRoleOnSource(roleDefinitions ?? []);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -119,18 +121,34 @@ const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user has the Reader role based on role definitions.
|
* Checks if the user has contributor-style read-write access on the source account.
|
||||||
*/
|
*/
|
||||||
export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinitionType[]): boolean {
|
export function checkTargetHasReadWriteRoleOnSource(roleDefinitions: RoleDefinitionType[]): boolean {
|
||||||
return roleDefinitions?.some(
|
return roleDefinitions?.some((role) => {
|
||||||
(role) =>
|
if (role.name === COSMOS_DB_BUILT_IN_DATA_CONTRIBUTOR_ROLE_ID) {
|
||||||
role.name === "00000000-0000-0000-0000-000000000001" ||
|
return true;
|
||||||
role.permissions.some(
|
}
|
||||||
(permission) =>
|
|
||||||
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") &&
|
const dataActions = role.permissions?.flatMap((permission) => permission.dataActions ?? []) ?? [];
|
||||||
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read"),
|
|
||||||
),
|
const hasAccountWildcard = dataActions.includes("Microsoft.DocumentDB/databaseAccounts/*");
|
||||||
);
|
const hasContainerWildcard =
|
||||||
|
hasAccountWildcard || dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*");
|
||||||
|
const hasItemsWildcard =
|
||||||
|
hasContainerWildcard ||
|
||||||
|
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*");
|
||||||
|
|
||||||
|
const hasAccountReadMetadata =
|
||||||
|
hasAccountWildcard || dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata");
|
||||||
|
const hasItemRead =
|
||||||
|
hasItemsWildcard ||
|
||||||
|
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read");
|
||||||
|
const hasItemWrite =
|
||||||
|
hasItemsWildcard ||
|
||||||
|
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write");
|
||||||
|
|
||||||
|
return hasAccountReadMetadata && hasItemRead && hasItemWrite;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ describe("AddCollectionPanelWrapper", () => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
},
|
},
|
||||||
setCopyJobState: mockSetCopyJobState,
|
setCopyJobState: mockSetCopyJobState,
|
||||||
flow: null,
|
flow: null,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ describe("PreviewCopyJob", () => {
|
|||||||
databaseId: "target-database",
|
databaseId: "target-database",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ describe("PreviewCopyJob", () => {
|
|||||||
databaseId: "target-database",
|
databaseId: "target-database",
|
||||||
containerId: "target-container",
|
containerId: "target-container",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: true,
|
sourceReadWriteAccessFromTarget: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe("AccountDropdown", () => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
} as CopyJobContextState;
|
} as CopyJobContextState;
|
||||||
|
|
||||||
const mockCopyJobContextValue = {
|
const mockCopyJobContextValue = {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ describe("MigrationType", () => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
},
|
},
|
||||||
setCopyJobState: mockSetCopyJobState,
|
setCopyJobState: mockSetCopyJobState,
|
||||||
flow: { currentScreen: "selectAccount" },
|
flow: { currentScreen: "selectAccount" },
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("SelectAccount", () => {
|
|||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
},
|
},
|
||||||
setCopyJobState: mockSetCopyJobState,
|
setCopyJobState: mockSetCopyJobState,
|
||||||
flow: { currentScreen: "selectAccount" },
|
flow: { currentScreen: "selectAccount" },
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { dropDownChangeHandler } from "./DropDownChangeHandler";
|
|||||||
const createMockInitialState = (): CopyJobContextState => ({
|
const createMockInitialState = (): CopyJobContextState => ({
|
||||||
jobName: "test-job",
|
jobName: "test-job",
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
source: {
|
source: {
|
||||||
subscriptionId: "source-sub-id",
|
subscriptionId: "source-sub-id",
|
||||||
account: {
|
account: {
|
||||||
@@ -181,7 +181,7 @@ describe("dropDownChangeHandler", () => {
|
|||||||
|
|
||||||
expect(capturedState.jobName).toBe(initialState.jobName);
|
expect(capturedState.jobName).toBe(initialState.jobName);
|
||||||
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
||||||
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
|
expect(capturedState.sourceReadWriteAccessFromTarget).toBe(initialState.sourceReadWriteAccessFromTarget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ describe("dropDownChangeHandler", () => {
|
|||||||
|
|
||||||
expect(capturedState.jobName).toBe(initialState.jobName);
|
expect(capturedState.jobName).toBe(initialState.jobName);
|
||||||
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
expect(capturedState.migrationType).toBe(initialState.migrationType);
|
||||||
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
|
expect(capturedState.sourceReadWriteAccessFromTarget).toBe(initialState.sourceReadWriteAccessFromTarget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ describe("SelectSourceAndTargetContainers", () => {
|
|||||||
databaseId: "db2",
|
databaseId: "db2",
|
||||||
containerId: "container2",
|
containerId: "container2",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockMemoizedData = {
|
const mockMemoizedData = {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe("useSourceAndTargetData", () => {
|
|||||||
const mockCopyJobState: CopyJobContextState = {
|
const mockCopyJobState: CopyJobContextState = {
|
||||||
jobName: "test-job",
|
jobName: "test-job",
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadWriteAccessFromTarget: false,
|
||||||
source: {
|
source: {
|
||||||
subscriptionId: "source-subscription-id",
|
subscriptionId: "source-subscription-id",
|
||||||
account: mockSourceAccount,
|
account: mockSourceAccount,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export interface DatabaseContainerSectionProps {
|
|||||||
export interface CopyJobContextState {
|
export interface CopyJobContextState {
|
||||||
jobName: string;
|
jobName: string;
|
||||||
migrationType: CopyJobMigrationType;
|
migrationType: CopyJobMigrationType;
|
||||||
sourceReadAccessFromTarget?: boolean;
|
sourceReadWriteAccessFromTarget?: boolean;
|
||||||
source: {
|
source: {
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
account: DatabaseAccount | null;
|
account: DatabaseAccount | null;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const assignRole = async (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const accountScope = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
|
const accountScope = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
|
||||||
const roleDefinitionId = `${accountScope}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001`;
|
const roleDefinitionId = `${accountScope}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002`; // Built-in Contributor role definition ID for Cosmos DB
|
||||||
const roleAssignmentName = crypto.randomUUID();
|
const roleAssignmentName = crypto.randomUUID();
|
||||||
const path = `${accountScope}/sqlRoleAssignments/${roleAssignmentName}`;
|
const path = `${accountScope}/sqlRoleAssignments/${roleAssignmentName}`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user