Skip to content

Commit 772c042

Browse files
committed
fix(credentials): resolve a reconnect's cloud before saving and refuse a cross-cloud ADX resource
A Microsoft app reconnect now waits for the stored cloud, or asks for one when it cannot be read (an organization credential or a failed lookup), instead of saving as global. The ADX schema refuses a resource override whose cloud differs from the cluster's before any credential is decrypted, and the Power BI description says app credentials run in the global cloud only.
1 parent c954b13 commit 772c042

7 files changed

Lines changed: 55 additions & 9 deletions

File tree

‎apps/docs/content/docs/integrations/powerbi.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Sim requests these scopes when someone connects a Power BI account. For a self-h
2929

3030
## Use the block [#usage-instructions]
3131

32-
Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.
32+
Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential in the global cloud, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.
3333

3434
## Actions
3535

‎apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/keyless-connection.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export function useKeylessSetup<P extends WorkloadIdentityProvider>({
7575
reconnectAuthType,
7676
/** A reconnected Microsoft app credential's national cloud; a reconnect reopens on it. */
7777
reconnectCloud: setup.data?.reconnectCloud,
78+
/**
79+
* Whether the reconnect fields above came back. False while they load, after a failed
80+
* lookup, and for an organization credential, whose stored secret this lookup never reads.
81+
*/
82+
reconnectFieldsLoaded: workspaceScoped && setup.isSuccess,
7883
isReconnect: Boolean(credentialId),
7984
subjectChoice,
8085
setSubjectChoice,

‎apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/microsoft-app-account-modal.tsx‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,15 @@ function MicrosoftAppAccountForm({
108108
? 'federated'
109109
: 'client_secret'
110110
const authType: MicrosoftAuthType = selectedAuthType ?? defaultAuthType
111-
// A reconnect also opens on the credential's stored cloud.
112-
const cloud: MicrosoftCloudId = selectedCloud ?? keyless.reconnectCloud ?? 'global'
111+
// A reconnect opens on the credential's stored cloud. Until that is known (still loading, a
112+
// failed lookup, or an organization credential) the cloud must be chosen, so a rotation never
113+
// silently moves a national-cloud credential to the global cloud.
114+
const storedCloudKnown = !credentialId || keyless.reconnectFieldsLoaded
115+
const cloud: MicrosoftCloudId | undefined =
116+
selectedCloud ?? (storedCloudKnown ? (keyless.reconnectCloud ?? 'global') : undefined)
113117
const disabled =
114118
isPending ||
119+
!cloud ||
115120
!tenantId.trim() ||
116121
!clientId.trim() ||
117122
(authType === 'client_secret' && !clientSecret.trim()) ||
@@ -123,13 +128,13 @@ function MicrosoftAppAccountForm({
123128
clearError()
124129
}
125130

126-
const buildConfig = (): Record<string, unknown> => {
127-
const isGlobal = cloud === 'global'
131+
const buildConfig = (appCloud: MicrosoftCloudId): Record<string, unknown> => {
132+
const isGlobal = appCloud === 'global'
128133
const base = {
129134
authType,
130135
tenantId: tenantId.trim(),
131136
clientId: clientId.trim(),
132-
...(isGlobal ? {} : { cloud }),
137+
...(isGlobal ? {} : { cloud: appCloud }),
133138
...(isGlobal && environmentUrl.trim() ? { environmentUrl: environmentUrl.trim() } : {}),
134139
}
135140
if (authType === 'federated') return { ...base, ...keylessSubjectFields(keyless) }
@@ -178,6 +183,7 @@ function MicrosoftAppAccountForm({
178183
clearError()
179184
}}
180185
options={CLOUD_OPTIONS}
186+
placeholder='Choose the app’s cloud'
181187
required
182188
hint='The Microsoft cloud the app is registered in. Only Azure Data Explorer runs outside the global cloud.'
183189
/>
@@ -287,7 +293,9 @@ function MicrosoftAppAccountForm({
287293
primaryAction={{
288294
label: isPending ? 'Verifying...' : `${action} credential`,
289295
onClick: () => {
290-
if (!disabled) void submit({ config: buildConfig(), displayName, description })
296+
if (!disabled && cloud) {
297+
void submit({ config: buildConfig(cloud), displayName, description })
298+
}
291299
},
292300
disabled,
293301
}}

‎apps/sim/blocks/blocks/powerbi.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const PowerBIBlock: BlockConfig = {
2525
type: 'powerbi',
2626
name: 'Power BI',
2727
description: 'Query semantic models and manage reports and refreshes',
28-
longDescription: `Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.`,
28+
longDescription: `Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential in the global cloud, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.`,
2929
docsLink: 'https://docs.sim.ai/integrations/powerbi',
3030
category: 'tools',
3131
integrationType: IntegrationType.Analytics,

‎apps/sim/lib/internal/azure-data-explorer/execute-tool.test.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,26 @@ describe('running an Azure Data Explorer tool', () => {
219219
)
220220
})
221221

222+
it('refuses a resource override in another cloud than the cluster before decrypting', async () => {
223+
storeAppCredential()
224+
225+
const response = await run(
226+
{
227+
clusterUri: CLUSTER,
228+
resource: 'https://mycluster.usgovvirginia.kusto.usgovcloudapi.net',
229+
},
230+
authorized
231+
)
232+
233+
expect(response.status).toBe(400)
234+
await expect(response.json()).resolves.toMatchObject({
235+
error: expect.stringMatching(/same Microsoft cloud/),
236+
})
237+
expect(encryptionMockFns.mockDecryptSecret).not.toHaveBeenCalled()
238+
expect(entra).not.toHaveBeenCalled()
239+
expect(secureFetch).not.toHaveBeenCalled()
240+
})
241+
222242
it('refuses a host outside the Kusto allowlist without minting', async () => {
223243
const response = await run({ clusterUri: 'https://evil.example.com' }, authorized)
224244

‎apps/sim/lib/internal/azure-data-explorer/schema.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,19 @@ function refineHosts(
125125
path: ['resource'],
126126
message: resourceCheck.message,
127127
})
128+
return
129+
}
130+
if (!clusterCheck.ok) return
131+
const clusterClouds = kustoCredentialClouds(clusterCheck.url.hostname)
132+
const sharesCloud = kustoCredentialClouds(resourceCheck.url.hostname).some((cloud) =>
133+
clusterClouds.includes(cloud)
134+
)
135+
if (!sharesCloud) {
136+
context.addIssue({
137+
code: z.ZodIssueCode.custom,
138+
path: ['resource'],
139+
message: 'resource must be in the same Microsoft cloud as clusterUri',
140+
})
128141
}
129142
}
130143

‎packages/deployment-config/src/integrations.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21622,7 +21622,7 @@
2162221622
"slug": "power-bi",
2162321623
"name": "Power BI",
2162421624
"description": "Query semantic models and manage reports and refreshes",
21625-
"longDescription": "Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.",
21625+
"longDescription": "Connect a home-tenant organizational account through the Power BI Microsoft connection, or run as an app registration with a Microsoft app credential in the global cloud, to list named workspaces, inspect reports and semantic models, execute DAX queries, request standard refreshes, and read refresh history. A Microsoft Graph connection cannot substitute for the Power BI connection or app credential. An app credential needs the Power BI tenant setting that allows service principals to use Power BI APIs and its service principal added to each workspace as a Member or Admin; DAX queries do not run as a service principal against semantic models with row-level security or SSO. Self-hosted installations use the shared Microsoft app registration and register /api/auth/oauth2/callback/microsoft-powerbi as a Web redirect URI. Workspace listing supports a result limit, offset, and OData filter; reports and semantic models return flat lists. Read-only models may contain only their ID and name, and paginated reports may omit their semantic-model ID. Missing optional fields are null and do not establish permission or feature availability. Keep results small with summaries or TOPN: provider JSON reads are limited to 20 MiB, and workflow tool responses have a stricter 10 MiB inline budget. My Workspace, cross-tenant guest configuration, Credential Groups, impersonation, enhanced refresh, report exports, dashboards, and triggers are outside this version.",
2162621626
"bgColor": "#FFFFFF",
2162721627
"iconName": "PowerBIIcon",
2162821628
"docsUrl": "https://docs.sim.ai/integrations/powerbi",

0 commit comments

Comments
 (0)