diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 1eed03a4f..a4f3b3f9b 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -179,6 +179,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 1f0eda4f7..0ac42a9bb 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -41,6 +41,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( @@ -110,7 +117,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); @@ -197,6 +211,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ { key: Constants.PriorityLevel.High, text: "High" }, ]; + const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [ + { key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" }, + { key: Constants.Queries.setTrueRBACOption, text: "True" }, + { key: Constants.Queries.setFalseRBACOption, text: "False"} + ]; + const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, @@ -208,6 +228,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); }; @@ -361,6 +385,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/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 32e686520..740b4cf63 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -733,7 +733,6 @@ export class CassandraAPIDataClient extends TableDataClient { private useCassandraProxyEndpoint(api: string): boolean { const activeCassandraProxyEndpoints: string[] = [ - // CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac, CassandraProxyEndpoints.Prod, ]; diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 7bf3c8a3f..790b5d643 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; @@ -74,6 +75,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 b229ac7db..3f43828fa 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -5,6 +5,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 2fa1bb946..753f36583 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -101,6 +101,7 @@ 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 4e80b3476..c46b944ad 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -4,6 +4,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"; @@ -270,8 +271,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) { @@ -393,8 +416,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")) { @@ -407,6 +431,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") { @@ -415,11 +440,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; } @@ -449,6 +474,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); } @@ -469,9 +525,11 @@ async function configurePortal(): Promise { }, false, ); - + sendReadyMessage(); + }); + } function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {