From d501184ba231033effcccabb5a2295684356643d Mon Sep 17 00:00:00 2001 From: Danny May Date: Thu, 27 Jun 2024 14:33:59 +0100 Subject: [PATCH 1/2] Add auth headers for all endpoints --- .azuredevops/containerApp.yaml | 10 +- src/ADP.Portal.Api/ADP.Portal.Api.csproj | 1 + .../Controllers/AadGroupController.cs | 5 + .../Controllers/AdoProjectController.cs | 3 + .../Controllers/FluxManifestController.cs | 2 + .../Controllers/FluxTeamConfigController.cs | 10 +- .../Controllers/GithubTeamsController.cs | 2 +- src/ADP.Portal.Api/Program.cs | 22 +- .../appsettings.Development.json | 7 +- test/ADP.Portal.Api.Tests/ProgramTests.cs | 397 ++++++++++-------- 10 files changed, 265 insertions(+), 194 deletions(-) diff --git a/.azuredevops/containerApp.yaml b/.azuredevops/containerApp.yaml index adc65eab..7e11ef5b 100644 --- a/.azuredevops/containerApp.yaml +++ b/.azuredevops/containerApp.yaml @@ -46,7 +46,9 @@ properties: - name: backstage-authority keyVaultUrl: https://{{appKeyVaultName}}.vault.azure.net/secrets/BACKSTAGE-AUTHORITY identity: {{userAssignedIdentity}} - - name: backstage-issuer + - name: api-auth-backend-app-reg-client-id + keyVaultUrl: https://{{appKeyVaultName}}.vault.azure.net/secrets/API-AUTH-BACKEND-APP-REG-CLIENT-ID + identity: {{userAssignedIdentity}} template: containers: - image: {{acrName}}.azurecr.io/image/{{imageRepoName}}:{{appVersion}} @@ -118,6 +120,12 @@ properties: value: false - name: Authentication__backstage__TokenValidationParameters__ValidateAudience value: false + - name: Authentication__pipeline__Instance + value: https://login.microsoftonline.com/ + - name: Authentication__pipeline__TenantId + value: {{tenantid}} + - name: Authentication__pipeline__ClientId + secretRef: api-auth-backend-app-reg-client-id resources: cpu: 0.5 memory: 1Gi diff --git a/src/ADP.Portal.Api/ADP.Portal.Api.csproj b/src/ADP.Portal.Api/ADP.Portal.Api.csproj index fa6b3ae7..0af3ef9c 100644 --- a/src/ADP.Portal.Api/ADP.Portal.Api.csproj +++ b/src/ADP.Portal.Api/ADP.Portal.Api.csproj @@ -20,6 +20,7 @@ + diff --git a/src/ADP.Portal.Api/Controllers/AadGroupController.cs b/src/ADP.Portal.Api/Controllers/AadGroupController.cs index 9b6b04a7..71afc4f7 100644 --- a/src/ADP.Portal.Api/Controllers/AadGroupController.cs +++ b/src/ADP.Portal.Api/Controllers/AadGroupController.cs @@ -3,6 +3,7 @@ using ADP.Portal.Core.Git.Entities; using ADP.Portal.Core.Git.Services; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -32,6 +33,7 @@ public AadGroupController(IGroupsConfigService groupsConfigService, ILoggerRequired: Name of the Team, like ffc-demo /// [HttpGet("{teamName}/groups-config", Name = "GetGroupsConfigForTeam")] + [Authorize(AuthenticationSchemes = "")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetGroupsConfigAsync(string teamName) { @@ -50,6 +52,7 @@ public async Task GetGroupsConfigAsync(string teamName) /// Required: Collection of the users to set up as members in the Admin Group /// [HttpPost("{teamName}/groups-config", Name = "CreateGroupsConfigForTeam")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateGroupsConfigAsync(string teamName, [FromBody] CreateGroupsConfigRequest createGroupsConfigRequest) @@ -82,6 +85,7 @@ public async Task CreateGroupsConfigAsync(string teamName, [FromBo } [HttpPatch("{teamName}/members", Name = "SetMembersForTeam")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SetGroupMembersAsync(string teamName, [FromBody] SetGroupMembersRequest setGroupMembersRequest) @@ -119,6 +123,7 @@ public async Task SetGroupMembersAsync(string teamName, [FromBody] /// Optional: Type of groups to sync i.e. UserGroup/AccessGroup/OpenVpnGroup /// [HttpPut("{teamName}/sync", Name = "SyncGroupsForTeam")] + [Authorize(AuthenticationSchemes = "")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SyncGroupsAsync(string teamName, [FromQuery] string? groupType = null) diff --git a/src/ADP.Portal.Api/Controllers/AdoProjectController.cs b/src/ADP.Portal.Api/Controllers/AdoProjectController.cs index 6204e7e9..9ec470c5 100644 --- a/src/ADP.Portal.Api/Controllers/AdoProjectController.cs +++ b/src/ADP.Portal.Api/Controllers/AdoProjectController.cs @@ -5,6 +5,7 @@ using ADP.Portal.Core.Ado.Services; using Asp.Versioning; using Mapster; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -32,6 +33,7 @@ public AdoProjectController(ILogger logger, IOptionsRequired: Name of the project /// [HttpGet("{projectName}")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAdoProject(string projectName) @@ -53,6 +55,7 @@ public async Task GetAdoProject(string projectName) /// Required: Details about environments, pools, connections & variable groups /// [HttpPatch("{projectName}/onboard")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(OnboardProjectResult))] public async Task OnBoardAsync(string projectName, [FromBody] OnBoardAdoProjectRequest onBoardRequest) { diff --git a/src/ADP.Portal.Api/Controllers/FluxManifestController.cs b/src/ADP.Portal.Api/Controllers/FluxManifestController.cs index 39226d7d..41020ede 100644 --- a/src/ADP.Portal.Api/Controllers/FluxManifestController.cs +++ b/src/ADP.Portal.Api/Controllers/FluxManifestController.cs @@ -1,6 +1,7 @@ using ADP.Portal.Api.Models.Flux; using ADP.Portal.Core.Git.Services; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ADP.Portal.Api.Controllers; @@ -26,6 +27,7 @@ public FluxManifestController(IFluxManifestService fluxManifestService, ILogger< /// /// [HttpGet("templates/service/{templateType}/patch-values")] + [AllowAnonymous] public async Task GetFluxServiceTemplateManifest([FromRoute] string templateType) { if (!Enum.TryParse(templateType, true, out var parsedTemplateType)) diff --git a/src/ADP.Portal.Api/Controllers/FluxTeamConfigController.cs b/src/ADP.Portal.Api/Controllers/FluxTeamConfigController.cs index f47b48e5..951a5b8a 100644 --- a/src/ADP.Portal.Api/Controllers/FluxTeamConfigController.cs +++ b/src/ADP.Portal.Api/Controllers/FluxTeamConfigController.cs @@ -1,11 +1,12 @@ using ADP.Portal.Api.Config; using ADP.Portal.Api.Models.Flux; -using Entities = ADP.Portal.Core.Git.Entities; using ADP.Portal.Core.Git.Services; using Asp.Versioning; using Mapster; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using Entities = ADP.Portal.Core.Git.Entities; namespace ADP.Portal.Api.Controllers; @@ -32,6 +33,7 @@ public FluxTeamConfigController(IFluxTeamConfigService fluxTeamConfigService, IL /// Required: Name of the Team, like ffc-demo /// [HttpGet("{teamName}", Name = "GetFluxConfigForTeam")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetConfigAsync(string teamName) @@ -54,6 +56,7 @@ public async Task GetConfigAsync(string teamName) /// Required: Details about the Services, Environments & ConfigVariables for the team /// [HttpPost("{teamName}", Name = "CreateFluxConfigForTeam")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateConfigAsync(string teamName, [FromBody] TeamConfigRequest fluxConfigRequest) @@ -78,6 +81,7 @@ public async Task CreateConfigAsync(string teamName, [FromBody] Te /// The request object containing all the necessary information to create a new service in the Flux Config. /// [HttpPost("{teamName}/services", Name = "CreateServiceFluxConfigForTeam")] + [Authorize(AuthenticationSchemes = "backstage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateServiceAsync(string teamName, [FromBody] ServiceConfigRequest serviceFluxConfigRequest) @@ -108,6 +112,7 @@ public async Task CreateServiceAsync(string teamName, [FromBody] S /// The request object containing all the necessary information to update the service in the Flux Config. /// [HttpPatch("{teamName}/services/{service}/environments/{environment}/manifest", Name = "SetEnvironmentManifestForTeamService")] + [Authorize(AuthenticationSchemes = "pipeline")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SetEnvironmentManifestAsync(string teamName, string service, string environment, [FromBody] ManifestConfigRequest manifestConfigRequest) @@ -137,6 +142,7 @@ public async Task SetEnvironmentManifestAsync(string teamName, str /// Required: Name of the Environment /// [HttpGet("{teamName}/services/{service}/environments/{environment}", Name = "GetEnvironmentForTeamService")] + [Authorize(AuthenticationSchemes = "pipeline")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetServiceEnvironmentAsync(string teamName, string service, string environment) @@ -160,6 +166,7 @@ public async Task GetServiceEnvironmentAsync(string teamName, stri /// Required: Details about the Environment for the service /// [HttpPost("{teamName}/services/{service}/environments", Name = "AddEnvironmentForTeamService")] + [Authorize(AuthenticationSchemes = "pipeline")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task AddServiceEnvironmentAsync(string teamName, string service, [FromBody] string environmentRequest) @@ -188,6 +195,7 @@ public async Task AddServiceEnvironmentAsync(string teamName, stri /// Optional: Generate manifests only for this service if specified. Default is All services /// [HttpPost("{teamName}/generate", Name = "GenerateFluxConfigForTeam")] + [Authorize(AuthenticationSchemes = "pipeline,backstage")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task GenerateAsync(string teamName, [FromQuery] string? serviceName, [FromQuery] string? environment = null) diff --git a/src/ADP.Portal.Api/Controllers/GithubTeamsController.cs b/src/ADP.Portal.Api/Controllers/GithubTeamsController.cs index 8dc14d25..f8c30f01 100644 --- a/src/ADP.Portal.Api/Controllers/GithubTeamsController.cs +++ b/src/ADP.Portal.Api/Controllers/GithubTeamsController.cs @@ -9,7 +9,6 @@ namespace ADP.Portal.Api.Controllers; [Route("api/github/teams")] [ApiVersion("1.0")] -[Authorize(AuthenticationSchemes = "backstage")] [ApiController] public class GithubTeamsController : ControllerBase { @@ -23,6 +22,7 @@ public GithubTeamsController(IGitHubService github, ILogger SyncTeam([FromRoute] int? teamId, [FromBody] SyncTeamRequest request, CancellationToken cancellationToken = default) diff --git a/src/ADP.Portal.Api/Program.cs b/src/ADP.Portal.Api/Program.cs index 29cdf442..4ba713c4 100644 --- a/src/ADP.Portal.Api/Program.cs +++ b/src/ADP.Portal.Api/Program.cs @@ -15,6 +15,7 @@ using Azure.Identity; using Microsoft.Extensions.Options; using Microsoft.Graph; +using Microsoft.Identity.Web; using Microsoft.IdentityModel.Logging; using Microsoft.OpenApi.Models; using Octokit; @@ -61,13 +62,7 @@ public static void ConfigureApp(WebApplicationBuilder builder) builder.Services.AddLogging(); builder.Services.AddExceptionHandler(); builder.Services.AddProblemDetails(); - builder.Services.AddAuthentication() - .AddJwtBearer("backstage", opt => - { - builder.Configuration - .GetSection("Authentication:backstage") - .Bind(opt); - }); + AddAuthenticationSchemes(builder); builder.Services.Configure(builder.Configuration.GetSection("Ado")); builder.Services.Configure(builder.Configuration.GetSection("AdpAdoProject")); builder.Services.Configure(builder.Configuration.GetSection("AzureAd")); @@ -157,6 +152,19 @@ public static void ConfigureApp(WebApplicationBuilder builder) config.ApiVersionReader = new HeaderApiVersionReader("api-version"); }); } + + private static void AddAuthenticationSchemes(WebApplicationBuilder builder) + { + var auth = builder.Services.AddAuthentication(); + auth.AddJwtBearer( + authenticationScheme: "backstage", + configureOptions: builder.Configuration.GetSection("Authentication:backstage").Bind); + auth.AddMicrosoftIdentityWebApi( + jwtBearerScheme: "pipeline", + configuration: builder.Configuration, + configSectionName: "Authentication:pipeline"); + } + private static GitHubClient GetGitHubClient(GitHubAppAuthConfig gitHubAppAuth) { var gitHubAppName = gitHubAppAuth.AppName.Replace(" ", ""); diff --git a/src/ADP.Portal.Api/appsettings.Development.json b/src/ADP.Portal.Api/appsettings.Development.json index 69ec0cbf..630b5489 100644 --- a/src/ADP.Portal.Api/appsettings.Development.json +++ b/src/ADP.Portal.Api/appsettings.Development.json @@ -56,8 +56,13 @@ "Authority": "http://localhost:7007/api/auth/", "TokenValidationParameters": { "ValidateAudience": false, - "ValidateIssuer": false + "ValidateIssuer": false } + }, + "pipeline": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "00000000-0000-0000-0000-000000000000", + "ClientId": "00000000-0000-0000-0000-000000000000" } } } \ No newline at end of file diff --git a/test/ADP.Portal.Api.Tests/ProgramTests.cs b/test/ADP.Portal.Api.Tests/ProgramTests.cs index 1e1cd2f4..0e9f14ac 100644 --- a/test/ADP.Portal.Api.Tests/ProgramTests.cs +++ b/test/ADP.Portal.Api.Tests/ProgramTests.cs @@ -1,224 +1,255 @@ using ADP.Portal.Api.Wrappers; using ADP.Portal.Core.Ado.Infrastructure; +using FluentAssertions; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Graph; using NUnit.Framework; using Octokit; +using System.Reflection; using YamlDotNet.Serialization; -namespace ADP.Portal.Api.Tests +namespace ADP.Portal.Api.Tests; + +public static class AppBuilder { - public static class AppBuilder + public static WebApplicationBuilder Create() { - public static WebApplicationBuilder Create() - { - IEnumerable> appInsightConfigList = [new KeyValuePair("AppInsights:ConnectionString", "InstrumentationKey=" + Guid.NewGuid().ToString())]; - var appInsightConfig = new ConfigurationBuilder() - .AddInMemoryCollection(appInsightConfigList) - .Build(); - var builder = WebApplication.CreateBuilder(); - builder.Configuration.AddConfiguration(appInsightConfig); - Program.ConfigureApp(builder); - return builder; - } + IEnumerable> appInsightConfigList = [new KeyValuePair("AppInsights:ConnectionString", "InstrumentationKey=" + Guid.NewGuid().ToString())]; + var appInsightConfig = new ConfigurationBuilder() + .AddInMemoryCollection(appInsightConfigList) + .Build(); + var builder = WebApplication.CreateBuilder(); + builder.Configuration.AddConfiguration(appInsightConfig); + Program.ConfigureApp(builder); + return builder; } +} - [TestFixture] - public class ProgramTests - { +[TestFixture] +public class ProgramTests +{ - [Test] - public void TestConfigureApp() - { - // Arrange - var builder = AppBuilder.Create(); + [Test] + public void TestConfigureApp() + { + // Arrange + var builder = AppBuilder.Create(); - // Act - var result = builder.Build(); + // Act + var result = builder.Build(); - // Assert - Assert.That(result, Is.Not.Null); - } + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestAzureCredentialResolution() - { - // Arrange - var builder = AppBuilder.Create(); + [Test] + public void TestAzureCredentialResolution() + { + // Arrange + var builder = AppBuilder.Create(); - // Act - var app = builder.Build(); - var result = app.Services.GetService(); + // Act + var app = builder.Build(); + var result = app.Services.GetService(); - // Assert - Assert.That(result, Is.Not.Null); - } + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestVssConnectionResolution() - { - // Arrange - var builder = AppBuilder.Create(); - KeyValuePair[] adoConfig = - [ - new KeyValuePair("Ado:UsePatToken", "true"), - new KeyValuePair("Ado:PatToken", "TestPatToken") - ]; - - IEnumerable> adoConfigList = adoConfig; - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(adoConfigList) - .Build(); - builder.Configuration.AddConfiguration(configuration); - Program.ConfigureApp(builder); - - - // Act - var app = builder.Build(); - var result = app.Services.GetService>(); - - // Assert - Assert.That(result, Is.Not.Null); - } + [Test] + public void TestVssConnectionResolution() + { + // Arrange + var builder = AppBuilder.Create(); + KeyValuePair[] adoConfig = + [ + new KeyValuePair("Ado:UsePatToken", "true"), + new KeyValuePair("Ado:PatToken", "TestPatToken") + ]; + + IEnumerable> adoConfigList = adoConfig; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(adoConfigList) + .Build(); + builder.Configuration.AddConfiguration(configuration); + Program.ConfigureApp(builder); + + + // Act + var app = builder.Build(); + var result = app.Services.GetService>(); + + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestGraphServiceClientResolution() - { - // Arrange - var builder = AppBuilder.Create(); - KeyValuePair[] aadConfig = - [ - new KeyValuePair("AzureAd:TenantId", Guid.NewGuid().ToString()), - new KeyValuePair("AzureAd:SpClientId", Guid.NewGuid().ToString()), - new KeyValuePair("AzureAd:SpClientSecret", Guid.NewGuid().ToString()) - ]; - - IEnumerable> aadConfigList = aadConfig; - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(aadConfigList) - .Build(); - builder.Configuration.AddConfiguration(configuration); - Program.ConfigureApp(builder); - - - // Act - var app = builder.Build(); - var result = app.Services.GetService(); - - // Assert - Assert.That(result, Is.Not.Null); - } + [Test] + public void TestGraphServiceClientResolution() + { + // Arrange + var builder = AppBuilder.Create(); + KeyValuePair[] aadConfig = + [ + new KeyValuePair("AzureAd:TenantId", Guid.NewGuid().ToString()), + new KeyValuePair("AzureAd:SpClientId", Guid.NewGuid().ToString()), + new KeyValuePair("AzureAd:SpClientSecret", Guid.NewGuid().ToString()) + ]; + + IEnumerable> aadConfigList = aadConfig; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(aadConfigList) + .Build(); + builder.Configuration.AddConfiguration(configuration); + Program.ConfigureApp(builder); + + + // Act + var app = builder.Build(); + var result = app.Services.GetService(); + + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestApiVersioningConfiguration() - { - // Arrange - var builder = AppBuilder.Create(); + [Test] + public void TestApiVersioningConfiguration() + { + // Arrange + var builder = AppBuilder.Create(); - // Act - var app = builder.Build(); - app.MapControllers(); - var result = app.Services.GetService(); + // Act + var app = builder.Build(); + app.MapControllers(); + var result = app.Services.GetService(); - // Assert - Assert.That(result, Is.Not.Null); - } + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestSerializerResolution() + [Test] + public void TestSerializerResolution() + { + // Arrange + var data = new Dictionary { - // Arrange - var data = new Dictionary - { - { "IsValid", true }, - { "Counter", 5 } - }; - var builder = AppBuilder.Create(); - - // Act - var app = builder.Build(); - app.MapControllers(); - var serializer = app.Services.GetService(); - var result = serializer?.Serialize(data); - - // Assert - Assert.That(result, Is.Not.Null); - } + { "IsValid", true }, + { "Counter", 5 } + }; + var builder = AppBuilder.Create(); + + // Act + var app = builder.Build(); + app.MapControllers(); + var serializer = app.Services.GetService(); + var result = serializer?.Serialize(data); + + // Assert + Assert.That(result, Is.Not.Null); + } - [Test] - public void TestDeserializerResolution() - { - // Arrange - var data = new StringReader(@" + [Test] + public void TestDeserializerResolution() + { + // Arrange + var data = new StringReader(@" isValid: 'true' counter: 5 "); - var builder = AppBuilder.Create(); + var builder = AppBuilder.Create(); - // Act - var app = builder.Build(); - app.MapControllers(); - var deserializer = app.Services.GetService(); - var result = deserializer?.Deserialize(data); + // Act + var app = builder.Build(); + app.MapControllers(); + var deserializer = app.Services.GetService(); + var result = deserializer?.Deserialize(data); - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result?.GetType(), Is.EqualTo(typeof(Dictionary))); - } + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result?.GetType(), Is.EqualTo(typeof(Dictionary))); + } - [Test] - public void TestOpenTelemetry() - { - // Arrange - var builder = AppBuilder.Create(); - KeyValuePair[] appEnvConfig = - [ - new KeyValuePair("ASPNETCORE_ENVIRONMENT", "Production"), - new KeyValuePair("UserAssignedIdentityResourceId", Guid.NewGuid().ToString()), - ]; - IEnumerable> appEnvConfigList = appEnvConfig; - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(appEnvConfigList) - .Build(); - builder.Configuration.AddConfiguration(configuration); - Program.ConfigureApp(builder); - - - // Act - var app = builder.Build(); - - // Assert - Assert.That(app, Is.Not.Null); - } + [Test] + public void TestOpenTelemetry() + { + // Arrange + var builder = AppBuilder.Create(); + KeyValuePair[] appEnvConfig = + [ + new KeyValuePair("ASPNETCORE_ENVIRONMENT", "Production"), + new KeyValuePair("UserAssignedIdentityResourceId", Guid.NewGuid().ToString()), + ]; + IEnumerable> appEnvConfigList = appEnvConfig; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(appEnvConfigList) + .Build(); + builder.Configuration.AddConfiguration(configuration); + Program.ConfigureApp(builder); + + + // Act + var app = builder.Build(); + + // Assert + Assert.That(app, Is.Not.Null); + } - [Test] - public void TestGitHub() + [Test] + public void TestGitHub() + { + // Arrange + var builder = AppBuilder.Create(); + KeyValuePair[] appEnvConfig = + [ + new KeyValuePair("GitHubAppAuth:Owner", "defra"), + new KeyValuePair("GitHubAppAuth:AppName", "test"), + new KeyValuePair("GitHubAppAuth:AppId", "12"), + new KeyValuePair("GitHubAppAuth:PrivateKeyBase64", "dGVzdA=="), + ]; + IEnumerable> appEnvConfigList = appEnvConfig; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(appEnvConfigList) + .Build(); + builder.Configuration.AddConfiguration(configuration); + Program.ConfigureApp(builder); + + // Act + var app = builder.Build(); + + // Assert + Assert.Throws(() => app.Services.GetService()); + } + + [Test] + public void AllActionsShouldBeAuthorized() + { + // Arrange + var tController = typeof(ControllerBase); + var actions = typeof(Program).Assembly.GetTypes() + .Where(tController.IsAssignableFrom) + .SelectMany(c => c.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(m => m.DeclaringType == c)) + .Where(m => !m.IsSpecialName); + + // Act & Assert + actions.Should().NotBeEmpty(); + foreach (var action in actions) { - // Arrange - var builder = AppBuilder.Create(); - KeyValuePair[] appEnvConfig = - [ - new KeyValuePair("GitHubAppAuth:Owner", "defra"), - new KeyValuePair("GitHubAppAuth:AppName", "test"), - new KeyValuePair("GitHubAppAuth:AppId", "12"), - new KeyValuePair("GitHubAppAuth:PrivateKeyBase64", "dGVzdA=="), - ]; - IEnumerable> appEnvConfigList = appEnvConfig; - var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(appEnvConfigList) - .Build(); - builder.Configuration.AddConfiguration(configuration); - Program.ConfigureApp(builder); - - // Act - var app = builder.Build(); - - // Assert - Assert.Throws(() => app.Services.GetService()); + action.Should() + .Match(m => HasAuthRelatedAttribute(m)); } + } + private static bool HasAuthRelatedAttribute(MethodInfo method) + { + return (method.GetCustomAttribute() as Attribute + ?? method.GetCustomAttribute() as Attribute + ?? method.DeclaringType?.GetCustomAttribute() as Attribute + ?? method.DeclaringType?.GetCustomAttribute()) + != null; } + } From e6b6a98078ac8aab1fa8256d55f534b92681473b Mon Sep 17 00:00:00 2001 From: Danny May Date: Thu, 27 Jun 2024 14:34:45 +0100 Subject: [PATCH 2/2] Bump version --- src/ADP.Portal.Api/ADP.Portal.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ADP.Portal.Api/ADP.Portal.Api.csproj b/src/ADP.Portal.Api/ADP.Portal.Api.csproj index 0af3ef9c..f12c111e 100644 --- a/src/ADP.Portal.Api/ADP.Portal.Api.csproj +++ b/src/ADP.Portal.Api/ADP.Portal.Api.csproj @@ -2,7 +2,7 @@ net8.0 - 0.2.16 + 0.2.17 enable enable true