Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Add possibility to extract identity providers. #747

Merged
merged 13 commits into from
Jun 21, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ public class ExtractorConsoleAppConfiguration
[Option(longName: "paramApiOauth2Scope", HelpText = "Parametrize API OAuth2 scope values")]
public string ParamApiOauth2Scope { get; set; }

[Option(longName: "extractSecrets", HelpText = "Exrtact secrets from the services if applies")]
f-alizada marked this conversation as resolved.
Show resolved Hide resolved
public string ExtractSecrets { get; set; }

/// <summary>
/// Api parameter properties for overriding Api OAuth2 scope or/and Service urloverride. Available via extractor-config file only.
/// </summary>
Expand Down
48 changes: 41 additions & 7 deletions src/ArmTemplates/Commands/Executors/ExtractorExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Gateway;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.GatewayApi;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Groups;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Logger;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Master;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.NamedValues;
Expand Down Expand Up @@ -60,6 +61,7 @@ public class ExtractorExecutor
readonly IApiRevisionExtractor apiRevisionExtractor;
readonly IGatewayExtractor gatewayExtractor;
readonly IGatewayApiExtractor gatewayApiExtractor;
readonly IIdentityProviderExtractor identityProviderExtractor;

public ExtractorExecutor(
ILogger<ExtractorExecutor> logger,
Expand All @@ -80,7 +82,8 @@ public ExtractorExecutor(
IGroupExtractor groupExtractor,
IApiRevisionExtractor apiRevisionExtractor,
IGatewayExtractor gatewayExtractor,
IGatewayApiExtractor gatewayApiExtractor)
IGatewayApiExtractor gatewayApiExtractor,
IIdentityProviderExtractor identityProviderExtractor)
{
this.logger = logger;
this.apisClient = apisClient;
Expand All @@ -101,6 +104,7 @@ public ExtractorExecutor(
this.apiRevisionExtractor = apiRevisionExtractor;
this.gatewayExtractor = gatewayExtractor;
this.gatewayApiExtractor = gatewayApiExtractor;
this.identityProviderExtractor = identityProviderExtractor;
}

/// <summary>
Expand All @@ -126,7 +130,8 @@ public static ExtractorExecutor BuildExtractorExecutor(
IGroupExtractor groupExtractor = null,
IApiRevisionExtractor apiRevisionExtractor = null,
IGatewayExtractor gatewayExtractor = null,
IGatewayApiExtractor gatewayApiExtractor = null)
IGatewayApiExtractor gatewayApiExtractor = null,
IIdentityProviderExtractor identityProviderExtractor = null)
=> new ExtractorExecutor(
logger,
apisClient,
Expand All @@ -146,7 +151,8 @@ public static ExtractorExecutor BuildExtractorExecutor(
groupExtractor,
apiRevisionExtractor,
gatewayExtractor,
gatewayApiExtractor);
gatewayApiExtractor,
identityProviderExtractor);

public void SetExtractorParameters(ExtractorParameters extractorParameters)
{
Expand Down Expand Up @@ -401,6 +407,7 @@ public async Task<Template> GenerateParametersTemplateAsync(
LoggerTemplateResources loggerResources,
BackendTemplateResources backendResources,
NamedValuesResources namedValuesResources,
IdentityProviderResources identityProviderResources,
string baseFilesGenerationDirectory)
{
this.logger.LogInformation("Started generation of parameters template...");
Expand All @@ -411,6 +418,7 @@ public async Task<Template> GenerateParametersTemplateAsync(
loggerResources,
backendResources,
namedValuesResources,
identityProviderResources,
this.extractorParameters);

if (!templateParameters.Parameters.IsNullOrEmpty())
Expand Down Expand Up @@ -438,7 +446,8 @@ public async Task<Template<MasterTemplateResources>> GenerateMasterTemplateAsync
AuthorizationServerTemplateResources authorizationServersTemplateResources = null,
NamedValuesResources namedValuesTemplateResources = null,
TagTemplateResources tagTemplateResources = null,
GroupTemplateResources groupTemplateResources = null)
GroupTemplateResources groupTemplateResources = null,
IdentityProviderResources identityProviderTemplateResources = null)
{
if (string.IsNullOrEmpty(this.extractorParameters.LinkedTemplatesBaseUrl))
{
Expand All @@ -452,7 +461,7 @@ public async Task<Template<MasterTemplateResources>> GenerateMasterTemplateAsync
this.extractorParameters, apiTemplateResources, policyTemplateResources, apiVersionSetTemplateResources,
productsTemplateResources, productApisTemplateResources, apiTagsTemplateResources, loggersTemplateResources,
backendsTemplateResources, authorizationServersTemplateResources, namedValuesTemplateResources, tagTemplateResources,
groupTemplateResources);
groupTemplateResources, identityProviderTemplateResources);

if (masterTemplate?.HasResources() == true)
{
Expand Down Expand Up @@ -663,6 +672,29 @@ await FileWriter.SaveAsJsonAsync(
return gatewayTemplate;
}

/// <summary>
/// Generates identity providers template in the desired folder
/// </summary>
/// <param name="baseFilesGenerationDirectory">name of base folder where to save output files</param>
/// <returns>generated identity provider template</returns>
public async Task<Template<IdentityProviderResources>> GenerateIdentityProviderTemplateAsync(string baseFilesGenerationDirectory)
{
this.logger.LogInformation("Started generation of identity provider template...");

var identityProviderTemplate = await this.identityProviderExtractor.GenerateIdentityProvidersTemplateAsync(this.extractorParameters);

if (identityProviderTemplate?.HasResources() == true)
{
await FileWriter.SaveAsJsonAsync(
identityProviderTemplate,
directory: baseFilesGenerationDirectory,
fileName: this.extractorParameters.FileNames.IdentityProviders);
}

this.logger.LogInformation("Finished generation of identity providers template...");
return identityProviderTemplate;
}

/// <summary>
/// Generates gateway-api template in the desired folder
/// </summary>
Expand Down Expand Up @@ -875,8 +907,9 @@ async Task GenerateTemplates(
var namedValueTemplate = await this.GenerateNamedValuesTemplateAsync(singleApiName, apiTemplate.TypedResources.GetAllPolicies(), loggerTemplate.TypedResources.Loggers, baseFilesGenerationDirectory);
var backendTemplate = await this.GenerateBackendTemplateAsync(singleApiName, apiTemplate.TypedResources.GetAllPolicies(), namedValueTemplate.TypedResources.NamedValues, baseFilesGenerationDirectory);
var groupTemplate = await this.GenerateGroupsTemplateAsync(baseFilesGenerationDirectory);
var identityProviderTemplate = await this.GenerateIdentityProviderTemplateAsync(baseFilesGenerationDirectory);
await this.GenerateGatewayTemplateAsync(singleApiName, baseFilesGenerationDirectory);
await this.GenerateParametersTemplateAsync(apisToExtract, loggerTemplate.TypedResources, backendTemplate.TypedResources, namedValueTemplate.TypedResources, baseFilesGenerationDirectory);
await this.GenerateParametersTemplateAsync(apisToExtract, loggerTemplate.TypedResources, backendTemplate.TypedResources, namedValueTemplate.TypedResources, identityProviderTemplate.TypedResources, baseFilesGenerationDirectory);

await this.GenerateMasterTemplateAsync(
baseFilesGenerationDirectory,
Expand All @@ -891,7 +924,8 @@ await this.GenerateMasterTemplateAsync(
authorizationServersTemplateResources: authorizationServerTemplate.TypedResources,
namedValuesTemplateResources: namedValueTemplate.TypedResources,
tagTemplateResources: tagTemplate.TypedResources,
groupTemplateResources: groupTemplate.TypedResources);
groupTemplateResources: groupTemplate.TypedResources,
identityProviderTemplateResources: identityProviderTemplate.TypedResources);
}


Expand Down
31 changes: 25 additions & 6 deletions src/ArmTemplates/Common/API/Clients/Abstractions/ApiClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT License.
// --------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
Expand All @@ -24,37 +25,55 @@ public abstract class ApiClientBase

protected AzureCliAuthenticator Auth { get; private set; } = new AzureCliAuthenticator();

protected Dictionary<string, HttpMethod> httpMethodsMap;

public const string HTTP_GET_METHOD = "get";
public const string HTTP_POST_METHOD = "post";

public ApiClientBase(string baseUrl = null)
{
if (!string.IsNullOrEmpty(baseUrl))
{
this.BaseUrl = baseUrl;
}

this.httpMethodsMap = new Dictionary<string, HttpMethod>();
this.httpMethodsMap.Add(HTTP_GET_METHOD, HttpMethod.Get);
this.httpMethodsMap.Add(HTTP_POST_METHOD, HttpMethod.Post);
}

protected async Task<string> CallApiManagementAsync(string azToken, string requestUrl)
protected async Task<string> CallApiManagementAsync(string azToken, string requestUrl, bool useCache = true, string method = HTTP_GET_METHOD)
DeagleGross marked this conversation as resolved.
Show resolved Hide resolved
{
if (this.cache.TryGetValue(requestUrl, out string cachedResponseBody))
if (useCache && this.cache.TryGetValue(requestUrl, out string cachedResponseBody))
{
return cachedResponseBody;
}

var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
if (!this.httpMethodsMap.ContainsKey(method))
{
throw new Exception($"Method {method} is not defined");
}

var httpMethod = this.httpMethodsMap[method];
var request = new HttpRequestMessage(httpMethod, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", azToken);
request.Headers.UserAgent.TryParseAdd($"{Application.Name}/{Application.BuildVersion}");

HttpResponseMessage response = await this.httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();

this.cache.Set(requestUrl, responseBody);
if (useCache)
{
this.cache.Set(requestUrl, responseBody);
}

return responseBody;
}

protected async Task<TResponse> GetResponseAsync<TResponse>(string azToken, string requestUrl)
protected async Task<TResponse> GetResponseAsync<TResponse>(string azToken, string requestUrl, bool useCache = true, string method = "get")
{
var stringResponse = await this.CallApiManagementAsync(azToken, requestUrl);
var stringResponse = await this.CallApiManagementAsync(azToken, requestUrl, useCache, method);
return stringResponse.Deserialize<TResponse>();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// --------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// --------------------------------------------------------------------------

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;

namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Abstractions
{
public interface IIdentityProviderClient
{
Task<List<IdentityProviderResource>> GetAllAsync(ExtractorParameters extractorParameters);

Task<IdentityProviderSecret> ListIdentityProviderSecrets(string identityProviderName, ExtractorParameters extractorParameters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// --------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// --------------------------------------------------------------------------

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Abstractions;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors.Absctraction;

namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.IdentityProviders
{
public class IdentityProviderClient : ApiClientBase, IIdentityProviderClient
{
const string GetAllIdentityProvidersRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/identityProviders?api-version={4}";
const string ListIdentyProviderSecret = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/identityProviders/{4}/listSecrets?api-version={5}";

readonly IIdentityProviderProcessor identityProviderProcessor;

public IdentityProviderClient(IIdentityProviderProcessor identityProviderProcessor)
{
this.identityProviderProcessor = identityProviderProcessor;
}

public async Task<List<IdentityProviderResource>> GetAllAsync(ExtractorParameters extractorParameters)
{
var (azToken, azSubId) = await this.Auth.GetAccessToken();

string requestUrl = string.Format(GetAllIdentityProvidersRequest,
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, GlobalConstants.ApiVersion);

var identityProviderTemplates = await this.GetPagedResponseAsync<IdentityProviderResource>(azToken, requestUrl);
this.identityProviderProcessor.ProcessData(identityProviderTemplates, extractorParameters);

return identityProviderTemplates;
}

public async Task<IdentityProviderSecret> ListIdentityProviderSecrets(string identityProviderName, ExtractorParameters extractorParameters)
{
var (azToken, azSubId) = await this.Auth.GetAccessToken();

string requestUrl = string.Format(ListIdentyProviderSecret,
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, identityProviderName, GlobalConstants.ApiVersion);

var identityProviderTemplates = await this.GetResponseAsync<IdentityProviderSecret>(azToken, requestUrl, useCache: false, method: HTTP_POST_METHOD);
f-alizada marked this conversation as resolved.
Show resolved Hide resolved
f-alizada marked this conversation as resolved.
Show resolved Hide resolved

return identityProviderTemplates;
}
}
}
2 changes: 2 additions & 0 deletions src/ArmTemplates/Common/Constants/GlobalConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public static class ParameterNames
public const string NamedValueKeyVaultSecrets = "namedValueKeyVaultSecrets";
public const string BackendSettings = "backendSettings";
public const string ApiOauth2ScopeSettings = "apiOauth2ScopeSettings";
public const string SecretValues = "secretValues";
public const string IdentityProvidersSecretValues = "identityProviders";
}

public static class ParameterPrefix
Expand Down
1 change: 1 addition & 0 deletions src/ArmTemplates/Common/Constants/ResourceTypeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ public static class ResourceTypeConstants
public const string Gateway = "Microsoft.ApiManagement/service/gateways";
public const string GatewayApi = "Microsoft.ApiManagement/service/gateways/apis";
public const string ArmDeployments = "Microsoft.Resources/deployments";
public const string IdentityProviders = "Microsoft.ApiManagement/service/identityProviders";
}
}
1 change: 1 addition & 0 deletions src/ArmTemplates/Common/FileHandlers/FileNameGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static FileNames GenerateFileNames(string baseFileName)
ProductAPIs = $@"{baseFileName}productAPIs.template.json",
Gateway = $@"{baseFileName}gateways.template.json",
GatewayApi = $@"{baseFileName}gateways-apis.template.json",
IdentityProviders = $@"{baseFileName}identity-providers.template.json",
TagApi = $@"{baseFileName}apiTags.template.json",
Parameters = $@"{baseFileName}parameters.json",
LinkedMaster = $@"{baseFileName}master.template.json",
Expand Down
2 changes: 2 additions & 0 deletions src/ArmTemplates/Common/FileHandlers/FileNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public class FileNames

public string GatewayApi { get; set; }

public string IdentityProviders { get; set; }

public string Parameters { get; set; }

// linked property outputs 1 master template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ public Template<TTemplateResources> Build<TTemplateResources>()
TemplateBuilder AddParameterizedBackendSettings(ExtractorParameters extractorParameters);

TemplateBuilder AddParameterizedLogResourceIdProperty(ExtractorParameters extractorParameters);

TemplateBuilder AddParametrizedIdentityProvidersSecrets();
}
}
11 changes: 11 additions & 0 deletions src/ArmTemplates/Common/Templates/Builders/TemplateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,16 @@ public TemplateBuilder AddParameterizedApiLoggerIdProperty(ExtractorParameters e

return this;
}

public TemplateBuilder AddParametrizedIdentityProvidersSecrets()
{
var apiLoggerProperty = new TemplateParameterProperties()
f-alizada marked this conversation as resolved.
Show resolved Hide resolved
{
Type = "object"
};
this.template.Parameters.Add(ParameterNames.SecretValues, apiLoggerProperty);

return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// --------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// --------------------------------------------------------------------------

namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders
{
public class IdentityProviderProperties
{
public string[] AllowedTenants { get; set; }
public string Authority { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string PasswordResetPolicyName { get; set; }
public string ProfileEditingPolicyName { get; set; }
public string SigninPolicyName { get; set; }
public string SigninTenant { get; set; }
public string SignupPolicyName { get; set; }
public string Type { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// --------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// --------------------------------------------------------------------------

using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Abstractions;
using Newtonsoft.Json;

namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.IdentityProviders
{
public class IdentityProviderResource : TemplateResource
{
[JsonIgnore]
public string OriginalName { get; set; }

public IdentityProviderProperties Properties { get; set; }
}
}
Loading