Skip to content

Commit

Permalink
Merge pull request #385 from microsoft/dev
Browse files Browse the repository at this point in the history
1.0.10 release
  • Loading branch information
blackchoey authored Feb 17, 2025
2 parents 3ef77fd + 45c829b commit e892486
Show file tree
Hide file tree
Showing 21 changed files with 1,158 additions and 72 deletions.
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 32 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-apimanagement",
"displayName": "Azure API Management",
"description": "An Azure API Management extension for Visual Studio Code.",
"version": "1.0.9",
"version": "1.0.10",
"publisher": "ms-azuretools",
"icon": "resources/apim-icon-newone.png",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
Expand Down Expand Up @@ -190,13 +190,19 @@
"title": "%azureApiManagement.openInPortal%",
"category": "Azure API Management"
},
{
"command": "azureApiManagement.selectTenant",
"title": "%azureApiManagement.selectTenant%",
"category": "Azure API Management"
},
{
"command": "azureApiManagement.selectSubscriptions",
"title": "Select Subscription...",
"title": "%azureApiManagement.selectSubscriptions%",
"icon": {
"light": "resources/light/filter.svg",
"dark": "resources/dark/filter.svg"
}
},
"category": "Azure API Management"
},
{
"command": "azureApiManagement.Refresh",
Expand Down Expand Up @@ -782,6 +788,27 @@
"type": "boolean",
"default": false,
"description": "%azureApiManagement.advancedPolicyAuthoringExperience%"
},
"azureApiManagement.selectedSubscriptions": {
"type": "array",
"description": "%azureApiManagement.selectAzureSubscriptions%",
"items": {
"type": "string"
}
},
"azureApiManagement.selectedTenant": {
"type": "object",
"description": "%azureApiManagement.selectAzureTenant%",
"properties": {
"name": {
"type": "string",
"description": "%azureApiManagement.selectAzureTenant.tenantName%"
},
"id": {
"type": "string",
"description": "%azureApiManagement.selectAzureTenant.tenantId%"
}
}
}
}
}
Expand Down Expand Up @@ -834,6 +861,7 @@
"@azure/arm-apimanagement": "^9.2.0",
"@azure/arm-appservice": "^15.0.0",
"@azure/arm-resources": "^4.0.0",
"@azure/arm-resources-subscriptions": "^2.1.0",
"@azure/ms-rest-nodeauth": "^3.1.1",
"@microsoft/vscode-azext-azureutils": "^3.1.2",
"@microsoft/vscode-azext-utils": "^2.5.12",
Expand All @@ -857,10 +885,9 @@
"xregexp": "^4.3.0"
},
"extensionDependencies": [
"ms-vscode.azure-account",
"humao.rest-client"
],
"overrides": {
"fsevents": "~2.3.2"
}
}
}
8 changes: 7 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
"azureApiManagement.openExtensionWorkspaceFolder": "Open extension workspace folder",
"azureApiManagement.initializeExtensionWorkspaceFolder": "Initialize extension workspace folder",
"azureApiManagement.advancedPolicyAuthoringExperience": "(Experimental Feature) Enables advanced policy authoring experience.",
"azureApiManagement.selectAzureSubscriptions": "Selected Azure subscriptions",
"azureApiManagement.selectAzureTenant": "Selected Azure Tenant",
"azureApiManagement.selectAzureTenant.tenantName": "tenant name",
"azureApiManagement.selectAzureTenant.tennatId": "tenant id",
"azureApiManagement.extractService": "Extract Service",
"azureApiManagement.extractApi": "Extract API",
"azureApiManagement.importFunctionApp": "Import from Azure Functions",
Expand Down Expand Up @@ -56,5 +60,7 @@
"azureApiManagement.deleteAuthorizationAccessPolicy": "Delete Access Policy",
"azureApiManagement.showArmAuthorizationProvider": "Edit Authorization Provider",
"azureApiManagement.showArmAuthorization": "Edit Authorization",
"azureApiManagement.showArmAuthorizationAccessPolicy": "Edit Authorization Access Policy"
"azureApiManagement.showArmAuthorizationAccessPolicy": "Edit Authorization Access Policy",
"azureApiManagement.selectTenant": "Select Tenant...",
"azureApiManagement.selectSubscriptions": "Select Subscription..."
}
56 changes: 56 additions & 0 deletions src/azure/azureLogin/authTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { AuthenticationSession, Event } from "vscode";
import { GeneralUtils } from "../../utils/generalUtils";

export enum SignInStatus {
Initializing = 'Initializing',
SigningIn = 'SigningIn',
SignedIn = 'SignedIn',
SignedOut = 'SignedOut',
}

export enum SelectionType {
Filtered,
All,
AllIfNoFilters,
}

export interface SubscriptionFilter {
tenantId: string;
subscriptionId: string;
}

export type TokenInfo = {
token: string;
expiry: Date;
};

export type AzureAuthenticationSession = AuthenticationSession & {
tenantId: string;
};

export type Tenant = {
name: string;
id: string;
};

export type GetAuthSessionOptions = {
applicationClientId?: string;
scopes?: string[];
};

export type AzureSessionProvider = {
signIn(): Promise<void>;
signInStatus: SignInStatus;
availableTenants: Tenant[];
selectedTenant: Tenant | null;
signInStatusChangeEvent: Event<SignInStatus>;
getAuthSession(options?: GetAuthSessionOptions): Promise<GeneralUtils.Errorable<AzureAuthenticationSession>>;
dispose(): void;
};

export type ReadyAzureSessionProvider = AzureSessionProvider & {
signInStatus: SignInStatus.SignedIn;
selectedTenant: Tenant;
};
149 changes: 149 additions & 0 deletions src/azure/azureLogin/azureAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { SubscriptionClient, TenantIdDescription } from "@azure/arm-resources-subscriptions";
import { TokenCredential } from "@azure/core-auth";
import { AuthenticationSession, QuickPickItem, Uri, env, window, workspace, ConfigurationTarget } from "vscode";
import { UiStrings } from "../../uiStrings";
import { GeneralUtils } from "../../utils/generalUtils";
import { SelectionType, SignInStatus, SubscriptionFilter, Tenant } from "./authTypes";
import { AzureAuth } from "./azureAuth";
import { AzureSessionProviderHelper } from "./azureSessionProvider";
import { AzureSubscriptionHelper } from "./subscriptions";
import { AzureAccountUrl, extensionPrefix } from "../../constants";
import { AzureLoginConstantString } from "./constants";
export namespace AzureAccount {
export async function signInToAzure(): Promise<void> {
await AzureSessionProviderHelper.getSessionProvider().signIn();
}

export function getSelectedTenant(): Tenant | undefined {
return workspace.getConfiguration(extensionPrefix).get<Tenant>(AzureLoginConstantString.selectedTenant);
}

export async function updateSelectedTenant(value?: Tenant): Promise<void> {
await workspace.getConfiguration(extensionPrefix).update(AzureLoginConstantString.selectedTenant, value, ConfigurationTarget.Global, true);
}

export async function selectTenant(): Promise<void> {
const sessionProvider = AzureSessionProviderHelper.getSessionProvider();
if (sessionProvider.signInStatus !== SignInStatus.SignedIn) {
window.showInformationMessage(UiStrings.SelectTenantBeforeSignIn);
return;
}

if (sessionProvider.availableTenants.length === 1) {
sessionProvider.selectedTenant = sessionProvider.availableTenants[0];

// If this tenant wasn't previously selected, it was probably because it wasn't immediately
// accessible (the user's current token didn't have access to it). Calling getAuthSession
// will prompt the user to re-authenticate if necessary.
const sessionResult = await sessionProvider.getAuthSession();
if (GeneralUtils.failed(sessionResult)) {
window.showErrorMessage(sessionResult.error);
}

return;
}

const selectedTenant = await AzureAuth.quickPickTenant(sessionProvider.availableTenants);
if (!selectedTenant) {
window.showInformationMessage(UiStrings.NoTenantSelected);
return;
}

sessionProvider.selectedTenant = selectedTenant;
await updateSelectedTenant(selectedTenant);
}

type SubscriptionQuickPickItem = QuickPickItem & { subscription: SubscriptionFilter };

export async function selectSubscriptions(): Promise<void> {
const sessionProvider = await AzureAuth.getReadySessionProvider();
if (GeneralUtils.failed(sessionProvider)) {
window.showErrorMessage(sessionProvider.error);
return;
}

const allSubscriptions = await AzureSubscriptionHelper.getSubscriptions(sessionProvider.result, SelectionType.All);
if (GeneralUtils.failed(allSubscriptions)) {
window.showErrorMessage(allSubscriptions.error);
return;
}

if (allSubscriptions.result.length === 0) {
const noSubscriptionsFound = UiStrings.NoSubscriptionsFoundAndSetup;
const setupAccount = UiStrings.SetUpAzureAccount;
const response = await window.showInformationMessage(noSubscriptionsFound, setupAccount);
if (response === setupAccount) {
env.openExternal(Uri.parse(AzureAccountUrl.azureMicrosoftLink));
}

return;
}

const session = await sessionProvider.result.getAuthSession();
if (GeneralUtils.failed(session)) {
window.showErrorMessage(session.error);
return;
}

const filteredSubscriptions = await AzureSubscriptionHelper.getFilteredSubscriptions();

const subscriptionsInCurrentTenant = filteredSubscriptions.filter(
(sub) => sub.tenantId === session.result.tenantId,
);
const subscriptionsInOtherTenants = filteredSubscriptions.filter((sub) => sub.tenantId !== session.result.tenantId);

const quickPickItems: SubscriptionQuickPickItem[] = allSubscriptions.result.map((sub) => {
return {
label: sub.displayName || "",
description: sub.subscriptionId,
picked: subscriptionsInCurrentTenant.some((filtered) => filtered.subscriptionId === sub.subscriptionId),
subscription: {
subscriptionId: sub.subscriptionId || "",
tenantId: sub.tenantId || "",
},
};
});

const selectedItems = await window.showQuickPick(quickPickItems, {
canPickMany: true,
placeHolder: UiStrings.SelectSubscription,
});

if (!selectedItems) {
return;
}

const newFilteredSubscriptions = [
...selectedItems.map((item) => item.subscription),
...subscriptionsInOtherTenants, // Retain filters in any other tenants.
];

await AzureSubscriptionHelper.setFilteredSubscriptions(newFilteredSubscriptions);
}
export async function getTenants(session: AuthenticationSession): Promise<GeneralUtils.Errorable<Tenant[]>> {
const armEndpoint = AzureAuth.getConfiguredAzureEnv().resourceManagerEndpointUrl;
const credential: TokenCredential = {
getToken: async () => {
return { token: session.accessToken, expiresOnTimestamp: 0 };
},
};
const subscriptionClient = new SubscriptionClient(credential, { endpoint: armEndpoint });

const tenantsResult = await AzureAuth.listAll(subscriptionClient.tenants.list());
return GeneralUtils.errMap(tenantsResult, (t) => t.filter(isTenant).map((t) => ({ name: t.displayName, id: t.tenantId })));
}
export function findTenant(tenants: Tenant[], tenantId: string): Tenant | null {
return tenants.find((t) => t.id === tenantId) || null;
}
export function isTenant(tenant: TenantIdDescription): tenant is { tenantId: string; displayName: string } {
return tenant.tenantId !== undefined && tenant.displayName !== undefined;
}
export function getIdString(tenants: Tenant[]): string {
return tenants
.map((t) => t.id)
.sort()
.join(",");
}
}
Loading

0 comments on commit e892486

Please sign in to comment.