Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions libs/designer-v2/src/lib/core/queries/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export const getListDynamicValues = async (
operationId: string,
parameters: Record<string, any>,
dynamicState: any,
operationPath?: string
operationPath?: string,
identity?: string
): Promise<ListDynamicValue[]> => {
const queryClient = getReactQueryClient();
const service = ConnectorService();
Expand All @@ -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)
);
};

Expand Down
8 changes: 5 additions & 3 deletions libs/designer-v2/src/lib/core/utils/parameters/dynamicdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export interface IConnectorService {
parameters: Record<string, any>,
dynamicState: any,
isManagedIdentityConnection?: boolean,
operationPath?: string
operationPath?: string,
identity?: string
): Promise<ListDynamicValue[]>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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();
Expand Down Expand Up @@ -320,6 +329,12 @@ describe('ConsumptionConnectorService', () => {
return (connectorService as any)._buildMcpAuthentication(props);
};

beforeEach(() => {
InitWorkflowService({
getAppIdentity: vi.fn().mockReturnValue({ type: 'SystemAssigned' }),
} as any);
});
Comment thread
rllyy97 marked this conversation as resolved.

it('should return undefined for None auth type', () => {
expect(buildAuth({ authenticationType: 'None' })).toBeUndefined();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
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';
import type { ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExtension, TreeDynamicValue } from '../connector';
import { pathCombine, unwrapPaginatedResponse } from '../helpers';
import { LoggerService } from '../logger';
import { LogEntryLevel } from '../logging/logEntry';
import { WorkflowService } from '../workflow';

interface ConsumptionConnectorServiceOptions extends BaseConnectorServiceOptions {
workflowReferenceId: string;
Expand Down Expand Up @@ -54,7 +63,8 @@ export class ConsumptionConnectorService extends BaseConnectorService {
parameters: Record<string, any>,
dynamicState: any,
isManagedIdentityConnection?: boolean,
operationPath?: string
operationPath?: string,
identity?: string
): Promise<ListDynamicValue[]> {
const { apiVersion, httpClient } = this.options;
const { operationId: dynamicOperation, apiType } = dynamicState;
Expand Down Expand Up @@ -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,
};
Comment thread
rllyy97 marked this conversation as resolved.
Expand Down Expand Up @@ -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;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export class StandardConnectorService extends BaseConnectorService {
parameters: Record<string, any>,
dynamicState: any,
connectionId: string | undefined,
operationPath?: string
operationPath?: string,
identity?: string
): Promise<ListDynamicValue[]> {
const { baseUrl, apiVersion, httpClient, getConfiguration } = this.options;
const { operationId: dynamicOperation, apiType } = dynamicState;
Expand Down Expand Up @@ -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),
},
};
}
Expand All @@ -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,
Expand Down
Loading