diff --git a/libs/designer-v2/src/lib/core/queries/connector.ts b/libs/designer-v2/src/lib/core/queries/connector.ts index 5e985464f53..4b7bdc57b14 100644 --- a/libs/designer-v2/src/lib/core/queries/connector.ts +++ b/libs/designer-v2/src/lib/core/queries/connector.ts @@ -106,7 +106,8 @@ export const getListDynamicValues = async ( operationId: string, parameters: Record, dynamicState: any, - operationPath?: string + operationPath?: string, + identity?: string ): Promise => { const queryClient = getReactQueryClient(); const service = ConnectorService(); @@ -119,8 +120,9 @@ export const getListDynamicValues = async ( operationId.toLowerCase(), dynamicState.operationId?.toLowerCase(), getParametersKey({ ...dynamicState.parameters, ...parameters }), + identity ?? '', ], - () => service.getListDynamicValues(connectionId, connectorId, operationId, parameters, dynamicState, undefined, operationPath) + () => service.getListDynamicValues(connectionId, connectorId, operationId, parameters, dynamicState, undefined, operationPath, identity) ); }; diff --git a/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts b/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts index 281482edb0c..a0b080cd45e 100644 --- a/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts +++ b/libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts @@ -113,13 +113,16 @@ export async function getDynamicValues( shouldEncodeBasedOnMetadata ); + // Thread the selected identity from connectionReference if present + const selectedIdentity = connectionReference?.connectionProperties?.authentication?.identity; return getListDynamicValues( connectionReference?.connection.id, operationInfo.connectorId, operationInfo.operationId, operationParameters, dynamicState, - operationInfo.operationPath + operationInfo.operationPath, + selectedIdentity ); } if (isLegacyDynamicValuesExtension(definition)) { @@ -739,7 +742,7 @@ function loadUnknownManifestBasedParameters( if (isNullOrEmpty(input) || !isObject(input)) { if (!knownKeys.has(keyPrefix)) { // Add a generic unknown parameter. - // eslint-disable-next-line no-param-reassign + result[keyPrefix] = { key: keyPrefix, name: previousKeyPath, @@ -947,7 +950,6 @@ function evaluateTemplateExpressions( function evaluateParameter(parameter: SerializedParameter, evaluator: ExpressionEvaluator): void { const value = parameter.value; if (isTemplateExpression(value)) { - // eslint-disable-next-line no-param-reassign parameter.value = evaluator.evaluate(value); } if (value !== parameter.value) { diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts index a7dab84d3e5..000233af8e7 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/connector.ts @@ -68,7 +68,8 @@ export interface IConnectorService { parameters: Record, dynamicState: any, isManagedIdentityConnection?: boolean, - operationPath?: string + operationPath?: string, + identity?: string ): Promise; /** diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts index 76bbe5aec79..16d0e6eeaf2 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/__tests__/connector.spec.ts @@ -2,6 +2,7 @@ import { describe, vi, beforeEach, it, expect } from 'vitest'; import { ConsumptionConnectorService } from '../connector'; import type { IHttpClient } from '../../httpClient'; import { InitConnectionService } from '../../connection'; +import { InitWorkflowService } from '../../workflow'; import type { Connection } from '../../../../utils/src'; describe('ConsumptionConnectorService', () => { @@ -248,6 +249,9 @@ describe('ConsumptionConnectorService', () => { }, } as unknown as Connection), } as any); + InitWorkflowService({ + getAppIdentity: vi.fn().mockReturnValue({ type: 'SystemAssigned' }), + } as any); }); it('should send managedConnection shape for non-builtin connections', async () => { @@ -268,6 +272,11 @@ describe('ConsumptionConnectorService', () => { expect(content.managedConnection).toEqual({ connection: { id: managedConnectionId }, + connectionProperties: { + authentication: { + type: 'ManagedServiceIdentity', + }, + }, }); expect(content.mcpServerPath).toBe('/mcp/path'); expect(content.connection).toBeUndefined(); @@ -320,6 +329,12 @@ describe('ConsumptionConnectorService', () => { return (connectorService as any)._buildMcpAuthentication(props); }; + beforeEach(() => { + InitWorkflowService({ + getAppIdentity: vi.fn().mockReturnValue({ type: 'SystemAssigned' }), + } as any); + }); + it('should return undefined for None auth type', () => { expect(buildAuth({ authenticationType: 'None' })).toBeUndefined(); }); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts index 1d75f2c61b3..c523c8c4243 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/consumption/connector.ts @@ -1,5 +1,13 @@ import type { OpenAPIV2 } from '../../../utils/src'; -import { ArgumentException, UnsupportedException, optional, equals, getResourceName } from '../../../utils/src'; +import { + ArgumentException, + UnsupportedException, + optional, + equals, + getResourceName, + isArmResourceId, + ResourceIdentityType, +} from '../../../utils/src'; import type { BaseConnectorServiceOptions } from '../base'; import { BaseConnectorService } from '../base'; import { ConnectionService } from '../connection'; @@ -7,6 +15,7 @@ import type { ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExt import { pathCombine, unwrapPaginatedResponse } from '../helpers'; import { LoggerService } from '../logger'; import { LogEntryLevel } from '../logging/logEntry'; +import { WorkflowService } from '../workflow'; interface ConsumptionConnectorServiceOptions extends BaseConnectorServiceOptions { workflowReferenceId: string; @@ -54,7 +63,8 @@ export class ConsumptionConnectorService extends BaseConnectorService { parameters: Record, dynamicState: any, isManagedIdentityConnection?: boolean, - operationPath?: string + operationPath?: string, + identity?: string ): Promise { const { apiVersion, httpClient } = this.options; const { operationId: dynamicOperation, apiType } = dynamicState; @@ -103,11 +113,18 @@ export class ConsumptionConnectorService extends BaseConnectorService { connection: connectionData, mcpServerPath: operationPath, }; - } else if (isRealConnectionId) { - // Managed MCP connection — send managed connection reference + } else if (isRealConnectionId && isArmResourceId(connectionId)) { + // Managed MCP connection — build full managed connection reference with identity + const connectionProperties = { + authentication: { + type: 'ManagedServiceIdentity', + ...optional('identity', identity), + }, + }; content = { managedConnection: { connection: { id: connectionId }, + connectionProperties, }, mcpServerPath: operationPath, }; @@ -268,8 +285,20 @@ export class ConsumptionConnectorService extends BaseConnectorService { authentication['value'] = connectionProperties['value']; } else if (mappedAuthType === 'ManagedServiceIdentity') { authentication['audience'] = connectionProperties['audience']; - if (connectionProperties['identity']) { - authentication['identity'] = connectionProperties['identity']; + // Identity may be stored in parameterValues (round-tripped from workflow definition) + // or must be derived from the workflow's managed identity configuration. + const storedIdentity = connectionProperties['identity']; + if (storedIdentity) { + authentication['identity'] = storedIdentity; + } else { + const appIdentity = WorkflowService().getAppIdentity?.(); + const userIdentity = + equals(appIdentity?.type, ResourceIdentityType.USER_ASSIGNED) && appIdentity?.userAssignedIdentities + ? Object.keys(appIdentity.userAssignedIdentities)[0] + : undefined; + if (userIdentity) { + authentication['identity'] = userIdentity; + } } } diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts index 1397166f222..c00a63384da 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connector.ts @@ -102,7 +102,8 @@ export class StandardConnectorService extends BaseConnectorService { parameters: Record, dynamicState: any, connectionId: string | undefined, - operationPath?: string + operationPath?: string, + identity?: string ): Promise { const { baseUrl, apiVersion, httpClient, getConfiguration } = this.options; const { operationId: dynamicOperation, apiType } = dynamicState; @@ -145,22 +146,26 @@ export class StandardConnectorService extends BaseConnectorService { // Generate connection reference for managed connections when it's not found. const connectionFromService = await ConnectionService().getConnection(connectionId); if (connectionFromService) { - const identity = WorkflowService().getAppIdentity?.(); - const userIdentity = - equals(identity?.type, ResourceIdentityType.USER_ASSIGNED) && identity?.userAssignedIdentities - ? Object.keys(identity.userAssignedIdentities)[0] - : undefined; + // Use explicitly passed identity, otherwise fall back to WorkflowService + const resolvedIdentity = + identity ?? + (() => { + const appIdentity = WorkflowService().getAppIdentity?.(); + return equals(appIdentity?.type, ResourceIdentityType.USER_ASSIGNED) && appIdentity?.userAssignedIdentities + ? Object.keys(appIdentity.userAssignedIdentities)[0] + : undefined; + })(); const properties = connectionFromService.properties as any; let connectionProperties: any; try { const connector = await ConnectionService().getConnector(properties.api.id); - connectionProperties = getConnectionProperties(connector, userIdentity); + connectionProperties = getConnectionProperties(connector, resolvedIdentity); } catch { connectionProperties = { authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', userIdentity), + ...optional('identity', resolvedIdentity), }, }; } @@ -169,7 +174,7 @@ export class StandardConnectorService extends BaseConnectorService { connection: { id: connectionId }, authentication: { type: 'ManagedServiceIdentity', - ...optional('identity', userIdentity), + ...optional('identity', resolvedIdentity), }, connectionRuntimeUrl: properties.connectionRuntimeUrl ?? '', connectionProperties,