diff --git a/api/src/auth/credentialManager/AzExtCredentialManager.ts b/api/src/auth/credentialManager/AzExtCredentialManager.ts new file mode 100644 index 00000000..3a126e43 --- /dev/null +++ b/api/src/auth/credentialManager/AzExtCredentialManager.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface AzExtCredentialManager { + createCredential(extensionId: string): string | Promise; + verifyCredential(credential: string, extensionId?: string): boolean | Promise; + + /** + * Masks sensitive information from a given string to ensure private credential management keys from the manager are not exposed. + * @param data - The string to be processed. + * @returns The same string stripped of any sensitive credentials. + */ + maskCredentials(data: string): string; +} diff --git a/api/src/auth/credentialManager/AzExtUUIDCredentialManager.ts b/api/src/auth/credentialManager/AzExtUUIDCredentialManager.ts new file mode 100644 index 00000000..11529818 --- /dev/null +++ b/api/src/auth/credentialManager/AzExtUUIDCredentialManager.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from "crypto"; +import { maskValue } from "../../utils/maskValue"; +import { AzExtCredentialManager } from "./AzExtCredentialManager"; + +/** + * A simple, light-weight credential manager that issues and tracks randomly generated UUIDs for extension verification. + */ +export class AzExtUUIDCredentialManager implements AzExtCredentialManager { + #uuidMap: Map; + + constructor() { + this.#uuidMap = new Map(); + } + + createCredential(extensionId: string): string { + const uuid: string = crypto.randomUUID(); + this.#uuidMap.set(extensionId, uuid); + return uuid; + } + + verifyCredential(credential: string, extensionId: string): boolean { + if (!credential || !extensionId) { + return false; + } + return credential === this.#uuidMap.get(extensionId); + } + + maskCredentials(data: string): string { + for (const uuid of this.#uuidMap.values()) { + data = maskValue(data, uuid); + } + return data; + } +} diff --git a/api/src/extensionApi.ts b/api/src/extensionApi.ts index b4738eb9..6d3e6fba 100644 --- a/api/src/extensionApi.ts +++ b/api/src/extensionApi.ts @@ -9,6 +9,14 @@ import { AzureExtensionApi } from "./utils/apiUtils"; /** * The current (v2) Azure Resources extension API. */ -export interface AzureResourcesExtensionApi extends AzureExtensionApi { +export interface AzureResourcesExtensionApi extends Omit { resources: ResourcesApi; } + +/** + * The authentication layer (v4) protecting the core Azure Resources extension API. + */ +export interface AzureResourcesExtensionAuthApi extends Omit { + getAzureResourcesApis(clientExtensionId: string, azureResourcesCredential: string, azureResourcesApiVersions: string[]): Promise<(AzureExtensionApi | undefined)[]>; + createAzureResourcesApiSession(clientExtensionId: string, clientExtensionVersion: string, clientExtensionCredential: string): Promise; +} diff --git a/api/src/utils/apiUtils.ts b/api/src/utils/apiUtils.ts index aacb1976..f26c5cc0 100644 --- a/api/src/utils/apiUtils.ts +++ b/api/src/utils/apiUtils.ts @@ -10,6 +10,16 @@ export interface AzureExtensionApi { * The API version for this extension. It should be versioned separately from the extension and ideally remains backwards compatible. */ apiVersion: string; + + /** + * Optional endpoint which Azure client extensions should implement in order to receive an Azure Resources API session. + * See: https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/src/auth/README.md + * + * @param azureResourcesCredential - The credential to use when requesting the Azure Resources API + * @param clientCredential - The client verification credential initially generated by the client and passed to the Azure Resources API when requesting a new session. + * This credential is used to verify that the real Azure Resources extension is the one providing back the session credential. + */ + receiveAzureResourcesApiSession?(azureResourcesCredential: string, clientCredential: string): void | Promise; } export interface GetApiOptions { @@ -62,20 +72,12 @@ export namespace apiUtils { } /** - * Get extension exports for the extension with the given id. Activates extension first if needed. + * Activates an extension and returns its exports. * * @returns `undefined` if the extension is not installed */ export async function getExtensionExports(extensionId: string): Promise { const extension: vscode.Extension | undefined = vscode.extensions.getExtension(extensionId); - if (extension) { - if (!extension.isActive) { - await extension.activate(); - } - - return extension.exports; - } - - return undefined; + return await extension?.activate(); } } diff --git a/api/src/utils/getApi.ts b/api/src/utils/getApi.ts index 7ed99e4d..718aa953 100644 --- a/api/src/utils/getApi.ts +++ b/api/src/utils/getApi.ts @@ -7,6 +7,10 @@ import * as vscode from 'vscode'; import type { AzureResourcesExtensionApi } from '../extensionApi'; import { apiUtils, GetApiOptions } from "./apiUtils"; +/** + * @deprecated The Azure Resources core API should be accessed through the new auth layer. + * See: https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/src/auth/README.md + * */ export async function getAzureResourcesExtensionApi(extensionContext: vscode.ExtensionContext, apiVersionRange: '2.0.0', options?: GetApiOptions): Promise { return apiUtils.getAzureExtensionApi(extensionContext, 'ms-azuretools.vscode-azureresourcegroups', apiVersionRange, options); } diff --git a/api/src/utils/maskValue.ts b/api/src/utils/maskValue.ts new file mode 100644 index 00000000..7cd96f1f --- /dev/null +++ b/api/src/utils/maskValue.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Sourced from @microsoft/vscode-azext-utils +export function maskValue(data: string, valueToMask: string | undefined): string { + if (valueToMask) { + const formsOfValue: string[] = [valueToMask, encodeURIComponent(valueToMask)]; + for (const v of formsOfValue) { + data = data.replace(new RegExp(escape(v), 'gi'), '---'); + } + } + return data; +} diff --git a/src/api/auth/createApiSession/CreateApiSessionInternalContext.ts b/src/api/auth/createApiSession/CreateApiSessionInternalContext.ts new file mode 100644 index 00000000..8670231e --- /dev/null +++ b/src/api/auth/createApiSession/CreateApiSessionInternalContext.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzExtCredentialManager } from "api/src/auth/credentialManager/AzExtCredentialManager"; +import { AzureExtensionApi } from "api/src/utils/apiUtils"; + +export interface CreateApiSessionInternalContext extends IActionContext { + credentialManager: AzExtCredentialManager; + clientExtensionId: string; + clientExtensionVersion: string; + clientExtensionCredential: string; + + /** + * An optional API provider to be used in lieu of the VS Code API `vscode.extension.getExtension()`. + * This should _NOT_ be defined in production environments. + */ + clientApiProvider?: CreateApiSessionExtensionProvider; +} + +export type CreateApiSessionExtensionProvider = { getApi(clientExtensionId: string, clientExtensionVersion: string): AzureExtensionApi }; diff --git a/src/api/auth/createApiSession/createApiSessionInternal.ts b/src/api/auth/createApiSession/createApiSessionInternal.ts new file mode 100644 index 00000000..f0e12696 --- /dev/null +++ b/src/api/auth/createApiSession/createApiSessionInternal.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IParsedError, maskUserInfo, parseError } from "@microsoft/vscode-azext-utils"; +import { apiUtils } from "../../../../api/src/utils/apiUtils"; +import { azureExtensions } from "../../../azureExtensions"; +import { ext } from '../../../extensionVariables'; +import { localize } from "../../../utils/localize"; +import { CreateApiSessionInternalContext } from './CreateApiSessionInternalContext'; + +const allowedExtensionIds = new Set(azureExtensions.map(extension => `${extension.publisher.toLowerCase()}.${extension.name.toLowerCase()}`)); + +export async function createApiSessionInternal(context: CreateApiSessionInternalContext): Promise { + context.telemetry.properties.clientExtensionId = context.clientExtensionId; + context.telemetry.properties.clientExtensionVersion = context.clientExtensionVersion; + + if (!allowedExtensionIds.has(context.clientExtensionId)) { + context.telemetry.properties.allowedExtension = 'false'; + ext.outputChannel.warn(localize('createResourcesApiSession.denied', 'Azure Resources API session denied for extension "{0}".', context.clientExtensionId)); + throw new Error('🧙 No, thank you! We don\'t want any more visitors, well-wishers, or distant relations! 🧝🦶'); + } + + context.telemetry.properties.allowedExtension = 'true'; + + try { + const clientApi = context.clientApiProvider?.getApi(context.clientExtensionId, context.clientExtensionVersion) ?? + await apiUtils.getAzureExtensionApi(ext.context, context.clientExtensionId, context.clientExtensionVersion); + + const azureResourcesCredential: string = await context.credentialManager.createCredential(context.clientExtensionId); + await clientApi.receiveAzureResourcesApiSession?.(azureResourcesCredential, context.clientExtensionCredential); + } catch (err) { + const failed: string = localize('createResourcesApiSession.failed', 'Failed to create Azure Resources API session for extension "{0}".', context.clientExtensionId); + ext.outputChannel.error(failed); + + const perr: IParsedError = parseError(err); + const perrMessage: string = context.credentialManager.maskCredentials(perr.message); + context.telemetry.properties.createResourcesApiSessionError = maskUserInfo(perrMessage, []); + ext.outputChannel.error(perrMessage); + throw new Error(failed); + } +} diff --git a/src/api/auth/createAuthApiFactory.ts b/src/api/auth/createAuthApiFactory.ts new file mode 100644 index 00000000..95e2f65b --- /dev/null +++ b/src/api/auth/createAuthApiFactory.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { apiUtils, AzureExtensionApiFactory, callWithTelemetryAndErrorHandling, GetApiOptions, IActionContext } from '@microsoft/vscode-azext-utils'; +import { AzExtCredentialManager } from '../../../api/src/auth/credentialManager/AzExtCredentialManager'; +import { AzExtUUIDCredentialManager } from '../../../api/src/auth/credentialManager/AzExtUUIDCredentialManager'; +import { AzureResourcesAuthApiInternal } from '../../hostapi.v4.internal'; +import { createApiSessionInternal } from './createApiSession/createApiSessionInternal'; +import { CreateApiSessionExtensionProvider, CreateApiSessionInternalContext } from './createApiSession/CreateApiSessionInternalContext'; +import { getApiVerifyError, verifyApiSessionInternal } from './verifyApiSession/verifyApiSessionInternal'; +import { VerifyApiSessionInternalContext } from './verifyApiSession/VerifyApiSessionInternalContext'; + +const v4: string = '4.0.0'; + +export type AuthApiFactoryDependencies = { + /** + * An optional credential manager used for issuing and verifying Azure Resources API credentials. If none are supplied, a simple UUID credential manager will be used. + * @test Use this to more easily mock and inspect the behavior of the underlying credential manager. + */ + credentialManager?: AzExtCredentialManager; + /** + * An optional API provider to be used in lieu of the VS Code extension provider `vscode.extension.getExtension()`. + * This should _NOT_ be defined in production environments. + * @test Use this to more easily mock and inject custom client extension API exports. + */ + clientApiProvider?: CreateApiSessionExtensionProvider; +} + +export function createAuthApiFactory(coreApiProvider: apiUtils.AzureExtensionApiProvider, customDependencies?: AuthApiFactoryDependencies): AzureExtensionApiFactory { + const credentialManager = customDependencies?.credentialManager ?? new AzExtUUIDCredentialManager(); + const clientApiProvider = customDependencies?.clientApiProvider; + + return { + apiVersion: v4, + createApi: (options?: GetApiOptions) => { + return { + apiVersion: v4, + + getAzureResourcesApis: async (clientExtensionId: string, azureResourcesCredential: string, azureResourcesApiVersions: string[]) => { + return await callWithTelemetryAndErrorHandling('api.getAzureResourcesApis', async (context: IActionContext) => { + setTelemetryAndErrorHandling(context, options?.extensionId); + + const verified: boolean = await verifyApiSessionInternal(Object.assign(context, { + credentialManager, + clientExtensionId: clientExtensionId?.toLowerCase(), + azureResourcesCredential, + }) satisfies VerifyApiSessionInternalContext); + + if (!verified) { + throw new Error(getApiVerifyError(clientExtensionId)); + } + + return azureResourcesApiVersions + .map((apiVersion) => { + try { + return coreApiProvider.getApi(apiVersion, options); + } catch { + return undefined; + } + }); + + }) ?? []; + }, + + createAzureResourcesApiSession: async (clientExtensionId: string, clientExtensionVersion: string, clientExtensionCredential: string) => { + return await callWithTelemetryAndErrorHandling('api.createAzureResourcesApiSession', async (context: IActionContext) => { + setTelemetryAndErrorHandling(context, options?.extensionId); + + return await createApiSessionInternal(Object.assign(context, { + credentialManager, + clientExtensionId: clientExtensionId?.toLowerCase(), + clientExtensionVersion, + clientExtensionCredential, + clientApiProvider, + }) satisfies CreateApiSessionInternalContext); + }); + }, + + }; + }, + }; +} + +function setTelemetryAndErrorHandling(context: IActionContext, extensionId?: string): void { + context.telemetry.properties.callingExtensionId = extensionId; + context.telemetry.properties.isActivationEvent = 'true'; + context.telemetry.properties.apiVersion = v4; + context.errorHandling.rethrow = true; + context.errorHandling.suppressDisplay = true; + context.errorHandling.suppressReportIssue = true; +} diff --git a/src/api/auth/verifyApiSession/VerifyApiSessionInternalContext.ts b/src/api/auth/verifyApiSession/VerifyApiSessionInternalContext.ts new file mode 100644 index 00000000..185924e6 --- /dev/null +++ b/src/api/auth/verifyApiSession/VerifyApiSessionInternalContext.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzExtCredentialManager } from "api/src/auth/credentialManager/AzExtCredentialManager"; + +export interface VerifyApiSessionInternalContext extends IActionContext { + credentialManager: AzExtCredentialManager; + clientExtensionId: string; + azureResourcesCredential: string; +} diff --git a/src/api/auth/verifyApiSession/verifyApiSessionInternal.ts b/src/api/auth/verifyApiSession/verifyApiSessionInternal.ts new file mode 100644 index 00000000..08636bef --- /dev/null +++ b/src/api/auth/verifyApiSession/verifyApiSessionInternal.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { l10n } from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { VerifyApiSessionInternalContext } from './VerifyApiSessionInternalContext'; + +export const getApiVerifyError = (clientExtensionId?: string) => `${clientExtensionId || 'Unknown Extension'} - 🧙 YOU SHALL NOT PASS! 🔥`; + +export async function verifyApiSessionInternal(context: VerifyApiSessionInternalContext): Promise { + const apiVerifyError: string = getApiVerifyError(context.clientExtensionId); + + if (!context.clientExtensionId || !context.azureResourcesCredential) { + context.telemetry.properties.deniedReason = 'missingDetails'; + throw new Error(apiVerifyError); + } + + context.telemetry.properties.clientExtensionId = context.clientExtensionId; + + let verified: boolean | undefined; + try { + verified = await context.credentialManager.verifyCredential(context.azureResourcesCredential, context.clientExtensionId); + } catch { /** Skip; handle below */ } + + if (!verified) { + context.telemetry.properties.deniedReason = 'failedVerification'; + ext.outputChannel.warn(l10n.t('Extension claiming to be "{0}" provided an access token that failed verification.', context.clientExtensionId)); + throw new Error(apiVerifyError); + } + + return verified; +} diff --git a/src/azureExtensions.ts b/src/azureExtensions.ts index c5cca88e..e870d9e3 100644 --- a/src/azureExtensions.ts +++ b/src/azureExtensions.ts @@ -7,9 +7,13 @@ import { AppResource } from "@microsoft/vscode-azext-utils/hostapi"; import { AzExtResourceType } from "../api/src/index"; import { localize } from "./utils/localize"; +const msAzureToolsPublisher: string = 'ms-azuretools'; + +// IMPORTANT: These are deduped and used to build the final list of allowed extensions that can access the core Azure Resources API. export const azureExtensions: IAzExtMetadata[] = [ { name: 'vscode-azurefunctions', + publisher: msAzureToolsPublisher, label: 'Functions', resourceTypes: [ AzExtResourceType.DurableTaskScheduler, @@ -23,6 +27,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azureappservice', + publisher: msAzureToolsPublisher, label: 'App Service', resourceTypes: [ AzExtResourceType.AppServices @@ -35,6 +40,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurearcenabledmachines', + publisher: msAzureToolsPublisher, label: 'Azure Arc-enabled machines', resourceTypes: [ AzExtResourceType.ArcEnabledMachines @@ -43,6 +49,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurestaticwebapps', + publisher: msAzureToolsPublisher, label: 'Static Web Apps', resourceTypes: [ AzExtResourceType.StaticWebApps @@ -55,12 +62,14 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azureresourcegroups', + publisher: msAzureToolsPublisher, label: 'Resource Groups', resourceTypes: [], reportIssueCommandId: 'azureResourceGroups.reportIssue' }, { name: 'vscode-azurestorage', + publisher: msAzureToolsPublisher, label: 'Storage', resourceTypes: [ AzExtResourceType.StorageAccounts @@ -69,6 +78,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurevirtualmachines', + publisher: msAzureToolsPublisher, label: 'Virtual Machines', resourceTypes: [ AzExtResourceType.VirtualMachines @@ -77,6 +87,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-cosmosdb', + publisher: msAzureToolsPublisher, label: 'Databases', resourceTypes: [ AzExtResourceType.AzureCosmosDb, @@ -87,6 +98,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-documentdb', + publisher: msAzureToolsPublisher, label: 'Databases', resourceTypes: [ AzExtResourceType.AzureCosmosDbForMongoDbRu, @@ -95,6 +107,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurecontainerapps', + publisher: msAzureToolsPublisher, label: 'Container Apps', resourceTypes: [ AzExtResourceType.ContainerAppsEnvironment, @@ -112,7 +125,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurelogicapps', - publisher: 'ms-azuretools', + publisher: msAzureToolsPublisher, label: 'Logic Apps', resourceTypes: [ AzExtResourceType.LogicApp, @@ -125,6 +138,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azurewebpubsub', + publisher: msAzureToolsPublisher, label: 'Web PubSub', resourceTypes: [ AzExtResourceType.WebPubSub @@ -133,6 +147,7 @@ export const azureExtensions: IAzExtMetadata[] = [ }, { name: 'vscode-azureresourcegroups', + publisher: msAzureToolsPublisher, label: 'Managed Identity', resourceTypes: [ AzExtResourceType.ManagedIdentityUserAssignedIdentities @@ -165,7 +180,7 @@ export const legacyTypeMap: Partial> = { export interface IAzExtMetadata { name: string; label: string; - publisher?: string; + publisher: string; resourceTypes: AzExtResourceType[]; tutorial?: IAzExtTutorial; reportIssueCommandId?: string; diff --git a/src/extension.ts b/src/extension.ts index 4d309777..03556a94 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,10 +11,10 @@ import { AzureSubscription } from 'api/src'; import { GetApiOptions, apiUtils } from 'api/src/utils/apiUtils'; import * as vscode from 'vscode'; import { AzExtResourceType } from '../api/src/AzExtResourceType'; -import { TestApi } from './testApi'; import { DefaultAzureResourceProvider } from './api/DefaultAzureResourceProvider'; import { ResourceGroupsExtensionManager } from './api/ResourceGroupsExtensionManager'; import { ActivityLogResourceProviderManager, AzureResourceProviderManager, TenantResourceProviderManager, WorkspaceResourceProviderManager } from './api/ResourceProviderManagers'; +import { createAuthApiFactory } from './api/auth/createAuthApiFactory'; import { InternalAzureResourceGroupsExtensionApi } from './api/compatibility/AzureResourceGroupsExtensionApi'; import { CompatibleAzExtTreeDataProvider } from './api/compatibility/CompatibleAzExtTreeDataProvider'; import { createCompatibilityPickAppResource } from './api/compatibility/pickAppResource'; @@ -36,6 +36,7 @@ import { AzureResourcesApiInternal } from './hostapi.v2.internal'; import { ManagedIdentityBranchDataProvider } from './managedIdentity/ManagedIdentityBranchDataProvider'; import { survey } from './nps'; import { getSubscriptionProviderFactory } from './services/getSubscriptionProviderFactory'; +import { TestApi } from './testApi'; import { BranchDataItemCache } from './tree/BranchDataItemCache'; import { HelpTreeItem } from './tree/HelpTreeItem'; import { TreeDataItem } from './tree/ResourceGroupsItem'; @@ -211,7 +212,7 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo const getSubscriptions: (filter: boolean) => Promise = async (filter: boolean) => { return await (await ext.subscriptionProviderFactory()).getSubscriptions(filter); }; - const apiFactories: AzureExtensionApiFactory[] = [ + const coreApiFactories: AzureExtensionApiFactory[] = [ { apiVersion: InternalAzureResourceGroupsExtensionApi.apiVersion, createApi: () => new InternalAzureResourceGroupsExtensionApi({ @@ -243,6 +244,7 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo * Dependent extensions should rely on this API signal rather than the extension version. * * This temporary API will be removed in a future version once the migration is complete. + * See: https://github.com/microsoft/vscode-azureresourcegroups/pull/1223 */ { apiVersion: "3.0.0", @@ -276,10 +278,18 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo }, }), }; - apiFactories.push(testApiFactory); + coreApiFactories.push(testApiFactory); } - return createApiProvider(apiFactories); + return createApiProvider( + [ + // Todo: Remove once extension clients finish migrating + ...coreApiFactories, + + // This will eventually be the only part of the API exposed publically + createAuthApiFactory(createApiProvider(coreApiFactories)), + ] + ); } export function deactivate(): void { diff --git a/src/hostapi.v2.internal.ts b/src/hostapi.v2.internal.ts index d0f40988..fd55855c 100644 --- a/src/hostapi.v2.internal.ts +++ b/src/hostapi.v2.internal.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { ActivityApi } from '@microsoft/vscode-azext-utils/activity'; import * as vscode from 'vscode'; import { AzureResource, AzureResourcesExtensionApi, AzureSubscription, ResourceProvider, ResourcesApi } from "../api/src/index"; diff --git a/src/hostapi.v4.internal.ts b/src/hostapi.v4.internal.ts new file mode 100644 index 00000000..cd0a0fb1 --- /dev/null +++ b/src/hostapi.v4.internal.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureResourcesExtensionAuthApi } from "../api/src/index"; + +export type AzureResourcesAuthApiInternal = AzureResourcesExtensionAuthApi;