diff --git a/.azure-pipelines/common/test.yml b/.azure-pipelines/common/test.yml
index 43716d2..7f9e4d9 100644
--- a/.azure-pipelines/common/test.yml
+++ b/.azure-pipelines/common/test.yml
@@ -11,13 +11,13 @@ steps:
condition: in(variables['agent.os'], 'Windows_NT')
displayName: 'Use Python on Windows' # specific version for Windows: https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
inputs:
- versionSpec: 3.9.9
+ versionSpec: 3.9.13
- task: UsePythonVersion@0
condition: in(variables['agent.os'], 'Darwin', 'Linux')
displayName: 'Use Python 3.9.10' # specific version for macOS: https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11-Readme.md
inputs:
- versionSpec: 3.9.10
+ versionSpec: 3.9.13
- task: Npm@1
displayName: 'Test'
diff --git a/aspnetcorerazor.json b/aspnetcorerazor.json
index 2f2d2cf..a9d6d66 100644
--- a/aspnetcorerazor.json
+++ b/aspnetcorerazor.json
@@ -509,5 +509,12 @@
"\t",
""
]
+ },
+ "get-authorization-context": {
+ "prefix": "get-authorization-context",
+ "description": "Gets the authorization context of the specified authorization, including the access token",
+ "body": [
+ ""
+ ]
}
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 38ad955..3a31cb6 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "vscode-apimanagement",
"displayName": "Azure API Management",
"description": "An Azure API Management extension for Visual Studio Code.",
- "version": "1.0.3",
+ "version": "1.0.4",
"publisher": "ms-azuretools",
"icon": "resources/apim-icon-newone.png",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
@@ -74,7 +74,19 @@
"onCommand:azureApiManagement.revisions",
"onCommand:azureApiManagement.setCustomHostName",
"onCommand:azureApiManagement.createSubscription",
- "onCommand:azureApiManagement.deleteSubscription"
+ "onCommand:azureApiManagement.deleteSubscription",
+ "onCommand:azureApiManagement.createAuthorizationProvider",
+ "onCommand:azureApiManagement.deleteAuthorizationProvider",
+ "onCommand:azureApiManagement.copyAuthorizationProviderRedirectUrl",
+ "onCommand:azureApiManagement.createAuthorization",
+ "onCommand:azureApiManagement.authorizeAuthorization",
+ "onCommand:azureApiManagement.deleteAuthorization",
+ "onCommand:azureApiManagement.createAuthorizationAccessPolicy",
+ "onCommand:azureApiManagement.deleteAuthorizationAccessPolicy",
+ "onCommand:azureApiManagement.copyAuthorizationPolicy",
+ "onCommand:azureApiManagement.showArmAuthorizationProvider",
+ "onCommand:azureApiManagement.showArmAuthorization",
+ "onCommand:azureApiManagement.showArmAuthorizationAccessPolicy"
],
"main": "main",
"contributes": {
@@ -385,6 +397,66 @@
"light": "resources/light/diff.svg",
"dark": "resources/dark/diff.svg"
}
+ },
+ {
+ "command": "azureApiManagement.createAuthorizationProvider",
+ "title": "%azureApiManagement.createAuthorizationProvider%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorizationProvider",
+ "title": "%azureApiManagement.deleteAuthorizationProvider%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.copyAuthorizationProviderRedirectUrl",
+ "title": "%azureApiManagement.copyAuthorizationProviderRedirectUrl%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.createAuthorization",
+ "title": "%azureApiManagement.createAuthorization%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.authorizeAuthorization",
+ "title": "%azureApiManagement.authorizeAuthorization%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorization",
+ "title": "%azureApiManagement.deleteAuthorization%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.copyAuthorizationPolicy",
+ "title": "%azureApiManagement.copyAuthorizationPolicy%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.createAuthorizationAccessPolicy",
+ "title": "%azureApiManagement.createAuthorizationAccessPolicy%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorizationAccessPolicy",
+ "title": "%azureApiManagement.deleteAuthorizationAccessPolicy%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.showArmAuthorizationProvider",
+ "title": "%azureApiManagement.showArmAuthorizationProvider%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.showArmAuthorization",
+ "title": "%azureApiManagement.showArmAuthorization%",
+ "category": "Azure API Management"
+ },
+ {
+ "command": "azureApiManagement.showArmAuthorizationAccessPolicy",
+ "title": "%azureApiManagement.showArmAuthorizationAccessPolicy%",
+ "category": "Azure API Management"
}
],
"viewsContainers": {
@@ -635,6 +707,66 @@
"command": "azureApiManagement.deleteSubscription",
"when": "view == azureApiManagementExplorer && viewItem == azureApiManagementSubscriptionTreeItem",
"group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.createAuthorizationProvider",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationProviders",
+ "group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.Refresh",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationProviders",
+ "group": "1@2"
+ },
+ {
+ "command": "azureApiManagement.copyAuthorizationProviderRedirectUrl",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationProvider",
+ "group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorizationProvider",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationProvider",
+ "group": "1@2"
+ },
+ {
+ "command": "azureApiManagement.createAuthorization",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizations",
+ "group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.Refresh",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizations",
+ "group": "1@2"
+ },
+ {
+ "command": "azureApiManagement.authorizeAuthorization",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorization",
+ "group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.copyAuthorizationPolicy",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorization",
+ "group": "1@2"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorization",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorization",
+ "group": "1@3"
+ },
+ {
+ "command": "azureApiManagement.createAuthorizationAccessPolicy",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationAccessPolicies",
+ "group": "1@1"
+ },
+ {
+ "command": "azureApiManagement.Refresh",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationAccessPolicies",
+ "group": "1@2"
+ },
+ {
+ "command": "azureApiManagement.deleteAuthorizationAccessPolicy",
+ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementAuthorizationAccessPolicy",
+ "group": "1@1"
}
]
},
diff --git a/package.nls.json b/package.nls.json
index a623e6a..0ddb009 100644
--- a/package.nls.json
+++ b/package.nls.json
@@ -46,5 +46,17 @@
"azureApiManagement.revisions": "API Revisions",
"azureApiManagement.setCustomHostName": "Select Gateway Host Name",
"azureApiManagement.createSubscription": "Create a new Subscription",
- "azureApiManagement.deleteSubscription": "Delete Subscription"
+ "azureApiManagement.deleteSubscription": "Delete Subscription",
+ "azureApiManagement.createAuthorizationProvider": "Create Authorization Provider",
+ "azureApiManagement.deleteAuthorizationProvider": "Delete Authorization Provider",
+ "azureApiManagement.copyAuthorizationProviderRedirectUrl": "Copy RedirectUrl",
+ "azureApiManagement.createAuthorization": "Create Authorization",
+ "azureApiManagement.authorizeAuthorization": "Login",
+ "azureApiManagement.deleteAuthorization": "Delete Authorization",
+ "azureApiManagement.copyAuthorizationPolicy": "Copy Policy Snippet",
+ "azureApiManagement.createAuthorizationAccessPolicy": "Create Access Policy",
+ "azureApiManagement.deleteAuthorizationAccessPolicy": "Delete Access Policy",
+ "azureApiManagement.showArmAuthorizationProvider": "Edit Authorization Provider",
+ "azureApiManagement.showArmAuthorization": "Edit Authorization",
+ "azureApiManagement.showArmAuthorizationAccessPolicy": "Edit Authorization Access Policy"
}
\ No newline at end of file
diff --git a/resources/dark/accesspolicy.svg b/resources/dark/accesspolicy.svg
new file mode 100644
index 0000000..52fd503
--- /dev/null
+++ b/resources/dark/accesspolicy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/dark/authorization.svg b/resources/dark/authorization.svg
new file mode 100644
index 0000000..a1813b2
--- /dev/null
+++ b/resources/dark/authorization.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/dark/authorizationprovider.svg b/resources/dark/authorizationprovider.svg
new file mode 100644
index 0000000..12cba68
--- /dev/null
+++ b/resources/dark/authorizationprovider.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/light/accesspolicy.svg b/resources/light/accesspolicy.svg
new file mode 100644
index 0000000..52fd503
--- /dev/null
+++ b/resources/light/accesspolicy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/light/authorization.svg b/resources/light/authorization.svg
new file mode 100644
index 0000000..a1813b2
--- /dev/null
+++ b/resources/light/authorization.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/resources/light/authorizationprovider.svg b/resources/light/authorizationprovider.svg
new file mode 100644
index 0000000..12cba68
--- /dev/null
+++ b/resources/light/authorizationprovider.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/azure/apim/ApimService.ts b/src/azure/apim/ApimService.ts
index a7a136a..7967a8f 100644
--- a/src/azure/apim/ApimService.ts
+++ b/src/azure/apim/ApimService.ts
@@ -6,7 +6,7 @@
import { HttpOperationResponse, ServiceClient } from "@azure/ms-rest-js";
import { TokenCredentialsBase } from "@azure/ms-rest-nodeauth";
import { createGenericClient } from "vscode-azureextensionui";
-import { IGatewayApiContract, IGatewayContract, IMasterSubscription } from "./contracts";
+import { IApimServiceContract, IAuthorizationAccessPolicyContract, IAuthorizationAccessPolicyPropertiesContract, IAuthorizationContract, IAuthorizationLoginLinkRequest, IAuthorizationLoginLinkResponse, IAuthorizationPropertiesContract, IAuthorizationProviderContract, IAuthorizationProviderPropertiesContract, IGatewayApiContract, IGatewayContract, IMasterSubscription, ITokenStoreIdentityProviderContract } from "./contracts";
export class ApimService {
public baseUrl: string;
@@ -16,6 +16,7 @@ export class ApimService {
public resourceGroup: string;
public serviceName: string;
private readonly apiVersion: string = "2018-06-01-preview";
+ private readonly authorizationProviderApiVersion: string = "2021-12-01-preview";
constructor(credentials: TokenCredentialsBase, endPointUrl: string, subscriptionId: string, resourceGroup: string, serviceName: string) {
this.baseUrl = this.genSiteUrl(endPointUrl, subscriptionId, resourceGroup, serviceName);
@@ -91,6 +92,191 @@ export class ApimService {
return result.parsedBody;
}
+ // Authorization Providers
+ public async listTokenStoreIdentityProviders(): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationIdentityProviders?api-version=${this.authorizationProviderApiVersion}`
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody.value);
+ }
+
+ public async getTokenStoreIdentityProvider(providerName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationIdentityProviders/${providerName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async listAuthorizationProviders(): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders?api-version=${this.authorizationProviderApiVersion}`
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody.value);
+ }
+
+ public async listAuthorizations(authorizationProviderId: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderId}/authorizations?api-version=${this.authorizationProviderApiVersion}`
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody.value);
+ }
+
+ public async listAuthorizationAccessPolicies(authorizationProviderId: string, authorizationName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderId}/authorizations/${authorizationName}/accesspolicies?api-version=${this.authorizationProviderApiVersion}`
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody.value);
+ }
+
+ public async getAuthorizationAccessPolicy(authorizationProviderId: string, authorizationName: string, accessPolicyName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderId}/authorizations/${authorizationName}/accesspolicies/${accessPolicyName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+
+ if (result.status === 404) {
+ return undefined;
+ }
+
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async createAuthorizationAccessPolicy(authorizationProviderId: string, authorizationName: string, accessPolicyName: string, accessPolicyPaylod: IAuthorizationAccessPolicyPropertiesContract): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "PUT",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderId}/authorizations/${authorizationName}/accesspolicies/${accessPolicyName}?api-version=${this.authorizationProviderApiVersion}`,
+ body: { properties: accessPolicyPaylod }
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async deleteAuthorizationAccessPolicy(authorizationProviderId: string, authorizationName: string, accessPolicyName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ await client.sendRequest({
+ method: "DELETE",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderId}/authorizations/${authorizationName}/accesspolicies/${accessPolicyName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+ }
+
+ public async createAuthorizationProvider(authorizationProviderName: string, authorizationProviderPayload: IAuthorizationProviderPropertiesContract): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "PUT",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}?api-version=${this.authorizationProviderApiVersion}`,
+ body: { properties: authorizationProviderPayload }
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async createAuthorization(authorizationProviderName: string, authorizationName: string, authorizationPayload: IAuthorizationPropertiesContract): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "PUT",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}/authorizations/${authorizationName}?api-version=${this.authorizationProviderApiVersion}`,
+ body: { properties: authorizationPayload }
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async deleteAuthorization(authorizationProviderName: string, authorizationName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ await client.sendRequest({
+ method: "DELETE",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}/authorizations/${authorizationName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+ }
+
+ public async getAuthorization(authorizationProviderName: string, authorizationName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}/authorizations/${authorizationName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+
+ if (result.status === 404) {
+ return undefined;
+ }
+
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async deleteAuthorizationProvider(authorizationProviderName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ await client.sendRequest({
+ method: "DELETE",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+ }
+
+ public async getAuthorizationProvider(authorizationProviderName: string): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}?api-version=${this.authorizationProviderApiVersion}`
+ });
+
+ if (result.status === 404) {
+ return undefined;
+ }
+
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async listAuthorizationLoginLinks(authorizationProviderName: string, authorizationName: string, loginLinkRequestPayload: IAuthorizationLoginLinkRequest): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "POST",
+ url: `${this.baseUrl}/authorizationProviders/${authorizationProviderName}/authorizations/${authorizationName}/getLoginLinks?api-version=${this.authorizationProviderApiVersion}`,
+ body: loginLinkRequestPayload
+ });
+ // tslint:disable-next-line: no-unsafe-any
+ return (result.parsedBody);
+ }
+
+ public async getService(): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.baseUrl}?api-version=${this.apiVersion}`
+ });
+ // tslint:disable-next-line:no-any
+ return (result.parsedBody);
+ }
+
+ public async turnOnManagedIdentity(): Promise {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "PATCH",
+ url: `${this.baseUrl}?api-version=${this.apiVersion}`,
+ body: { identity : { type: "systemassigned" } }
+ });
+ // tslint:disable-next-line:no-any
+ return (result.parsedBody);
+ }
+
private genSiteUrl(endPointUrl: string, subscriptionId: string, resourceGroup: string, serviceName: string): string {
return `${endPointUrl}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.ApiManagement/service/${serviceName}`;
}
diff --git a/src/azure/apim/contracts.ts b/src/azure/apim/contracts.ts
index e11d623..419dd19 100644
--- a/src/azure/apim/contracts.ts
+++ b/src/azure/apim/contracts.ts
@@ -3,6 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+export interface IApimServiceContract {
+ id: string;
+ name: string;
+ // tslint:disable-next-line: no-reserved-keywords
+ type: string;
+ location?: string;
+ properties: object;
+ identity: IApimServiceIdentityContract;
+}
+
+export interface IApimServiceIdentityContract {
+ // tslint:disable-next-line:no-reserved-keywords
+ type: string;
+ principalId: string;
+ tenantId: string;
+}
+
export interface IGatewayContract {
id: string;
name: string;
@@ -42,3 +59,129 @@ export interface ISubscriptionProperty {
primaryKey: string;
secondaryKey: string;
}
+
+// Authorization Provider Contracts
+export enum IGrantTypesContract {
+ authorizationCode = "authorizationCode",
+ clientCredentials = "clientCredentials"
+}
+
+export interface IAuthorizationProviderContract {
+ id: string;
+ name: string;
+ // tslint:disable-next-line: no-reserved-keywords
+ type: string;
+ location?: string;
+ properties: IAuthorizationProviderPropertiesContract;
+}
+
+export interface IAuthorizationProviderPropertiesContract {
+ displayName?: string;
+ identityProvider: string;
+ oauth2?: IAuthorizationProviderOAuth2SettingsContract;
+}
+
+export interface IAuthorizationProviderOAuth2SettingsContract {
+ redirectUrl?: string;
+ grantTypes: IAuthorizationProviderOAuth2GrantTypesContract;
+}
+
+export type IAuthorizationProviderOAuth2GrantTypesContract = {
+ [key in IGrantTypesContract]?: {
+ [key: string]: string | boolean
+ };
+};
+
+export interface IAuthorizationContract {
+ id: string;
+ name: string;
+ // tslint:disable-next-line: no-reserved-keywords
+ type: string;
+ location?: string;
+ properties: IAuthorizationPropertiesContract;
+}
+
+export interface IAuthorizationPropertiesContract {
+ authorizationType: string;
+ oauth2grantType: string;
+ parameters?: {
+ [key: string]: string | boolean;
+ };
+ status?: ITokenStoreAuthorizationState;
+ error?: IAuthorizationErrorContract;
+}
+
+export enum ITokenStoreAuthorizationState {
+ connected = "Connected",
+ error = "Error"
+}
+
+export interface IAuthorizationErrorContract {
+ code: string;
+ message: string;
+ // tslint:disable-next-line:no-any
+ refreshResponseBodyFromIdentityProvider?: any;
+}
+
+export interface ITokenStoreIdentityProviderContract {
+ id: string;
+ name: string;
+ // tslint:disable-next-line: no-reserved-keywords
+ type: string;
+ location?: string;
+ properties: ITokenStoreIdentityProviderPropertiesContract;
+}
+
+export interface ITokenStoreIdentityProviderPropertiesContract {
+ displayName: string;
+ oauth2: {
+ grantTypes: ITokenStoreIdentityProviderGrantTypeContract;
+ };
+}
+
+export type ITokenStoreIdentityProviderGrantTypeContract = {
+ [key in IGrantTypesContract]?: ITokenStoreGrantTypeParameterContract;
+};
+
+export interface ITokenStoreGrantTypeParameterContract {
+ [key: string]: ITokenStoreGrantTypeParameterDefinitionContract;
+}
+
+export interface ITokenStoreGrantTypeParameterDefinitionContract {
+ // tslint:disable-next-line:no-reserved-keywords
+ type: "string" | "securestring" | "bool";
+ displayName: string;
+ description?: string;
+ // tslint:disable-next-line:no-reserved-keywords
+ default?: string;
+ uidefinition: {
+ atAuthorizationProviderLevel: "REQUIRED" | "OPTIONAL" | "HIDDEN"
+ };
+}
+
+export interface IAuthorizationLoginLinkRequest {
+ postLoginRedirectUrl: string;
+}
+
+export interface IAuthorizationLoginLinkResponse {
+ loginLink: string;
+}
+
+export interface IAuthorizationAccessPolicyContract {
+ id: string;
+ name: string;
+ // tslint:disable-next-line: no-reserved-keywords
+ type: string;
+ location?: string;
+ properties: IAuthorizationAccessPolicyPropertiesContract;
+}
+
+export interface IAuthorizationAccessPolicyPropertiesContract {
+ objectId: string;
+ tenantId: string;
+}
+
+export enum IAuthorizationTypeEnum {
+ OAuth2,
+ OAuth1
+}
diff --git a/src/azure/graph/GraphService.ts b/src/azure/graph/GraphService.ts
new file mode 100644
index 0000000..5461111
--- /dev/null
+++ b/src/azure/graph/GraphService.ts
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { HttpOperationResponse, ServiceClient } from "@azure/ms-rest-js";
+import { TokenCredentialsBase } from "@azure/ms-rest-nodeauth";
+import { TokenResponse } from "adal-node";
+import { createGenericClient } from "vscode-azureextensionui";
+import { ext } from "../../extensionVariables";
+import { nonNullValue } from "../../utils/nonNull";
+
+export class GraphService {
+ private accessToken: string;
+ constructor(private credentials: TokenCredentialsBase,
+ private graphEndpoint: string,
+ private tenantId: string) {}
+
+ public async acquireGraphToken(): Promise {
+ const token = await this.credentials.getToken();
+ this.credentials.authContext.acquireToken(
+ this.graphEndpoint,
+ nonNullValue(token.userId),
+ this.credentials.clientId,
+ (error, response) => {
+ if (error == null) {
+ this.accessToken = (response).accessToken;
+ } else {
+ ext.outputChannel.append(error.message);
+ }
+ }
+ );
+ }
+
+ // tslint:disable-next-line:no-any
+ public async getUser(emailId: string): Promise<{ userPrincipalName: string, objectId: string } | undefined> {
+ const client: ServiceClient = await createGenericClient();
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.graphEndpoint}/${this.tenantId}/users/${emailId}`,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`,
+ 'api-version': '1.61-internal'
+ }
+ });
+
+ if (result.status >= 400) {
+ // tslint:disable-next-line: no-any no-unsafe-any
+ ext.outputChannel.append(JSON.stringify(result.parsedBody));
+ return undefined;
+ }
+ // tslint:disable-next-line:no-any
+ return <{ userPrincipalName: string, objectId: string }>(result.parsedBody);
+ }
+
+ // tslint:disable-next-line:no-any
+ public async getGroup(displayNameOrEmail: string): Promise<{ displayName: string, objectId: string } | undefined> {
+ const client: ServiceClient = await createGenericClient();
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.graphEndpoint}/${this.tenantId}/groups?$filter=securityEnabled eq true and (startswith(displayName,'${displayNameOrEmail}') or startswith(mail,'${displayNameOrEmail}'))&$top=1`,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`,
+ 'api-version': '1.61-internal'
+ }
+ });
+
+ if (result.status >= 400) {
+ // tslint:disable-next-line: no-any no-unsafe-any
+ ext.outputChannel.append(JSON.stringify(result.parsedBody));
+ return undefined;
+ }
+
+ // tslint:disable-next-line: no-any no-unsafe-any
+ return <{ displayName: string, objectId: string }>(result.parsedBody.value[0]);
+ }
+
+ // tslint:disable-next-line:no-any
+ public async getServicePrincipal(displayName: string): Promise<{ displayName: string, objectId: string } | undefined> {
+ const client: ServiceClient = await createGenericClient();
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "GET",
+ url: `${this.graphEndpoint}/${this.tenantId}/servicePrincipals?$filter=startswith(displayName,'${displayName}')&$top=1`,
+ headers: {
+ Authorization: `Bearer ${this.accessToken}`,
+ 'api-version': '1.61-internal'
+ }
+ });
+
+ if (result.status >= 400) {
+ // tslint:disable-next-line: no-any no-unsafe-any
+ ext.outputChannel.append(JSON.stringify(result.parsedBody));
+ return undefined;
+ }
+
+ // tslint:disable-next-line: no-any no-unsafe-any
+ return <{ displayName: string, objectId: string }>(result.parsedBody.value[0]);
+ }
+}
diff --git a/src/azure/resourceGraph/ResourceGraphService.ts b/src/azure/resourceGraph/ResourceGraphService.ts
new file mode 100644
index 0000000..7be79d5
--- /dev/null
+++ b/src/azure/resourceGraph/ResourceGraphService.ts
@@ -0,0 +1,56 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { HttpOperationResponse, ServiceClient } from "@azure/ms-rest-js";
+import { TokenCredentialsBase } from "@azure/ms-rest-nodeauth";
+import { createGenericClient } from "vscode-azureextensionui";
+
+export class ResourceGraphService {
+ public resourceGraphUrl: string;
+ constructor(public credentials: TokenCredentialsBase,
+ public endPointUrl: string,
+ public subscriptionId: string) {
+ this.credentials = credentials;
+ this.endPointUrl = endPointUrl;
+ this.subscriptionId = subscriptionId;
+
+ this.resourceGraphUrl = `${this.endPointUrl}/providers/Microsoft.ResourceGraph/resources?api-version=2019-04-01`;
+ }
+
+ // tslint:disable-next-line:no-any no-reserved-keywords
+ public async listSystemAssignedIdentities(): Promise<{ name: string, id: string, type: string, principalId: string }[]> {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "POST",
+ url: this.resourceGraphUrl,
+ body: {
+ subscriptions: [ this.subscriptionId ],
+ options: { resultFormat: "objectArray" },
+ query: "Resources | where notempty(identity) | project name, id, type, principalId = identity.principalId"
+ },
+ timeout: 5000
+ });
+ // tslint:disable-next-line:no-any no-unsafe-any no-reserved-keywords
+ return <{ name: string, id: string, type: string, principalId: string }[]>(result.parsedBody?.data);
+ }
+
+ // tslint:disable-next-line:no-any
+ public async listUserAssignedIdentities(): Promise<{ name: string, id: string, principalId: string }[]> {
+ const client: ServiceClient = await createGenericClient(this.credentials);
+ const result: HttpOperationResponse = await client.sendRequest({
+ method: "POST",
+ url: this.resourceGraphUrl,
+ body: {
+ subscriptions: [ this.subscriptionId ],
+ options: { resultFormat: "objectArray" },
+ query: "resources | where type == 'microsoft.managedidentity/userassignedidentities' | project name, id, principalId = properties.principalId"
+ },
+ timeout: 5000
+ });
+ // tslint:disable-next-line:no-any no-unsafe-any
+ return <{ name: string, id: string, principalId: string }[]>(result.parsedBody?.data);
+ }
+
+}
diff --git a/src/commands/authorizations/authorizeAuthorization.ts b/src/commands/authorizations/authorizeAuthorization.ts
new file mode 100644
index 0000000..7268e6e
--- /dev/null
+++ b/src/commands/authorizations/authorizeAuthorization.ts
@@ -0,0 +1,65 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { window } from 'vscode';
+import { IActionContext } from "vscode-azureextensionui";
+import { ApimService } from "../../azure/apim/ApimService";
+import { IAuthorizationProviderContract, ITokenStoreIdentityProviderContract } from "../../azure/apim/contracts";
+import { AuthorizationProviderTreeItem } from "../../explorer/AuthorizationProviderTreeItem";
+import { AuthorizationTreeItem } from "../../explorer/AuthorizationTreeItem";
+import { ext } from "../../extensionVariables";
+import { localize } from "../../localize";
+import { nonNullValue } from '../../utils/nonNull';
+import { askAuthorizationParameterValues } from './common';
+
+export async function authorizeAuthorization(context: IActionContext, node?: AuthorizationTreeItem): Promise {
+ if (!node) {
+ const authorizationNode = await ext.tree.showTreeItemPicker(AuthorizationTreeItem.contextValue, context);
+ node = authorizationNode;
+ }
+
+ const apimService = new ApimService(
+ node.root.credentials,
+ node.root.environment.resourceManagerEndpointUrl,
+ node.root.subscriptionId,
+ node.root.resourceGroupName,
+ node.root.serviceName);
+
+ if (node.authorizationContract.properties.oauth2grantType === "AuthorizationCode") {
+ const extensionId = "ms-azuretools.vscode-apimanagement";
+ const key = `vscodeauthcomplete/${node.root.authorizationProviderName}/${node.root.authorizationName}`;
+ const redirectUrl = `vscode://${extensionId}/${key}`;
+ const loginLinks = await apimService.listAuthorizationLoginLinks(
+ node.root.authorizationProviderName,
+ node.authorizationContract.name,
+ { postLoginRedirectUrl : redirectUrl });
+
+ vscode.env.openExternal(vscode.Uri.parse(loginLinks.loginLink));
+ } else if (node.authorizationContract.properties.oauth2grantType === "ClientCredentials") {
+ const authorizationProvider : IAuthorizationProviderContract = (node.parent?.parent).authorizationProviderContract;
+ const identityProvider: ITokenStoreIdentityProviderContract = await apimService.getTokenStoreIdentityProvider(authorizationProvider.properties.identityProvider);
+ const grant = identityProvider.properties.oauth2.grantTypes.clientCredentials;
+
+ const parameterValues = await askAuthorizationParameterValues(nonNullValue(grant));
+
+ const authorization = node.authorizationContract;
+ authorization.properties.parameters = parameterValues;
+
+ window.withProgress(
+ {
+ location: vscode.ProgressLocation.Notification,
+ title: localize("authorizeAuthorization", `Updating Authorization '${authorization.name}' ...`),
+ cancellable: false
+ },
+ // tslint:disable-next-line:no-non-null-assertion
+ async () => { return apimService.createAuthorization(authorizationProvider.name, authorization.name, authorization.properties); }
+ ).then(async () => {
+ // tslint:disable-next-line:no-non-null-assertion
+ await node!.refresh(context);
+ window.showInformationMessage(localize("updatedAuthorization", `Updated Authorization '${authorization.name}' succesfully.`));
+ });
+ }
+}
diff --git a/src/commands/authorizations/common.ts b/src/commands/authorizations/common.ts
new file mode 100644
index 0000000..23a9bf4
--- /dev/null
+++ b/src/commands/authorizations/common.ts
@@ -0,0 +1,81 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ITokenStoreGrantTypeParameterContract, ITokenStoreGrantTypeParameterDefinitionContract } from "../../azure/apim/contracts";
+import { ext } from "../../extensionVariables";
+import { localize } from "../../localize";
+
+export async function askAuthorizationProviderParameterValues(grant: ITokenStoreGrantTypeParameterContract) : Promise {
+ const parameterValues: IParameterValues = {};
+ // tslint:disable-next-line:forin no-for-in
+ for (const parameter in grant) {
+ const parameterUIMetadata = grant[parameter];
+ if (parameterUIMetadata.uidefinition.atAuthorizationProviderLevel !== "HIDDEN") {
+ parameterValues[parameter] = await askParam(
+ parameterUIMetadata,
+ parameterUIMetadata.uidefinition.atAuthorizationProviderLevel === "REQUIRED" );
+ }
+ }
+
+ return parameterValues;
+}
+
+export async function askAuthorizationParameterValues(grant: ITokenStoreGrantTypeParameterContract) : Promise {
+ const parameterValues: IParameterValues = {};
+ // tslint:disable-next-line:forin no-for-in
+ for (const parameter in grant) {
+ const parameterUIMetadata = grant[parameter];
+ if (parameterUIMetadata.uidefinition.atAuthorizationProviderLevel === "HIDDEN") {
+ parameterValues[parameter] = await askParam(
+ parameterUIMetadata,
+ true);
+ }
+ }
+
+ return parameterValues;
+}
+
+async function askParam(parameterUIMetadata: ITokenStoreGrantTypeParameterDefinitionContract, isRequired: boolean) : Promise {
+ return await ext.ui.showInputBox({
+ placeHolder: localize('parameterDisplayName', `Enter ${parameterUIMetadata.displayName} ...`),
+ prompt: localize('parameterDescription', `${parameterUIMetadata.description}`),
+ value: parameterUIMetadata.default,
+ password: parameterUIMetadata.type === "securestring",
+ validateInput: async (value: string | undefined): Promise => {
+ value = value ? value.trim() : '';
+
+ if (isRequired && value.length < 1) {
+ return localize("parameterRequired", `${parameterUIMetadata.displayName} is required.`);
+ }
+
+ return undefined;
+ }
+ });
+}
+
+export async function askId(prompt: string, errorMessage: string, defaultValue: string = ''): Promise {
+ const idPrompt: string = localize('idPrompt', prompt);
+ return (await ext.ui.showInputBox({
+ prompt: idPrompt,
+ value: defaultValue,
+ validateInput: async (value: string): Promise => {
+ value = value ? value.trim() : '';
+ return validateId(value, errorMessage);
+ }
+ })).trim();
+}
+
+function validateId(id: string, errorMessage: string): string | undefined {
+ const test = "^[\w]+$)|(^[\w][\w\-]+[\w]$";
+ if (id.match(test) === null) {
+ return localize("idInvalid", errorMessage);
+ }
+
+ return undefined;
+}
+
+export interface IParameterValues {
+ [key: string]: string;
+}
diff --git a/src/commands/authorizations/copyAuthorizationPolicy.ts b/src/commands/authorizations/copyAuthorizationPolicy.ts
new file mode 100644
index 0000000..85550f7
--- /dev/null
+++ b/src/commands/authorizations/copyAuthorizationPolicy.ts
@@ -0,0 +1,87 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { IActionContext } from "vscode-azureextensionui";
+import { AuthorizationTreeItem } from "../../explorer/AuthorizationTreeItem";
+import { ext } from "../../extensionVariables";
+import { localize } from '../../localize';
+
+export async function copyAuthorizationPolicy(context: IActionContext, node?: AuthorizationTreeItem): Promise {
+ if (!node) {
+ const authorizationNode = await ext.tree.showTreeItemPicker(AuthorizationTreeItem.contextValue, context);
+ node = authorizationNode;
+ }
+
+ // Select purpose
+ const attachToken = "Attach access token to backend request";
+ const tokenBack = "Retrieve access token";
+ const purposeOptions = [attachToken, tokenBack];
+ const purposeSelected = await ext.ui.showQuickPick(
+ purposeOptions.map(purpose => { return { label: purpose, description: '', detail: '' }; }),
+ { placeHolder: 'How do you want to use the policy?', canPickMany: false });
+ const managed = "managed";
+ const jwt = "jwt";
+ const identityTypeOptions = [
+ {
+ label: managed,
+ description: "Use the managed identity of the service."
+ },
+ {
+ label: jwt,
+ description: "Use the identity of the specified token."
+ }
+ ];
+ const identityTypeSelected = await ext.ui.showQuickPick(
+ identityTypeOptions.map(option => { return { label: option.label, description: option.description, detail: '' }; }),
+ { placeHolder: 'Which identity type do you want to use?', canPickMany: false, suppressPersistence: true });
+
+ const pid = node.root.authorizationProviderName;
+ const aid = node.authorizationContract.name;
+
+ let comment = '';
+ let identityPhrase = '';
+ let additionalMessage = '';
+ if (identityTypeSelected.label === managed) {
+ comment = ``;
+ identityPhrase = `identity-type="${identityTypeSelected.label}"`;
+ additionalMessage = "For 'managed' identity-type, make sure managed identity is turned on.";
+ } else {
+ const allowedAudienceMessage = `Allowed audiences for jwt in "identity" attribute are "https://azure-api.net/authorization-manager"`;
+ comment = ``;
+ identityPhrase = `identity-type="${identityTypeSelected.label}" identity="@(context.Request.Headers["Authorization"][0].Replace("Bearer ", ""))"`;
+ additionalMessage = `For 'jwt' identity-type, ${allowedAudienceMessage}`;
+ }
+
+ let policy = '';
+ if (purposeSelected.label === attachToken) {
+ policy = `${comment}
+
+
+ @("Bearer " + ((Authorization)context.Variables.GetValueOrDefault("${pid}-${aid}-context"))?.AccessToken)
+`;
+ } else {
+ policy = `${comment}
+
+
+
+ @(((Authorization)context.Variables.GetValueOrDefault("${pid}-${aid}-context"))?.AccessToken)
+`;
+ }
+
+ vscode.env.clipboard.writeText(policy);
+ vscode.window.showInformationMessage(localize("CopySnippet", `Policy copied to clipboard. ${additionalMessage}`));
+ ext.outputChannel.appendLine(`Policy copied to clipboard. ${additionalMessage}`);
+}
diff --git a/src/commands/authorizations/copyAuthorizationProviderRedirectUrl.ts b/src/commands/authorizations/copyAuthorizationProviderRedirectUrl.ts
new file mode 100644
index 0000000..482fed2
--- /dev/null
+++ b/src/commands/authorizations/copyAuthorizationProviderRedirectUrl.ts
@@ -0,0 +1,22 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { IActionContext } from "vscode-azureextensionui";
+import { AuthorizationProviderTreeItem } from '../../explorer/AuthorizationProviderTreeItem';
+import { ext } from "../../extensionVariables";
+import { localize } from '../../localize';
+import { nonNullValue } from '../../utils/nonNull';
+
+export async function copyAuthorizationProviderRedirectUrl(context: IActionContext, node?: AuthorizationProviderTreeItem): Promise {
+ if (!node) {
+ node = await ext.tree.showTreeItemPicker(AuthorizationProviderTreeItem.contextValue, context);
+ }
+
+ const redirectUrl = nonNullValue(node.authorizationProviderContract.properties.oauth2?.redirectUrl);
+ vscode.env.clipboard.writeText(redirectUrl);
+ vscode.window.showInformationMessage(localize("copyRedirect", `RedirectUrl for Authorization provider '${node.authorizationProviderContract.name}' copied to clipboard. value - ${redirectUrl}`));
+ ext.outputChannel.appendLine(`RedirectUrl for Authorization provider '${node.authorizationProviderContract.name}' copied to clipboard. value - ${redirectUrl}`);
+}
diff --git a/src/commands/authorizations/createAuthorization.ts b/src/commands/authorizations/createAuthorization.ts
new file mode 100644
index 0000000..7ba62c4
--- /dev/null
+++ b/src/commands/authorizations/createAuthorization.ts
@@ -0,0 +1,18 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 "vscode-azureextensionui";
+import { AuthorizationProviderTreeItem } from "../../explorer/AuthorizationProviderTreeItem";
+import { AuthorizationsTreeItem, IAuthorizationTreeItemContext } from "../../explorer/AuthorizationsTreeItem";
+import { ext } from "../../extensionVariables";
+
+export async function createAuthorization(context: IActionContext & Partial, node?: AuthorizationsTreeItem): Promise {
+ if (!node) {
+ const authorizationProviderNode = await ext.tree.showTreeItemPicker(AuthorizationProviderTreeItem.contextValue, context);
+ node = authorizationProviderNode.authorizationsTreeItem;
+ }
+
+ await node.createChild(context);
+}
diff --git a/src/commands/authorizations/createAuthorizationAccessPolicy.ts b/src/commands/authorizations/createAuthorizationAccessPolicy.ts
new file mode 100644
index 0000000..0c465d7
--- /dev/null
+++ b/src/commands/authorizations/createAuthorizationAccessPolicy.ts
@@ -0,0 +1,227 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { TokenCredentialsBase } from "@azure/ms-rest-nodeauth";
+import { ProgressLocation, QuickPickItem, window } from "vscode";
+import { IActionContext } from "vscode-azureextensionui";
+import { ApimService } from "../../azure/apim/ApimService";
+import { GraphService } from "../../azure/graph/GraphService";
+import { ResourceGraphService } from "../../azure/resourceGraph/ResourceGraphService";
+import { AuthorizationAccessPoliciesTreeItem, IAuthorizationAccessPolicyTreeItemContext } from "../../explorer/AuthorizationAccessPoliciesTreeItem";
+import { AuthorizationTreeItem } from "../../explorer/AuthorizationTreeItem";
+import { ext } from "../../extensionVariables";
+import { localize } from "../../localize";
+import { nonNullValue } from "../../utils/nonNull";
+
+const systemAssignedManagedIdentitiesOptionLabel = "System assigned managed identity";
+const userAssignedManagedIdentitiesOptionLabel = "User assigned managed identity";
+const userEmailIdLabel = "User";
+const groupDisplayNameorEmailIdLabel = "Group";
+const servicePrincipalDisplayNameLabel = "Service principal";
+
+let resourceGraphService: ResourceGraphService;
+let graphService: GraphService;
+
+ // tslint:disable-next-line: no-any no-unsafe-any
+export async function createAuthorizationAccessPolicy(context: IActionContext & Partial, node?: AuthorizationAccessPoliciesTreeItem): Promise {
+ if (!node) {
+ const AuthorizationNode = await ext.tree.showTreeItemPicker(AuthorizationTreeItem.contextValue, context);
+ node = AuthorizationNode.authorizationAccessPoliciesTreeItem;
+ }
+
+ const apimService = new ApimService(
+ node.root.credentials,
+ node.root.environment.resourceManagerEndpointUrl,
+ node.root.subscriptionId,
+ node.root.resourceGroupName,
+ node.root.serviceName);
+
+ resourceGraphService = new ResourceGraphService(
+ node.root.credentials,
+ node.root.environment.resourceManagerEndpointUrl,
+ node.root.subscriptionId
+ );
+
+ graphService = new GraphService(
+ node.root.credentials,
+ nonNullValue(node.root.environment.activeDirectoryGraphResourceId),
+ node.root.tenantId
+ );
+
+ await graphService.acquireGraphToken();
+
+ const identityOptions = await populateIdentityOptionsAsync(
+ apimService, node.root.credentials, node.root.environment.resourceManagerEndpointUrl);
+
+ const identitySelected = await ext.ui.showQuickPick(
+ identityOptions, { placeHolder: 'Select identity...', canPickMany: false, suppressPersistence: true });
+
+ let permissionName = '';
+ let oid = '';
+
+ if (identitySelected.label === systemAssignedManagedIdentitiesOptionLabel) {
+ const response = await resourceGraphService.listSystemAssignedIdentities();
+ // tslint:disable-next-line: no-any no-unsafe-any
+ const otherManagedIdentityOptions = await populateManageIdentityOptions(response);
+
+ const managedIdentitySelected = await ext.ui.showQuickPick(
+ otherManagedIdentityOptions, { placeHolder: 'Select system assigned managed identity ...', canPickMany: false, suppressPersistence: true });
+
+ permissionName = managedIdentitySelected.label;
+ oid = nonNullValue(managedIdentitySelected.description);
+ } else if (identitySelected.label === userAssignedManagedIdentitiesOptionLabel) {
+ const response = await resourceGraphService.listUserAssignedIdentities();
+ const otherManagedIdentityOptions = await populateManageIdentityOptions(response);
+
+ const managedIdentitySelected = await ext.ui.showQuickPick(
+ otherManagedIdentityOptions, { placeHolder: 'Select user assigned managed identity ...', canPickMany: false, suppressPersistence: true });
+
+ permissionName = managedIdentitySelected.label;
+ oid = nonNullValue(managedIdentitySelected.description);
+ } else if (identitySelected.label === userEmailIdLabel) {
+ const userId = await askInput('Enter user emailId ...', 'mary@contoso.net');
+ const user = await graphService.getUser(userId);
+
+ if (user !== undefined && user.objectId !== null) {
+ permissionName = user.userPrincipalName;
+ oid = user.objectId;
+ } else {
+ window.showErrorMessage(localize('invalidUserEmailId', 'Please specify a valid user emailId.'));
+ }
+ } else if (identitySelected.label === groupDisplayNameorEmailIdLabel) {
+ const groupDisplayNameOrEmailId = await askInput('Enter group displayname (or) emailId ...', 'myfullgroupname (or) mygroup@contoso.net');
+ const group = await graphService.getGroup(groupDisplayNameOrEmailId);
+
+ if (group !== undefined && group.objectId !== null) {
+ permissionName = group.displayName.replace(' ', '');
+ oid = group.objectId;
+ } else {
+ window.showErrorMessage(localize('invalidGroupDisplayNameorEmailId', 'Please specify a valid group display name (or) emailId. Example, myfullgroupname (or) mygroup@contoso.net'));
+ }
+ } else if (identitySelected.label === servicePrincipalDisplayNameLabel) {
+ const servicePrincipalDisplayName = await askInput('Enter service principal display name ...', 'myserviceprincipalname');
+
+ const spn = await graphService.getServicePrincipal(servicePrincipalDisplayName);
+
+ if (spn !== undefined && spn.objectId !== null) {
+ permissionName = spn.displayName.replace(' ', '');
+ oid = spn.objectId;
+ } else {
+ window.showErrorMessage(localize('invalidSpnDisplayName', 'Please specify a valid service principal display name.'));
+ }
+ } else {
+ permissionName = identitySelected.label;
+ oid = nonNullValue(identitySelected.description);
+ }
+
+ context.authorizationAccessPolicyName = permissionName;
+ context.authorizationAccessPolicy = {
+ objectId: oid,
+ tenantId: node.root.tenantId
+ };
+
+ createAccessPolicy(permissionName, node, context);
+}
+
+function createAccessPolicy(
+ permissionName: string,
+ node: AuthorizationAccessPoliciesTreeItem,
+ context: IActionContext & Partial) : void {
+ window.withProgress(
+ {
+ location: ProgressLocation.Notification,
+ title: localize("creatingAuthorizationPermission", `Creating Access policy '${permissionName}' for Authorization ${node.root.authorizationName} ...`),
+ cancellable: false
+ },
+ async () => {
+ // tslint:disable-next-line:no-non-null-assertion
+ return node!.createChild(context);
+ }
+ ).then(async () => {
+ // tslint:disable-next-line:no-non-null-assertion
+ await node!.refresh(context);
+ window.showInformationMessage(localize("createdAuthorizationPermission", `Created Access policy '${permissionName}' successfully.`));
+ });
+}
+
+async function populateIdentityOptionsAsync(
+ apimService: ApimService,
+ credential : TokenCredentialsBase,
+ resourceManagerEndpointUrl: string) : Promise {
+ const options : QuickPickItem[] = [];
+
+ // 1. Self
+ const token = await credential.getToken();
+ const meOption : QuickPickItem = {
+ label: nonNullValue(token.userId),
+ description: token.oid,
+ detail: "Current signedIn user"
+ };
+ options.push(meOption);
+
+ // 2. APIM Service
+ const service = await apimService.getService();
+ if (!!service.identity?.principalId) {
+ const apimOption : QuickPickItem = {
+ label: service.name,
+ description: service.identity.principalId,
+ detail: "Current service system managed identity"
+ };
+ options.push(apimOption);
+ }
+
+ // 3. Other Managed identities. Dogfood doesn't support this endpoint, so only show this in prod
+ if (resourceManagerEndpointUrl === "https://management.azure.com/") {
+ const systemAssignedManagedIdentities : QuickPickItem = {
+ label: systemAssignedManagedIdentitiesOptionLabel,
+ description: "",
+ detail: ""
+ };
+ options.push(systemAssignedManagedIdentities);
+
+ const userAssignedManagedIdentities : QuickPickItem = {
+ label: userAssignedManagedIdentitiesOptionLabel,
+ description: "",
+ detail: ""
+ };
+ options.push(userAssignedManagedIdentities);
+ }
+
+ // 4. Custom
+ options.push({ label: userEmailIdLabel });
+ options.push({ label: groupDisplayNameorEmailIdLabel });
+ options.push({ label: servicePrincipalDisplayNameLabel });
+ return options;
+}
+
+// tslint:disable-next-line:no-any
+async function populateManageIdentityOptions(data: { name: string, id: string, principalId: string }[]) : Promise {
+ const options : QuickPickItem[] = [];
+ const managedIdentityOptions : QuickPickItem[] = data.map(d => {
+ return {
+ label: d.name,
+ description: d.principalId,
+ detail: d.id
+ };
+ });
+ options.push(...managedIdentityOptions);
+
+ return options;
+}
+
+async function askInput(message: string, placeholder: string = '') : Promise {
+ const idPrompt: string = localize('value', message);
+ return (await ext.ui.showInputBox({
+ prompt: idPrompt,
+ placeHolder: placeholder,
+ validateInput: async (value: string): Promise => {
+ value = value ? value.trim() : '';
+ if (value === '') {
+ return localize("valueInvalid", 'Value cannot be empty.');
+ }
+ return undefined;
+ }
+ })).trim();
+}
diff --git a/src/commands/authorizations/createAuthorizationProvider.ts b/src/commands/authorizations/createAuthorizationProvider.ts
new file mode 100644
index 0000000..b26f662
--- /dev/null
+++ b/src/commands/authorizations/createAuthorizationProvider.ts
@@ -0,0 +1,19 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 "vscode-azureextensionui";
+import { AuthorizationProvidersTreeItem, IAuthorizationProviderTreeItemContext } from "../../explorer/AuthorizationProvidersTreeItem";
+import { ServiceTreeItem } from "../../explorer/ServiceTreeItem";
+import { ext } from "../../extensionVariables";
+
+export async function createAuthorizationProvider(context: IActionContext & Partial, node?: AuthorizationProvidersTreeItem): Promise {
+ if (!node) {
+ const serviceNode = await ext.tree.showTreeItemPicker(ServiceTreeItem.contextValue, context);
+ node = serviceNode.authorizationProvidersTreeItem;
+ }
+
+ // support update
+ await node.createChild(context);
+}
diff --git a/src/explorer/AuthorizationAccessPoliciesTreeItem.ts b/src/explorer/AuthorizationAccessPoliciesTreeItem.ts
new file mode 100644
index 0000000..4472c12
--- /dev/null
+++ b/src/explorer/AuthorizationAccessPoliciesTreeItem.ts
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationAccessPolicyContract, IAuthorizationAccessPolicyPropertiesContract } from "../azure/apim/contracts";
+import { localize } from "../localize";
+import { processError } from "../utils/errorUtil";
+import { treeUtils } from "../utils/treeUtils";
+import { AuthorizationAccessPolicyTreeItem } from "./AuthorizationAccessPolicyTreeItem";
+import { IAuthorizationTreeRoot } from "./IAuthorizationTreeRoot";
+
+export interface IAuthorizationAccessPolicyTreeItemContext extends ICreateChildImplContext {
+ authorizationAccessPolicyName: string;
+ authorizationAccessPolicy: IAuthorizationAccessPolicyPropertiesContract;
+}
+
+export class AuthorizationAccessPoliciesTreeItem extends AzureParentTreeItem {
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('list');
+ }
+ public static contextValue: string = 'azureApiManagementAuthorizationAccessPolicies';
+ public label: string = "Access policies";
+ public contextValue: string = AuthorizationAccessPoliciesTreeItem.contextValue;
+ public readonly childTypeLabel: string = localize('azureApiManagement.AuthorizationAccessPolicy', 'AuthorizationAccessPolicy');
+ private _nextLink: string | undefined;
+
+ public hasMoreChildrenImpl(): boolean {
+ return this._nextLink !== undefined;
+ }
+
+ public async loadMoreChildrenImpl(clearCache: boolean): Promise {
+ if (clearCache) {
+ this._nextLink = undefined;
+ }
+
+ const apimService = new ApimService(
+ this.root.credentials,
+ this.root.environment.resourceManagerEndpointUrl,
+ this.root.subscriptionId,
+ this.root.resourceGroupName,
+ this.root.serviceName);
+
+ const authorizationAccessPolicies: IAuthorizationAccessPolicyContract[] = await apimService.listAuthorizationAccessPolicies(
+ this.root.authorizationProviderName,
+ this.root.authorizationName);
+
+ return this.createTreeItemsWithErrorHandling(
+ authorizationAccessPolicies,
+ "invalidApiManagementAuthorizationAccessPolicy",
+ async (accessPolicy: IAuthorizationAccessPolicyContract) => new AuthorizationAccessPolicyTreeItem(this, accessPolicy),
+ (accessPolicy: IAuthorizationAccessPolicyContract) => {
+ return accessPolicy.name;
+ });
+ }
+
+ public async createChildImpl(context: IAuthorizationAccessPolicyTreeItemContext): Promise {
+ if (context.authorizationAccessPolicyName
+ && context.authorizationAccessPolicy !== undefined) {
+ const authorizationAccessPolicyName = context.authorizationAccessPolicyName;
+ context.showCreatingTreeItem(authorizationAccessPolicyName);
+
+ try {
+ const apimService = new ApimService(this.root.credentials, this.root.environment.resourceManagerEndpointUrl, this.root.subscriptionId, this.root.resourceGroupName, this.root.serviceName);
+ let authorizationAccessPolicy = await apimService.getAuthorizationAccessPolicy(this.root.authorizationProviderName, this.root.authorizationName, authorizationAccessPolicyName);
+ if (authorizationAccessPolicy === undefined) {
+ authorizationAccessPolicy = await apimService.createAuthorizationAccessPolicy(
+ this.root.authorizationProviderName,
+ this.root.authorizationName,
+ authorizationAccessPolicyName,
+ context.authorizationAccessPolicy);
+ return new AuthorizationAccessPolicyTreeItem(this, authorizationAccessPolicy);
+ } else {
+ throw new Error(localize("createAuthorizationAccessPolicy", `Access policy '${authorizationAccessPolicyName}' already exists.`));
+ }
+ } catch (error) {
+ throw new Error(processError(error, localize("createAuthorizationAccessPolicy", `Failed to access policy '${authorizationAccessPolicyName}' to Authorization '${this.root.authorizationName}'.`)));
+ }
+ } else {
+ throw Error("Expected Access Policy name.");
+ }
+ }
+}
diff --git a/src/explorer/AuthorizationAccessPolicyTreeItem.ts b/src/explorer/AuthorizationAccessPolicyTreeItem.ts
new file mode 100644
index 0000000..00cdb5c
--- /dev/null
+++ b/src/explorer/AuthorizationAccessPolicyTreeItem.ts
@@ -0,0 +1,74 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProgressLocation, window } from "vscode";
+import { AzureParentTreeItem, AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationAccessPolicyContract } from "../azure/apim/contracts";
+import { localize } from "../localize";
+import { nonNullProp } from "../utils/nonNull";
+import { treeUtils } from "../utils/treeUtils";
+import { IAuthorizationAccessPolicyTreeRoot } from "./IAuthorizationAccessPolicyTreeRoot";
+
+export class AuthorizationAccessPolicyTreeItem extends AzureTreeItem {
+ public static contextValue: string = 'azureApiManagementAuthorizationAccessPolicy';
+ public contextValue: string = AuthorizationAccessPolicyTreeItem.contextValue;
+ public readonly commandId: string = 'azureApiManagement.showArmAuthorizationAccessPolicy';
+
+ private _label: string;
+ private _root: IAuthorizationAccessPolicyTreeRoot;
+
+ constructor(
+ parent: AzureParentTreeItem,
+ public readonly authorizationAccessPolicyContract: IAuthorizationAccessPolicyContract) {
+ super(parent);
+
+ this._root = this.createRoot(parent.root);
+
+ this._label = nonNullProp(authorizationAccessPolicyContract, 'name');
+ }
+
+ public get label() : string {
+ return this._label;
+ }
+
+ public get root(): IAuthorizationAccessPolicyTreeRoot {
+ return this._root;
+ }
+
+ public get description(): string | undefined {
+ return this.authorizationAccessPolicyContract.properties.objectId;
+ }
+
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('accesspolicy');
+ }
+
+ public async deleteTreeItemImpl(): Promise {
+ const message: string = localize("confirmAccessPolicyRemove", `Are you sure you want to remove Access Policy '${this.authorizationAccessPolicyContract.name}' from Authorization '${this.root.authorizationName}'?`);
+ const result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
+ if (result === DialogResponses.deleteResponse) {
+ const deletingMessage: string = localize("removingAuthorizationAccessPolicy", `Removing Access Policy "${this.authorizationAccessPolicyContract.name}" from Authorization '${this.root.authorizationName}.'`);
+ await window.withProgress({ location: ProgressLocation.Notification, title: deletingMessage }, async () => {
+ const apimService = new ApimService(this.root.credentials, this.root.environment.resourceManagerEndpointUrl, this.root.subscriptionId, this.root.resourceGroupName, this.root.serviceName);
+ await apimService.deleteAuthorizationAccessPolicy(
+ this.root.authorizationProviderName,
+ this.root.authorizationName,
+ nonNullProp(this.authorizationAccessPolicyContract, "name"));
+ });
+ // don't wait
+ window.showInformationMessage(localize("removedAuthorizationAccessPolicy", `Successfully removed Access Policy "${this.authorizationAccessPolicyContract.name}" from Authorization '${this.root.authorizationName}'.`));
+
+ } else {
+ throw new UserCancelledError();
+ }
+ }
+
+ private createRoot(subRoot: ISubscriptionContext): IAuthorizationAccessPolicyTreeRoot {
+ return Object.assign({}, subRoot, {
+ accessPolicyName: nonNullProp(this.authorizationAccessPolicyContract, 'name')
+ });
+ }
+}
diff --git a/src/explorer/AuthorizationProviderTreeItem.ts b/src/explorer/AuthorizationProviderTreeItem.ts
new file mode 100644
index 0000000..4f8086f
--- /dev/null
+++ b/src/explorer/AuthorizationProviderTreeItem.ts
@@ -0,0 +1,90 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProgressLocation, window } from "vscode";
+import { AzureParentTreeItem, AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationProviderContract } from "../azure/apim/contracts";
+import { localize } from "../localize";
+import { nonNullProp } from "../utils/nonNull";
+import { treeUtils } from "../utils/treeUtils";
+import { AuthorizationsTreeItem } from "./AuthorizationsTreeItem";
+import { AuthorizationTreeItem } from "./AuthorizationTreeItem";
+import { IAuthorizationProviderTreeRoot } from "./IAuthorizationProviderTreeRoot";
+import { IServiceTreeRoot } from "./IServiceTreeRoot";
+
+export class AuthorizationProviderTreeItem extends AzureParentTreeItem {
+ public static contextValue: string = 'azureApiManagementAuthorizationProvider';
+ public contextValue: string = AuthorizationProviderTreeItem.contextValue;
+ public readonly authorizationsTreeItem: AuthorizationsTreeItem;
+ public readonly commandId: string = 'azureApiManagement.showArmAuthorizationProvider';
+
+ private _label: string;
+ private _root: IAuthorizationProviderTreeRoot;
+
+ constructor(
+ parent: AzureParentTreeItem,
+ public readonly authorizationProviderContract: IAuthorizationProviderContract) {
+ super(parent);
+ this._label = nonNullProp(this.authorizationProviderContract, 'name');
+ this._root = this.createRoot(parent.root);
+
+ this.authorizationsTreeItem = new AuthorizationsTreeItem(this);
+ }
+
+ public get label() : string {
+ return this._label;
+ }
+
+ public get root(): IAuthorizationProviderTreeRoot {
+ return this._root;
+ }
+
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('authorizationprovider');
+ }
+
+ public hasMoreChildrenImpl(): boolean {
+ return false;
+ }
+
+ public async loadMoreChildrenImpl(): Promise[]> {
+ return [this.authorizationsTreeItem];
+ }
+
+ public pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): AzureTreeItem | undefined {
+ for (const expectedContextValue of expectedContextValues) {
+ switch (expectedContextValue) {
+ case AuthorizationTreeItem.contextValue:
+ return this.authorizationsTreeItem;
+ default:
+ }
+ }
+ return undefined;
+ }
+
+ public async deleteTreeItemImpl(): Promise {
+ const message: string = localize("confirmDeleteAuthorizationProvider", `Are you sure you want to remove Authorization provider '${this.authorizationProviderContract.name}'?`);
+ const result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
+ if (result === DialogResponses.deleteResponse) {
+ const deletingMessage: string = localize("removingAuthorizationProvider", `Removing Authorization provider "${this.authorizationProviderContract.name}".'`);
+ await window.withProgress({ location: ProgressLocation.Notification, title: deletingMessage }, async () => {
+ const apimService = new ApimService(this.root.credentials, this.root.environment.resourceManagerEndpointUrl, this.root.subscriptionId, this.root.resourceGroupName, this.root.serviceName);
+ await apimService.deleteAuthorizationProvider(this.root.authorizationProviderName);
+ });
+ // don't wait
+ window.showInformationMessage(localize("removedAuthorizationProvider", `Successfully removed Authorization provider "${this.authorizationProviderContract.name}".`));
+
+ } else {
+ throw new UserCancelledError();
+ }
+ }
+
+ private createRoot(subRoot: ISubscriptionContext): IAuthorizationProviderTreeRoot {
+ return Object.assign({}, subRoot, {
+ authorizationProviderName: nonNullProp(this.authorizationProviderContract, 'name')
+ });
+ }
+}
diff --git a/src/explorer/AuthorizationProvidersTreeItem.ts b/src/explorer/AuthorizationProvidersTreeItem.ts
new file mode 100644
index 0000000..0ff6baa
--- /dev/null
+++ b/src/explorer/AuthorizationProvidersTreeItem.ts
@@ -0,0 +1,171 @@
+
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProgressLocation, window } from "vscode";
+import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationProviderContract, IAuthorizationProviderOAuth2GrantTypesContract, IAuthorizationProviderPropertiesContract, IGrantTypesContract, ITokenStoreGrantTypeParameterContract, ITokenStoreIdentityProviderContract } from "../azure/apim/contracts";
+import { askAuthorizationProviderParameterValues, askId } from "../commands/authorizations/common";
+import { ext } from "../extensionVariables";
+import { localize } from "../localize";
+import { processError } from "../utils/errorUtil";
+import { treeUtils } from "../utils/treeUtils";
+import { AuthorizationProviderTreeItem } from "./AuthorizationProviderTreeItem";
+import { IServiceTreeRoot } from "./IServiceTreeRoot";
+
+export interface IAuthorizationProviderTreeItemContext extends ICreateChildImplContext {
+ name: string;
+ authorizationProvider: IAuthorizationProviderPropertiesContract;
+}
+
+export class AuthorizationProvidersTreeItem extends AzureParentTreeItem {
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('list');
+ }
+ public static contextValue: string = 'azureApiManagementAuthorizationProviders';
+ public readonly childTypeLabel: string = localize('azureApiManagement.AuthorizationProvider', 'AuthorizationProvider');
+ public label: string = "Authorizations (preview)";
+ public contextValue: string = AuthorizationProvidersTreeItem.contextValue;
+ private _nextLink: string | undefined;
+ private apimService: ApimService;
+
+ public hasMoreChildrenImpl(): boolean {
+ return this._nextLink !== undefined;
+ }
+
+ public async loadMoreChildrenImpl(clearCache: boolean): Promise {
+ if (clearCache) {
+ this._nextLink = undefined;
+ }
+
+ this.apimService = new ApimService(
+ this.root.credentials,
+ this.root.environment.resourceManagerEndpointUrl,
+ this.root.subscriptionId,
+ this.root.resourceGroupName,
+ this.root.serviceName);
+
+ const tokenProviders: IAuthorizationProviderContract[] = await this.apimService.listAuthorizationProviders();
+
+ return this.createTreeItemsWithErrorHandling(
+ tokenProviders,
+ "invalidApiManagementAuthorizationProvider",
+ async (authorizationProvider: IAuthorizationProviderContract) => new AuthorizationProviderTreeItem(this, authorizationProvider),
+ (authorizationProvider: IAuthorizationProviderContract) => {
+ return authorizationProvider.name;
+ });
+ }
+
+ public async createChildImpl(context: IAuthorizationProviderTreeItemContext): Promise {
+ await this.checkManagedIdentityEnabled();
+ await this.buildContext(context);
+ if (context.name !== null && context.authorizationProvider !== null) {
+ return window.withProgress(
+ {
+ location: ProgressLocation.Notification,
+ title: localize("creatingAuthorizationProvider", `Creating Authorization provider '${context.name}' in service ${this.root.serviceName} ...`),
+ cancellable: false
+ },
+ // tslint:disable-next-line:no-non-null-assertion
+ async (): Promise => {
+ const authorizationProviderName = context.name;
+ context.showCreatingTreeItem(authorizationProviderName);
+ try {
+ let authorizationProvider = await this.apimService.getAuthorizationProvider(context.name);
+ if (authorizationProvider === undefined) {
+ authorizationProvider = await this.apimService.createAuthorizationProvider(context.name, context.authorizationProvider);
+ const message = `Please add redirect url '${authorizationProvider.properties.oauth2?.redirectUrl}' to the OAuth application.`;
+ ext.outputChannel.show();
+ ext.outputChannel.appendLine(message);
+ window.showWarningMessage(localize("redirectUrlMessage", message));
+ window.showInformationMessage(localize("createdAuthorizationProvider", `Created Authorization provider '${context.name}'.`));
+ return new AuthorizationProviderTreeItem(this, authorizationProvider);
+ } else {
+ throw new Error(localize("createAuthorizationProvider", `Authorization provider '${authorizationProviderName}' already exists.`));
+ }
+ } catch (error) {
+ throw new Error(processError(error, localize("createAuthorizationProvider", `Failed to create Authorization provider '${authorizationProviderName}'.`)));
+ }
+ }
+ );
+ } else {
+ throw Error("Expected Authorization provider information.");
+ }
+ }
+
+ private async checkManagedIdentityEnabled() : Promise {
+ const service = await this.apimService.getService();
+ if (service.identity === undefined) {
+ const options = ['Yes', 'No'];
+ const option = await ext.ui.showQuickPick(options.map((s) => { return { label: s, description: '', detail: '' }; }), { placeHolder: 'Enable system assigned managed identity', canPickMany: false });
+ if (option.label === options[0]) {
+ await window.withProgress(
+ {
+ location: ProgressLocation.Notification,
+ title: localize("enableManagedIdentity", `Enabling system assigned managed identity.`),
+ cancellable: false
+ },
+ async () => {
+ await this.apimService.turnOnManagedIdentity();
+ window.showInformationMessage(localize("enabledManagedIdentity", `Enabled system assigned managed identity.`));
+ }
+ );
+ }
+ }
+ }
+
+ private async buildContext(context: IAuthorizationProviderTreeItemContext): Promise {
+ let supportedIdentityProviders: ITokenStoreIdentityProviderContract[] = await this.apimService.listTokenStoreIdentityProviders();
+ // tslint:disable-next-line:no-function-expression
+ supportedIdentityProviders = supportedIdentityProviders.sort(function compare(a: ITokenStoreIdentityProviderContract, b: ITokenStoreIdentityProviderContract): number {
+ return a.properties.displayName.localeCompare(b.properties.displayName);
+ });
+
+ const identityProviderPicked = await ext.ui.showQuickPick(supportedIdentityProviders.map((s) => { return { label: s.properties.displayName, description: '', detail: '' }; }), { placeHolder: 'Select identity provider ...', canPickMany: false });
+ const selectedIdentityProvider = supportedIdentityProviders.find(s => s.properties.displayName === identityProviderPicked.label);
+
+ let grantType: string = "";
+ if (selectedIdentityProvider
+ && selectedIdentityProvider.properties.oauth2.grantTypes !== null) {
+ const authorizationProviderName = await askId(
+ 'Enter Authorization provider name ...',
+ 'Invalid Authorization provider name.');
+
+ const grantTypes = Object.keys(selectedIdentityProvider.properties.oauth2.grantTypes);
+ if (grantTypes.length > 1) {
+ const grantTypePicked = await ext.ui.showQuickPick(grantTypes.map((s) => { return { label: s[0].toUpperCase() + s.slice(1), description: '', detail: '' }; }), { placeHolder: 'Select grant type ...', canPickMany: false });
+ grantType = grantTypePicked.label[0].toLocaleLowerCase() + grantTypePicked.label.slice(1);
+ } else {
+ grantType = grantTypes[0];
+ }
+
+ const grantTypeValue: IGrantTypesContract = grantType;
+
+ // tslint:disable-next-line:no-any
+ // tslint:disable-next-line:no-unsafe-any
+ const grant: ITokenStoreGrantTypeParameterContract = selectedIdentityProvider?.properties.oauth2.grantTypes[grantType];
+
+ const parameterValues = await askAuthorizationProviderParameterValues(grant);
+
+ const authorizationProviderGrant: IAuthorizationProviderOAuth2GrantTypesContract = {};
+ if (grantTypeValue === IGrantTypesContract.authorizationCode) {
+ authorizationProviderGrant.authorizationCode = parameterValues;
+ } else if (grantTypeValue === IGrantTypesContract.clientCredentials) {
+ authorizationProviderGrant.clientCredentials = parameterValues;
+ }
+
+ const authorizationProviderPayload: IAuthorizationProviderPropertiesContract = {
+ identityProvider: selectedIdentityProvider.name,
+ oauth2: {
+ grantTypes: authorizationProviderGrant
+ }
+ };
+
+ context.name = authorizationProviderName;
+ context.authorizationProvider = authorizationProviderPayload;
+ }
+ }
+}
diff --git a/src/explorer/AuthorizationTreeItem.ts b/src/explorer/AuthorizationTreeItem.ts
new file mode 100644
index 0000000..580771e
--- /dev/null
+++ b/src/explorer/AuthorizationTreeItem.ts
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProgressLocation, window } from "vscode";
+import { AzureParentTreeItem, AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationContract } from "../azure/apim/contracts";
+import { localize } from "../localize";
+import { nonNullProp } from "../utils/nonNull";
+import { treeUtils } from "../utils/treeUtils";
+import { AuthorizationAccessPoliciesTreeItem } from "./AuthorizationAccessPoliciesTreeItem";
+import { AuthorizationAccessPolicyTreeItem } from "./AuthorizationAccessPolicyTreeItem";
+import { IAuthorizationProviderTreeRoot } from "./IAuthorizationProviderTreeRoot";
+import { IAuthorizationTreeRoot } from "./IAuthorizationTreeRoot";
+
+export class AuthorizationTreeItem extends AzureParentTreeItem {
+ public static contextValue: string = 'azureApiManagementAuthorization';
+ public readonly authorizationAccessPoliciesTreeItem: AuthorizationAccessPoliciesTreeItem;
+ public contextValue: string = AuthorizationTreeItem.contextValue;
+ public readonly commandId: string = 'azureApiManagement.showArmAuthorization';
+ private _label: string;
+ private _root: IAuthorizationTreeRoot;
+
+ constructor(
+ parent: AzureParentTreeItem,
+ public authorizationContract: IAuthorizationContract) {
+ super(parent);
+ this._label = nonNullProp(authorizationContract, 'name');
+
+ this._root = this.createRoot(parent.root);
+
+ this.authorizationAccessPoliciesTreeItem = new AuthorizationAccessPoliciesTreeItem(this);
+ }
+
+ public get label(): string {
+ return this._label;
+ }
+
+ public get description(): string | undefined {
+ return this.authorizationContract.properties.status;
+ }
+
+ public get root(): IAuthorizationTreeRoot {
+ return this._root;
+ }
+
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('authorization');
+ }
+
+ public async loadMoreChildrenImpl(): Promise[]> {
+ return [this.authorizationAccessPoliciesTreeItem];
+ }
+
+ public hasMoreChildrenImpl(): boolean {
+ return false;
+ }
+
+ public pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): AzureTreeItem | undefined {
+ for (const expectedContextValue of expectedContextValues) {
+ switch (expectedContextValue) {
+ case AuthorizationAccessPolicyTreeItem.contextValue:
+ return this.authorizationAccessPoliciesTreeItem;
+ default:
+ }
+ }
+ return undefined;
+ }
+
+ public async deleteTreeItemImpl(): Promise {
+ const message: string = localize("confirmAuthorizationRemove", `Are you sure you want to remove Authorization '${this.authorizationContract.name}' from Authorization provider '${this.root.authorizationProviderName}'?`);
+ const result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
+ if (result === DialogResponses.deleteResponse) {
+ const deletingMessage: string = localize("removingAuthorization", `Removing Authorization "${this.authorizationContract.name}" from Authorization provider '${this.root.authorizationProviderName}.'`);
+ await window.withProgress({ location: ProgressLocation.Notification, title: deletingMessage }, async () => {
+ const apimService = new ApimService(
+ this.root.credentials,
+ this.root.environment.resourceManagerEndpointUrl,
+ this.root.subscriptionId,
+ this.root.resourceGroupName,
+ this.root.serviceName);
+ await apimService.deleteAuthorization(this.root.authorizationProviderName, nonNullProp(this.authorizationContract, "name"));
+ });
+ // don't wait
+ window.showInformationMessage(localize("removedAuthorization", `Successfully removed authorization "${this.authorizationContract.name}" from Authorization provider '${this.root.authorizationProviderName}'.`));
+
+ } else {
+ throw new UserCancelledError();
+ }
+ }
+
+ private createRoot(subRoot: ISubscriptionContext): IAuthorizationTreeRoot {
+ return Object.assign({}, subRoot, {
+ authorizationName: nonNullProp(this.authorizationContract, 'name')
+ });
+ }
+}
diff --git a/src/explorer/AuthorizationsTreeItem.ts b/src/explorer/AuthorizationsTreeItem.ts
new file mode 100644
index 0000000..4c9c5eb
--- /dev/null
+++ b/src/explorer/AuthorizationsTreeItem.ts
@@ -0,0 +1,110 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ProgressLocation, window } from "vscode";
+import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui";
+import { ApimService } from "../azure/apim/ApimService";
+import { IAuthorizationContract, IAuthorizationPropertiesContract, IAuthorizationProviderContract, IGrantTypesContract, ITokenStoreIdentityProviderContract } from "../azure/apim/contracts";
+import { askAuthorizationParameterValues, askId, IParameterValues } from "../commands/authorizations/common";
+import { localize } from "../localize";
+import { processError } from "../utils/errorUtil";
+import { nonNullValue } from "../utils/nonNull";
+import { treeUtils } from "../utils/treeUtils";
+import { AuthorizationProviderTreeItem } from "./AuthorizationProviderTreeItem";
+import { AuthorizationTreeItem } from "./AuthorizationTreeItem";
+import { IAuthorizationProviderTreeRoot } from "./IAuthorizationProviderTreeRoot";
+
+export interface IAuthorizationTreeItemContext extends ICreateChildImplContext {
+ authorizationName: string;
+ authorization: IAuthorizationPropertiesContract;
+}
+
+export class AuthorizationsTreeItem extends AzureParentTreeItem {
+ public get iconPath(): { light: string, dark: string } {
+ return treeUtils.getThemedIconPath('list');
+ }
+ public static contextValue: string = 'azureApiManagementAuthorizations';
+ public label: string = "Authorizations";
+ public contextValue: string = AuthorizationsTreeItem.contextValue;
+ public readonly childTypeLabel: string = localize('azureApiManagement.Authorization', 'Authorization');
+ private _nextLink: string | undefined;
+ private apimService: ApimService;
+
+ public hasMoreChildrenImpl(): boolean {
+ return this._nextLink !== undefined;
+ }
+
+ public async loadMoreChildrenImpl(clearCache: boolean): Promise {
+ if (clearCache) {
+ this._nextLink = undefined;
+ }
+
+ this.apimService = new ApimService(
+ this.root.credentials,
+ this.root.environment.resourceManagerEndpointUrl,
+ this.root.subscriptionId,
+ this.root.resourceGroupName,
+ this.root.serviceName);
+
+ const authorizations: IAuthorizationContract[] = await this.apimService.listAuthorizations(this.root.authorizationProviderName);
+
+ return this.createTreeItemsWithErrorHandling(
+ authorizations,
+ "invalidApiManagementAuthorization",
+ async (authorization: IAuthorizationContract) => new AuthorizationTreeItem(this, authorization),
+ (authorization: IAuthorizationContract) => {
+ return authorization.name;
+ });
+ }
+
+ public async createChildImpl(context: IAuthorizationTreeItemContext): Promise {
+ await this.buildContext(context);
+ if (context.authorizationName !== null
+ && context.authorization !== null) {
+ return window.withProgress(
+ {
+ location: ProgressLocation.Notification,
+ title: localize("creatingAuthorization", `Creating Authorization '${context.authorizationName}' under Authorization Provider ${this.root.authorizationProviderName} ...`),
+ cancellable: false
+ },
+ // tslint:disable-next-line:no-non-null-assertion
+ async () => {
+ const authorizationName = context.authorizationName;
+ context.showCreatingTreeItem(authorizationName);
+ try {
+ const apimService = new ApimService(this.root.credentials, this.root.environment.resourceManagerEndpointUrl, this.root.subscriptionId, this.root.resourceGroupName, this.root.serviceName);
+ let authorization = await apimService.getAuthorization(this.root.authorizationProviderName, authorizationName);
+ if (authorization === undefined) {
+ authorization = await apimService.createAuthorization(this.root.authorizationProviderName, authorizationName, context.authorization);
+ window.showInformationMessage(localize("createdAuthorization", `Created Authorization '${authorizationName}' succesfully.`));
+ return new AuthorizationTreeItem(this, authorization);
+ } else {
+ throw new Error(localize("createAuthorization", `Authorization '${authorizationName}' already exists.`));
+ }
+ } catch (error) {
+ throw new Error(processError(error, localize("createAuthorization", `Failed to add authorization '${authorizationName}' to Authorization provider '${this.root.authorizationProviderName}'.`)));
+ }
+ }
+ );
+ } else {
+ throw Error("Expected Authorization name.");
+ }
+ }
+
+ private async buildContext(context: IAuthorizationTreeItemContext): Promise {
+ const authorizationProvider: IAuthorizationProviderContract = (this.parent).authorizationProviderContract;
+ const authorizationName = await askId('Enter Authorization name ...', 'Invalid Authorization name ...');
+ context.authorizationName = authorizationName;
+ let parameterValues: IParameterValues = {};
+ let grantType = IGrantTypesContract.authorizationCode;
+ if (authorizationProvider.properties.oauth2?.grantTypes.clientCredentials) {
+ grantType = IGrantTypesContract.clientCredentials;
+ const identityProvider: ITokenStoreIdentityProviderContract = await this.apimService.getTokenStoreIdentityProvider(authorizationProvider.properties.identityProvider);
+ const grant = identityProvider.properties.oauth2.grantTypes.clientCredentials;
+ parameterValues = await askAuthorizationParameterValues(nonNullValue(grant));
+ }
+ context.authorization = { authorizationType: "oauth2", oauth2grantType: grantType, parameters: parameterValues };
+ }
+}
diff --git a/src/explorer/IAuthorizationAccessPolicyTreeRoot.ts b/src/explorer/IAuthorizationAccessPolicyTreeRoot.ts
new file mode 100644
index 0000000..3e0a9bd
--- /dev/null
+++ b/src/explorer/IAuthorizationAccessPolicyTreeRoot.ts
@@ -0,0 +1,10 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IAuthorizationTreeRoot } from "./IAuthorizationTreeRoot";
+
+export interface IAuthorizationAccessPolicyTreeRoot extends IAuthorizationTreeRoot {
+ accessPolicyName: string;
+}
diff --git a/src/explorer/IAuthorizationProviderTreeRoot.ts b/src/explorer/IAuthorizationProviderTreeRoot.ts
new file mode 100644
index 0000000..d6274ae
--- /dev/null
+++ b/src/explorer/IAuthorizationProviderTreeRoot.ts
@@ -0,0 +1,10 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IServiceTreeRoot } from "./IServiceTreeRoot";
+
+export interface IAuthorizationProviderTreeRoot extends IServiceTreeRoot {
+ authorizationProviderName: string;
+}
diff --git a/src/explorer/IAuthorizationTreeRoot.ts b/src/explorer/IAuthorizationTreeRoot.ts
new file mode 100644
index 0000000..eec64a1
--- /dev/null
+++ b/src/explorer/IAuthorizationTreeRoot.ts
@@ -0,0 +1,10 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IAuthorizationProviderTreeRoot } from "./IAuthorizationProviderTreeRoot";
+
+export interface IAuthorizationTreeRoot extends IAuthorizationProviderTreeRoot {
+ authorizationName: string;
+}
diff --git a/src/explorer/ServiceTreeItem.ts b/src/explorer/ServiceTreeItem.ts
index 2713eeb..98f57a6 100644
--- a/src/explorer/ServiceTreeItem.ts
+++ b/src/explorer/ServiceTreeItem.ts
@@ -14,6 +14,10 @@ import { ApiOperationTreeItem } from "./ApiOperationTreeItem";
import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem";
import { ApisTreeItem } from "./ApisTreeItem";
import { ApiTreeItem } from "./ApiTreeItem";
+import { AuthorizationAccessPolicyTreeItem } from "./AuthorizationAccessPolicyTreeItem";
+import { AuthorizationProvidersTreeItem } from "./AuthorizationProvidersTreeItem";
+import { AuthorizationProviderTreeItem } from "./AuthorizationProviderTreeItem";
+import { AuthorizationTreeItem } from "./AuthorizationTreeItem";
import { GatewaysTreeItem } from "./GatewaysTreeItem";
import { IServiceTreeRoot } from "./IServiceTreeRoot";
import { NamedValuesTreeItem } from "./NamedValuesTreeItem";
@@ -47,6 +51,7 @@ export class ServiceTreeItem extends AzureParentTreeItem {
public readonly productsTreeItem: ProductsTreeItem;
public readonly gatewaysTreeItem: GatewaysTreeItem;
public readonly subscriptionsTreeItem: SubscriptionsTreeItem;
+ public readonly authorizationProvidersTreeItem: AuthorizationProvidersTreeItem;
private _root: IServiceTreeRoot;
@@ -62,6 +67,7 @@ export class ServiceTreeItem extends AzureParentTreeItem {
this.productsTreeItem = new ProductsTreeItem(this);
this.namedValuesTreeItem = new NamedValuesTreeItem(this);
this.subscriptionsTreeItem = new SubscriptionsTreeItem(this);
+ this.authorizationProvidersTreeItem = new AuthorizationProvidersTreeItem(this);
//parent.iconPath =
const sku = nonNullValue(this.apiManagementService.sku.name);
@@ -76,9 +82,9 @@ export class ServiceTreeItem extends AzureParentTreeItem {
public async loadMoreChildrenImpl(): Promise[]> {
if (this.gatewaysTreeItem === undefined) {
- return [this.apisTreeItem, this.namedValuesTreeItem, this.productsTreeItem, this.servicePolicyTreeItem, this.subscriptionsTreeItem];
+ return [this.apisTreeItem, this.namedValuesTreeItem, this.productsTreeItem, this.servicePolicyTreeItem, this.subscriptionsTreeItem, this.authorizationProvidersTreeItem];
}
- return [this.apisTreeItem, this.namedValuesTreeItem, this.productsTreeItem, this.servicePolicyTreeItem, this.gatewaysTreeItem, this.subscriptionsTreeItem];
+ return [this.apisTreeItem, this.namedValuesTreeItem, this.productsTreeItem, this.servicePolicyTreeItem, this.gatewaysTreeItem, this.subscriptionsTreeItem, this.authorizationProvidersTreeItem];
}
public hasMoreChildrenImpl(): boolean {
@@ -124,6 +130,10 @@ export class ServiceTreeItem extends AzureParentTreeItem {
case ProductTreeItem.contextValue:
case ProductPolicyTreeItem.contextValue:
return this.productsTreeItem;
+ case AuthorizationProviderTreeItem.contextValue:
+ case AuthorizationTreeItem.contextValue:
+ case AuthorizationAccessPolicyTreeItem.contextValue:
+ return this.authorizationProvidersTreeItem;
default:
}
}
diff --git a/src/explorer/editors/arm/AuthorizationAccessPolicyResourceEditor.ts b/src/explorer/editors/arm/AuthorizationAccessPolicyResourceEditor.ts
new file mode 100644
index 0000000..cf60b1a
--- /dev/null
+++ b/src/explorer/editors/arm/AuthorizationAccessPolicyResourceEditor.ts
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import { AzureTreeItem } from "vscode-azureextensionui";
+import { ApimService } from "../../../azure/apim/ApimService";
+import { IAuthorizationAccessPolicyContract } from "../../../azure/apim/contracts";
+
+import { nonNullValue } from "../../../utils/nonNull";
+import { IAuthorizationAccessPolicyTreeRoot } from "../../IAuthorizationAccessPolicyTreeRoot";
+import { BaseArmResourceEditor } from "./BaseArmResourceEditor";
+
+// tslint:disable-next-line:no-any
+export class AuthorizationAccessPolicyResourceEditor extends BaseArmResourceEditor {
+ public entityType: string = "AuthorizationAccessPolicy";
+ constructor() {
+ super();
+ }
+
+ public async getDataInternal(context: AzureTreeItem): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.getAuthorizationAccessPolicy(context.root.authorizationProviderName, context.root.authorizationName, context.root.accessPolicyName);
+ return nonNullValue(response);
+ }
+
+ public async updateDataInternal(context: AzureTreeItem, payload: IAuthorizationAccessPolicyContract): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.createAuthorizationAccessPolicy(context.root.authorizationProviderName, context.root.authorizationName, context.root.accessPolicyName, payload.properties);
+ return nonNullValue(response);
+ }
+}
diff --git a/src/explorer/editors/arm/AuthorizationProviderResourceEditor.ts b/src/explorer/editors/arm/AuthorizationProviderResourceEditor.ts
new file mode 100644
index 0000000..a4ccf8b
--- /dev/null
+++ b/src/explorer/editors/arm/AuthorizationProviderResourceEditor.ts
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import { AzureTreeItem } from "vscode-azureextensionui";
+import { ApimService } from "../../../azure/apim/ApimService";
+import { IAuthorizationProviderContract } from "../../../azure/apim/contracts";
+import { nonNullValue } from "../../../utils/nonNull";
+import { IAuthorizationProviderTreeRoot } from "../../IAuthorizationProviderTreeRoot";
+import { BaseArmResourceEditor } from "./BaseArmResourceEditor";
+
+// tslint:disable-next-line:no-any
+export class AuthorizationProviderResourceEditor extends BaseArmResourceEditor {
+ public entityType: string = "AuthorizationProvider";
+ constructor() {
+ super();
+ }
+
+ public async getDataInternal(context: AzureTreeItem): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.getAuthorizationProvider(context.root.authorizationProviderName);
+ return nonNullValue(response);
+ }
+
+ public async updateDataInternal(context: AzureTreeItem, payload: IAuthorizationProviderContract): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.createAuthorizationProvider(context.root.authorizationProviderName, payload.properties);
+ return nonNullValue(response);
+ }
+}
diff --git a/src/explorer/editors/arm/AuthorizationResourceEditor.ts b/src/explorer/editors/arm/AuthorizationResourceEditor.ts
new file mode 100644
index 0000000..06df512
--- /dev/null
+++ b/src/explorer/editors/arm/AuthorizationResourceEditor.ts
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.md in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import { AzureTreeItem } from "vscode-azureextensionui";
+import { ApimService } from "../../../azure/apim/ApimService";
+import { IAuthorizationContract } from "../../../azure/apim/contracts";
+
+import { nonNullValue } from "../../../utils/nonNull";
+import { IAuthorizationTreeRoot } from "../../IAuthorizationTreeRoot";
+import { BaseArmResourceEditor } from "./BaseArmResourceEditor";
+
+// tslint:disable-next-line:no-any
+export class AuthorizationResourceEditor extends BaseArmResourceEditor {
+ public entityType: string = "Authorization";
+ constructor() {
+ super();
+ }
+
+ public async getDataInternal(context: AzureTreeItem): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.getAuthorization(context.root.authorizationProviderName, context.root.authorizationName);
+ return nonNullValue(response);
+ }
+
+ public async updateDataInternal(context: AzureTreeItem, payload: IAuthorizationContract): Promise {
+ const apimService = new ApimService(
+ context.root.credentials,
+ context.root.environment.resourceManagerEndpointUrl,
+ context.root.subscriptionId,
+ context.root.resourceGroupName,
+ context.root.serviceName);
+
+ const response = await apimService.createAuthorization(context.root.authorizationProviderName, context.root.authorizationName, payload.properties);
+ return nonNullValue(response);
+ }
+}
diff --git a/src/extension.ts b/src/extension.ts
index 966425c..6b3454c 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -6,11 +6,18 @@
'use strict';
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
+import * as query from 'querystring';
import * as vscode from 'vscode';
import { AzExtTreeDataProvider, AzureParentTreeItem, AzureTreeItem, AzureUserInput, callWithTelemetryAndErrorHandling, createAzExtOutputChannel, IActionContext, registerCommand, registerEvent, registerUIExtensionVariables } from 'vscode-azureextensionui';
import { addApiFilter } from './commands/addApiFilter';
import { addApiToGateway } from './commands/addApiToGateway';
import { addApiToProduct } from './commands/addApiToProduct';
+import { authorizeAuthorization } from './commands/authorizations/authorizeAuthorization';
+import { copyAuthorizationPolicy } from './commands/authorizations/copyAuthorizationPolicy';
+import { copyAuthorizationProviderRedirectUrl } from './commands/authorizations/copyAuthorizationProviderRedirectUrl';
+import { createAuthorization } from './commands/authorizations/createAuthorization';
+import { createAuthorizationAccessPolicy } from './commands/authorizations/createAuthorizationAccessPolicy';
+import { createAuthorizationProvider } from './commands/authorizations/createAuthorizationProvider';
import { copySubscriptionKey } from './commands/copySubscriptionKey';
import { createService } from './commands/createService';
import { debugPolicy } from './commands/debugPolicies/debugPolicy';
@@ -38,8 +45,17 @@ import { ApiOperationTreeItem } from './explorer/ApiOperationTreeItem';
import { ApiPolicyTreeItem } from './explorer/ApiPolicyTreeItem';
import { ApisTreeItem } from './explorer/ApisTreeItem';
import { ApiTreeItem } from './explorer/ApiTreeItem';
+import { AuthorizationAccessPoliciesTreeItem } from './explorer/AuthorizationAccessPoliciesTreeItem';
+import { AuthorizationAccessPolicyTreeItem } from './explorer/AuthorizationAccessPolicyTreeItem';
+import { AuthorizationProvidersTreeItem } from './explorer/AuthorizationProvidersTreeItem';
+import { AuthorizationProviderTreeItem } from './explorer/AuthorizationProviderTreeItem';
+import { AuthorizationsTreeItem } from './explorer/AuthorizationsTreeItem';
+import { AuthorizationTreeItem } from './explorer/AuthorizationTreeItem';
import { AzureAccountTreeItem } from './explorer/AzureAccountTreeItem';
import { ApiResourceEditor } from './explorer/editors/arm/ApiResourceEditor';
+import { AuthorizationAccessPolicyResourceEditor } from './explorer/editors/arm/AuthorizationAccessPolicyResourceEditor';
+import { AuthorizationProviderResourceEditor } from './explorer/editors/arm/AuthorizationProviderResourceEditor';
+import { AuthorizationResourceEditor } from './explorer/editors/arm/AuthorizationResourceEditor';
import { OperationResourceEditor } from './explorer/editors/arm/OperationResourceEditor';
import { ProductResourceEditor } from './explorer/editors/arm/ProductResourceEditor';
import { OpenApiEditor } from './explorer/editors/openApi/OpenApiEditor';
@@ -61,6 +77,7 @@ import { ServicePolicyTreeItem } from './explorer/ServicePolicyTreeItem';
import { ServiceTreeItem } from './explorer/ServiceTreeItem';
import { SubscriptionTreeItem } from './explorer/SubscriptionTreeItem';
import { ext } from './extensionVariables';
+import { localize } from './localize';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
@@ -86,8 +103,13 @@ export async function activateInternal(context: vscode.ExtensionContext) {
registerCommands(ext.tree);
registerEditors(context);
- activate(context); // activeta debug context
+ const handler = new UriEventHandler();
+ context.subscriptions.push(
+ vscode.window.registerUriHandler(handler)
+ );
+
+ activate(context); // activeta debug context
});
}
@@ -133,6 +155,16 @@ function registerCommands(tree: AzExtTreeDataProvider): void {
registerCommand('azureApiManagement.setCustomHostName', setCustomHostName);
registerCommand('azureApiManagement.createSubscription', createSubscription);
registerCommand('azureApiManagement.deleteSubscription', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, SubscriptionTreeItem.contextValue, node));
+
+ registerCommand('azureApiManagement.createAuthorizationProvider', async (context: IActionContext, node?: AuthorizationProvidersTreeItem) => { await createAuthorizationProvider(context, node); });
+ registerCommand('azureApiManagement.createAuthorization', async (context: IActionContext, node?: AuthorizationsTreeItem) => { await createAuthorization(context, node); });
+ registerCommand('azureApiManagement.createAuthorizationAccessPolicy', async (context: IActionContext, node?: AuthorizationAccessPoliciesTreeItem) => { await createAuthorizationAccessPolicy(context, node); });
+ registerCommand('azureApiManagement.deleteAuthorizationProvider', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, AuthorizationProviderTreeItem.contextValue, node));
+ registerCommand('azureApiManagement.copyAuthorizationProviderRedirectUrl', async (context: IActionContext, node?: AuthorizationProviderTreeItem) => await copyAuthorizationProviderRedirectUrl(context, node));
+ registerCommand('azureApiManagement.authorizeAuthorization', async (context: IActionContext, node?: AuthorizationTreeItem) => { await authorizeAuthorization(context, node); });
+ registerCommand('azureApiManagement.copyAuthorizationPolicy', async (context: IActionContext, node?: AuthorizationTreeItem) => { await copyAuthorizationPolicy(context, node); });
+ registerCommand('azureApiManagement.deleteAuthorization', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, AuthorizationTreeItem.contextValue, node));
+ registerCommand('azureApiManagement.deleteAuthorizationAccessPolicy', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, AuthorizationAccessPolicyTreeItem.contextValue, node));
}
// tslint:disable-next-line: max-func-body-length
@@ -248,9 +280,72 @@ function registerEditors(context: vscode.ExtensionContext) : void {
await productPolicyEditor.showEditor(node);
vscode.commands.executeCommand('setContext', 'isEditorEnabled', true);
}, doubleClickDebounceDelay);
+
+ const authorizationProviderResourceEditor: AuthorizationProviderResourceEditor = new AuthorizationProviderResourceEditor();
+ context.subscriptions.push(authorizationProviderResourceEditor);
+ registerEvent('azureApiManagement.AuthorizationProviderResourceEditor.onDidSaveTextDocument',
+ vscode.workspace.onDidSaveTextDocument, async (actionContext: IActionContext, doc: vscode.TextDocument) => {
+ await authorizationProviderResourceEditor.onDidSaveTextDocument(actionContext, context.globalState, doc); });
+
+ registerCommand('azureApiManagement.showArmAuthorizationProvider', async (actionContext: IActionContext, node?: AuthorizationProviderTreeItem) => {
+ if (!node) {
+ node = await ext.tree.showTreeItemPicker(AuthorizationProviderTreeItem.contextValue, actionContext);
+ }
+ await authorizationProviderResourceEditor.showEditor(node);
+ vscode.commands.executeCommand('setContext', 'isEditorEnabled', true);
+ }, doubleClickDebounceDelay);
+
+ const authorizationResourceEditor: AuthorizationResourceEditor = new AuthorizationResourceEditor();
+ context.subscriptions.push(authorizationResourceEditor);
+ registerEvent('azureApiManagement.AuthorizationResourceEditor.onDidSaveTextDocument',
+ vscode.workspace.onDidSaveTextDocument, async (actionContext: IActionContext, doc: vscode.TextDocument) => {
+ await authorizationResourceEditor.onDidSaveTextDocument(actionContext, context.globalState, doc); });
+
+ registerCommand('azureApiManagement.showArmAuthorization', async (actionContext: IActionContext, node?: AuthorizationTreeItem) => {
+ if (!node) {
+ node = await ext.tree.showTreeItemPicker(AuthorizationTreeItem.contextValue, actionContext);
+ }
+ await authorizationResourceEditor.showEditor(node);
+ vscode.commands.executeCommand('setContext', 'isEditorEnabled', true);
+ }, doubleClickDebounceDelay);
+
+ const authorizationAccessPolicyResourceEditor: AuthorizationAccessPolicyResourceEditor = new AuthorizationAccessPolicyResourceEditor();
+ context.subscriptions.push(authorizationAccessPolicyResourceEditor);
+ registerEvent('azureApiManagement.AuthorizationAccessPolicyResourceEditor.onDidSaveTextDocument',
+ vscode.workspace.onDidSaveTextDocument, async (actionContext: IActionContext, doc: vscode.TextDocument) => {
+ await authorizationAccessPolicyResourceEditor.onDidSaveTextDocument(actionContext, context.globalState, doc); });
+
+ registerCommand('azureApiManagement.showArmAuthorizationAccessPolicy', async (actionContext: IActionContext, node?: AuthorizationAccessPolicyTreeItem) => {
+ if (!node) {
+ node = await ext.tree.showTreeItemPicker(AuthorizationAccessPolicyTreeItem.contextValue, actionContext);
+ }
+ await authorizationAccessPolicyResourceEditor.showEditor(node);
+ vscode.commands.executeCommand('setContext', 'isEditorEnabled', true);
+ }, doubleClickDebounceDelay);
}
// this method is called when your extension is deactivated
// tslint:disable:typedef
// tslint:disable-next-line:no-empty
export function deactivateInternal() {}
+
+class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler {
+ public handleUri(uri: vscode.Uri) {
+ if (uri.path.startsWith('/vscodeauthcomplete')) {
+ if (uri.query !== null && uri.query.includes('error')) {
+ const queryParams = >query.parse(uri.query);
+ // tslint:disable-next-line:no-string-literal
+ const errorValue = queryParams['error'];
+ const errorDecoded = new Buffer(errorValue, 'base64');
+ ext.outputChannel.appendLine(localize('authFailed', `Authorization failed. ${errorDecoded.toString('utf8')}.`));
+ vscode.window.showInformationMessage(localize('authFailed', `Authorization failed. ${errorDecoded.toString('utf8')}.`));
+ } else {
+ ext.outputChannel.appendLine(localize('authComplete', `Authorization success. ${uri.path}`));
+ const authProvider = uri.path.split('/')[2];
+ const authorization = uri.path.split('/')[3];
+ vscode.window.showInformationMessage(localize('authSuccess', `Authorized '${authorization}' under Authorization Provider '${authProvider}'.`));
+ vscode.window.showInformationMessage(localize('closeBrowserWindow', `You can now close the browser window that was launched during the authorization process.`));
+ }
+ }
+ }
+}