diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index ff121f3a5..ad413db3c 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -185,6 +185,9 @@ export class CassandraProxyAPIs { export class Queries { public static CustomPageOption: string = "custom"; public static UnlimitedPageOption: string = "unlimited"; + public static setAutomaticRBACOption: string = "Automatic"; + public static setTrueRBACOption: string = "True"; + public static setFalseRBACOption: string = "False"; public static itemsPerPage: number = 100; public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions public static containersPerPage: number = 50; diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 320a10015..8c83edff1 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -44,6 +44,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ? Constants.Queries.UnlimitedPageOption : Constants.Queries.CustomPageOption, ); + const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState( + LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setAutomaticRBACOption + ? Constants.Queries.setAutomaticRBACOption + : LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setTrueRBACOption + ? Constants.Queries.setTrueRBACOption + : Constants.Queries.setFalseRBACOption + ); const [ruThresholdEnabled, setRUThresholdEnabled] = useState(isRUThresholdEnabled()); const [ruThreshold, setRUThreshold] = useState(getRUThreshold()); const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState( @@ -119,7 +126,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ StorageKey.ActualItemPerPage, isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage, ); + LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); + + LocalStorageUtility.setEntryString( + StorageKey.DataPlaneRbacEnabled, + enableDataPlaneRBACOption + ); + LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); @@ -207,9 +221,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ { key: Constants.PriorityLevel.High, text: "High" }, ]; - const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [ - { key: SplitterDirection.Vertical, text: "Vertical" }, - { key: SplitterDirection.Horizontal, text: "Horizontal" }, + const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [ + { key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" }, + { key: Constants.Queries.setTrueRBACOption, text: "True" }, + { key: Constants.Queries.setFalseRBACOption, text: "False"} ]; const handleOnPriorityLevelOptionChange = ( @@ -223,6 +238,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ setPageOption(option.key); }; + const handleOnDataPlaneRBACOptionChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { + setEnableDataPlaneRBACOption(option.key); + }; + const handleOnRUThresholdToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setRUThresholdEnabled(checked); }; @@ -383,6 +402,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )} + {( +
+
+
+ + Enable DataPlane RBAC + + + Choose Automatic to enable DataPlane RBAC automatically. True/False to voluntarily enable/disable DataPlane RBAC + + +
+
+
+ )} {userContext.apiType === "SQL" && ( <>
diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 5bd84516e..28723e475 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -14,6 +14,7 @@ export type Features = { readonly enableTtl: boolean; readonly executeSproc: boolean; readonly enableAadDataPlane: boolean; + readonly enableDataPlaneRbac: boolean; readonly enableResourceGraph: boolean; readonly enableKoResourceTree: boolean; readonly hostedDataExplorer: boolean; @@ -69,6 +70,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear canExceedMaximumValue: "true" === get("canexceedmaximumvalue"), cosmosdb: "true" === get("cosmosdb"), enableAadDataPlane: "true" === get("enableaaddataplane"), + enableDataPlaneRbac: "true" === get("enabledataplanerbac"), enableResourceGraph: "true" === get("enableresourcegraph"), enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"), enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"), diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts index 652451e57..6cf5fa47d 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -6,6 +6,9 @@ import * as StringUtility from "./StringUtility"; export { LocalStorageUtility, SessionStorageUtility }; export enum StorageKey { ActualItemPerPage, + DataPlaneRbacEnabled, + DataPlaneRbacDisabled, + isDataPlaneRbacAutomatic, RUThresholdEnabled, RUThreshold, QueryTimeoutEnabled, diff --git a/src/UserContext.ts b/src/UserContext.ts index 30a0236dc..4718e3915 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -101,6 +101,7 @@ export interface UserContext { sampleDataConnectionInfo?: ParsedResourceTokenConnectionString; readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams; readonly feedbackPolicies?: AdminFeedbackPolicySettings; + readonly dataPlaneRbacEnabled?: boolean; } export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 3a6061acd..c0d1ea1fe 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -5,6 +5,7 @@ import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesCon import Explorer from "Explorer/Explorer"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; +import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { useQueryCopilot } from "hooks/useQueryCopilot"; @@ -271,8 +272,30 @@ async function configureHostedWithAAD(config: AAD): Promise { } } try { - if (!account.properties.disableLocalAuth) { - keys = await listKeys(subscriptionId, resourceGroup, account.name); + if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) { + var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled); + if (isDataPlaneRbacSetting == "Automatic") + { + if (!account.properties.disableLocalAuth) { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + } + else { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + } + else if(isDataPlaneRbacSetting == "True") { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + else { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + updateUserContext({ + dataPlaneRbacEnabled: false + }); + } } } catch (e) { if (userContext.features.enableAadDataPlane) { @@ -394,8 +417,9 @@ async function configurePortal(): Promise { updateUserContext({ authType: AuthType.AAD, }); + let explorer: Explorer; - return new Promise((resolve) => { + return new Promise(async (resolve) => { // In development mode, try to load the iframe message from session storage. // This allows webpack hot reload to function properly in the portal if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) { @@ -408,6 +432,7 @@ async function configurePortal(): Promise { console.dir(message); updateContextsFromPortalMessage(message); explorer = new Explorer(); + // In development mode, save the iframe message from the portal in session storage. // This allows webpack hot reload to funciton properly if (process.env.NODE_ENV === "development") { @@ -416,11 +441,11 @@ async function configurePortal(): Promise { resolve(explorer); } } - + // In the Portal, configuration of Explorer happens via iframe message window.addEventListener( "message", - (event) => { + async (event) => { if (isInvalidParentFrameOrigin(event)) { return; } @@ -450,6 +475,37 @@ async function configurePortal(): Promise { setTimeout(() => explorer.openNPSSurveyDialog(), 3000); } + let dbAccount = userContext.databaseAccount; + let keys: DatabaseAccountListKeysResult = {}; + const account = userContext.databaseAccount; + const subscriptionId = userContext.subscriptionId; + const resourceGroup = userContext.resourceGroup; + if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) { + var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled); + if (isDataPlaneRbacSetting == "Automatic") + { + if (!account.properties.disableLocalAuth) { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + } + else { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + } + else if(isDataPlaneRbacSetting == "True") { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + else { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + updateUserContext({ + dataPlaneRbacEnabled: false + }); + } + } + if (openAction) { handleOpenAction(openAction, useDatabases.getState().databases, explorer); } @@ -470,9 +526,11 @@ async function configurePortal(): Promise { }, false, ); - + sendReadyMessage(); + }); + } function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {