├── .github ├── CODEOWNERS └── workflows │ ├── assign-issues-to-projects.yml │ ├── codeql-analysis.yml │ ├── publish-nugets.yml │ ├── build-and-analyze-fork.yml │ └── build-and-analyze.yml ├── test └── Altinn.AccessToken.Tests │ ├── Usings.cs │ ├── ttd-org.pfx │ ├── Mock │ ├── PrincipalUtil.cs │ ├── PublicSigningKeyProviderMock.cs │ └── AccessTokenCreator.cs │ ├── GlobalSuppressions.cs │ ├── AccessTokenValidatorTests.cs │ ├── ttd-org.pem │ ├── Altinn.AccessToken.Tests.csproj │ └── AccessTokenHandlerTests.cs ├── renovate.json ├── README.md ├── src ├── Altinn.Common.AccessToken │ ├── Constants │ │ └── AccessTokenClaimTypes.cs │ ├── IAccessTokenRequirement.cs │ ├── Services │ │ ├── IAccessTokenValidator.cs │ │ ├── IPublicSigningKeyProvider.cs │ │ ├── AccessTokenValidator.cs │ │ └── PublicSigningKeyProvider.cs │ ├── Configuration │ │ ├── KeyVaultSettings.cs │ │ └── AccessTokenSettings.cs │ ├── AccessTokenRequirement.cs │ ├── README.md │ ├── Altinn.Common.AccessToken.csproj │ └── AccessTokenHandler.cs └── Altinn.Common.AccessTokenClient │ ├── Configuration │ ├── AccessTokenClaimTypes.cs │ └── AccessTokenSettings.cs │ ├── Services │ ├── ISigningCredentialsResolver.cs │ ├── IAccessTokenGenerator.cs │ ├── SigningCredentialsResolver.cs │ └── AccessTokenGenerator.cs │ └── Altinn.Common.AccessTokenClient.csproj ├── Directory.Build.targets ├── LICENSE.md ├── Directory.Build.props ├── stylecop.json ├── .gitattributes ├── Altinn.Common.AccessToken.sln ├── CONTRIBUTING.md ├── .gitignore ├── Settings.StyleCop └── .editorconfig /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/CODEOWNERS @altinn/team-core 2 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Moq; 2 | global using Xunit; 3 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/ttd-org.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-accesstoken/main/test/Altinn.AccessToken.Tests/ttd-org.pfx -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Altinn/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Altinn AccessToken 2 | 3 | The projects in this repository is used by Altinn 3 to secure some of the API in the Altinn 3 solution. 4 | There is one library to be used by API clients to generate a token and the other is used by the API application themselves to verify the callers. 5 | 6 | Clients and servers have access to separate KeyVaults where the private and public keys are stored respectively. 7 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Constants/AccessTokenClaimTypes.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Altinn.Common.AccessToken.Constants; 4 | 5 | /// 6 | /// Claimtypes used in access token 7 | /// 8 | public static class AccessTokenClaimTypes 9 | { 10 | /// 11 | /// The application 12 | /// 13 | public const string App = "urn:altinn:app"; 14 | } 15 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/Mock/PrincipalUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace Altinn.AccessToken.Tests.Mock 4 | { 5 | public static class PrincipalUtil 6 | { 7 | public static ClaimsPrincipal CreateClaimsPrincipal() 8 | { 9 | ClaimsIdentity identity = new ClaimsIdentity("mock-org"); 10 | return new ClaimsPrincipal(identity); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Configuration/AccessTokenClaimTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Common.AccessTokenClient.Constants 2 | { 3 | /// 4 | /// Access token claim types. 5 | /// 6 | public static class AccessTokenClaimTypes 7 | { 8 | /// 9 | /// The application claim. 10 | /// 11 | public const string App = "urn:altinn:app"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/assign-issues-to-projects.yml: -------------------------------------------------------------------------------- 1 | name: Auto assign to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to Team Platform project 11 | runs-on: ubuntu-latest 12 | permissions: {} 13 | steps: 14 | - uses: actions/add-to-project@main 15 | with: 16 | project-url: https://github.com/orgs/Altinn/projects/20 17 | github-token: ${{ secrets.ASSIGN_PROJECT_TOKEN }} 18 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Test description should be in the name of the test.", Scope = "module")] 9 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/IAccessTokenRequirement.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using Microsoft.AspNetCore.Authorization; 4 | 5 | namespace Altinn.Common.AccessToken; 6 | 7 | /// 8 | /// This interface describes the implementation of an access token requirement in policy based authorization. 9 | /// 10 | public interface IAccessTokenRequirement : IAuthorizationRequirement 11 | { 12 | /// 13 | /// Gets the list of approved issuers to validate against. 14 | /// 15 | public string[] ApprovedIssuers { get; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Services/IAccessTokenValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Altinn.Common.AccessToken.Services; 4 | 5 | /// 6 | /// Defines the methods required for an implementation of an access token validator. 7 | /// 8 | public interface IAccessTokenValidator 9 | { 10 | /// 11 | /// Validates an access token 12 | /// 13 | /// The token to validate 14 | /// A boolean indicating the validity of the token 15 | Task Validate(string token); 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Services/ISigningCredentialsResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IdentityModel.Tokens; 2 | 3 | namespace Altinn.Common.AccessTokenClient.Services 4 | { 5 | /// 6 | /// Interface to retrive signing credentials for issuer and signing keys for consumer of tokens 7 | /// 8 | public interface ISigningCredentialsResolver 9 | { 10 | /// 11 | /// Returns certificate to be used for signing a JWT 12 | /// 13 | /// The signing credentials 14 | SigningCredentials GetSigningCredentials(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Services/IPublicSigningKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | using Microsoft.IdentityModel.Tokens; 5 | 6 | namespace Altinn.Common.AccessToken.Services; 7 | 8 | /// 9 | /// Interface for a service that can obtain the public key for the given token issuer. 10 | /// 11 | public interface IPublicSigningKeyProvider 12 | { 13 | /// 14 | /// Returns the public key for the given issuer as a 15 | /// 16 | /// The issuer 17 | /// The public key of the issuer 18 | Task> GetSigningKeys(string issuer); 19 | } 20 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | all 5 | runtime; build; native; contentfiles; analyzers 6 | 7 | 8 | 9 | 10 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/AccessTokenValidatorTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Common.AccessToken.Services; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Altinn.AccessToken.Tests; 5 | 6 | public class AccessTokenValidatorTests 7 | { 8 | [Fact] 9 | public async Task Validate_InputIsNotValidatable_ReturnsFalse() 10 | { 11 | // Arrange 12 | Mock signingKeyProviderMock = new Mock(); 13 | Mock> loggerMock = new Mock>(); 14 | 15 | var target = new AccessTokenValidator(signingKeyProviderMock.Object, loggerMock.Object); 16 | 17 | // Act 18 | bool result = await target.Validate("notatoken"); 19 | 20 | // Arrange 21 | Assert.False(result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Configuration/KeyVaultSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Common.AccessToken.Configuration; 2 | 3 | /// 4 | /// The key vault settings used to fetch certificate information from key vault 5 | /// 6 | public class KeyVaultSettings 7 | { 8 | /// 9 | /// The key vault reader client id 10 | /// 11 | public string ClientId { get; set; } = string.Empty; 12 | 13 | /// 14 | /// The key vault client secret 15 | /// 16 | public string ClientSecret { get; set; } = string.Empty; 17 | 18 | /// 19 | /// The key vault tenant Id 20 | /// 21 | public string TenantId { get; set; } = string.Empty; 22 | 23 | /// 24 | /// The uri to the key vault 25 | /// 26 | public string SecretUri { get; set; } = string.Empty; 27 | } 28 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Configuration/AccessTokenSettings.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Altinn.Common.AccessToken.Configuration; 4 | 5 | /// 6 | /// Settings for access token 7 | /// 8 | public class AccessTokenSettings 9 | { 10 | /// 11 | /// Disable access token verification 12 | /// 13 | public bool DisableAccessTokenVerification { get; set; } 14 | 15 | /// 16 | /// The Access token headerId 17 | /// 18 | public string AccessTokenHeaderId { get; set; } = "PlatformAccessToken"; 19 | 20 | /// 21 | /// Cache lifetime for certs 22 | /// 23 | public int CacheCertLifetimeInSeconds { get; set; } = 3600; 24 | 25 | /// 26 | /// ID for cache token in 27 | /// 28 | public string AccessTokenHttpContextId { get; set; } = "accesstokencontextid"; 29 | } 30 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/Mock/PublicSigningKeyProviderMock.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | using Altinn.Common.AccessToken.Services; 4 | 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace Altinn.AccessToken.Tests.Mock 8 | { 9 | public class PublicSigningKeyProviderMock : IPublicSigningKeyProvider 10 | { 11 | public Task> GetSigningKeys(string issuer) 12 | { 13 | List signingKeys = new List(); 14 | 15 | #if NET9_0_OR_GREATER 16 | X509Certificate2 cert = X509CertificateLoader.LoadCertificateFromFile($"{issuer}-org.pem"); 17 | #elif NET8_0 18 | X509Certificate2 cert = new X509Certificate2($"{issuer}-org.pem"); 19 | #else 20 | #error This code block does not match csproj TargetFrameworks list 21 | #endif 22 | SecurityKey key = new X509SecurityKey(cert); 23 | 24 | signingKeys.Add(key); 25 | 26 | return Task.FromResult(signingKeys.AsEnumerable()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/ttd-org.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAeugAwIBAgIJANTdO8o3I8x5MA0GCSqGSIb3DQEBCwUAMA4xDDAKBgNV 3 | BAMTA3R0ZDAeFw0yMDA1MjUxMjIxMzdaFw0zMDA1MjQxMjIxMzdaMA4xDDAKBgNV 4 | BAMTA3R0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcfTsXwwLyC 5 | UkIz06eadWJvG3yrzT+ZB2Oy/WPaZosDnPcnZvCDueN+oy0zTx5TyH5gCi1FvzX2 6 | 7G2eZEKwQaRPv0yuM+McHy1rXxMSOlH/ebP9KJj3FDMUgZl1DCAjJxSAANdTwdrq 7 | ydVg1Crp37AQx8IIEjnBhXsfQh1uPGt1XwgeNyjl00IejxvQOPzd1CofYWwODVtQ 8 | l3PKn1SEgOGcB6wuHNRlnZPCIelQmqxWkcEZiu/NU+kst3NspVUQG2Jf2AF8UWgC 9 | rnrhMQR0Ra1Vi7bWpu6QIKYkN9q0NRHeRSsELOvTh1FgDySYJtNd2xDRSf6IvOiu 10 | tSipl1NZlV0CAwEAAaNkMGIwIAYDVR0OAQH/BBYEFIwq/KbSMzLETdo9NNxj0rz4 11 | qMqVMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQG 12 | CCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAE56UmH5gEYbe 13 | 1kVw7nrfH0R9FyVZGeQQWBn4/6Ifn+eMS9mxqe0Lq74Ue1zEzvRhRRqWYi9JlKNf 14 | 7QQNrc+DzCceIa1U6cMXgXKuXquVHLmRfqvKHbWHJfIkaY8Mlfy++77UmbkvIzly 15 | T1HVhKKp6Xx0r5koa6frBh4Xo/vKBlEyQxWLWF0RPGpGErnYIosJ41M3Po3nw3lY 16 | f7lmH47cdXatcntj2Ho/b2wGi9+W29teVCDfHn2/0oqc7K0EOY9c2ODLjUvQyPZR 17 | OD2yykpyh9x/YeYHFDYdLDJ76/kIdxN43kLU4/hTrh9tMb1PZF+/4DshpAlRoQuL 18 | o8I8avQm/A== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Services/IAccessTokenGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace Altinn.Common.AccessTokenClient.Services 4 | { 5 | /// 6 | /// Access token generator interface 7 | /// 8 | public interface IAccessTokenGenerator 9 | { 10 | /// 11 | /// Generates a access token for apps needing to access platform components. 12 | /// 13 | /// Can be a app or platform component 14 | /// The application creating token (app or component) 15 | /// Accesstoken 16 | string GenerateAccessToken(string issuer, string app); 17 | 18 | /// 19 | /// Generates a access token for anyone needing to access platform components. 20 | /// 21 | /// Can be a app, function or platform component 22 | /// The application creating token (app or component) 23 | /// Certificate to generate SigningCredentials 24 | /// Accesstoken 25 | string GenerateAccessToken(string issuer, string app, X509Certificate2 certificate); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Configuration/AccessTokenSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Common.AccessTokenClient.Configuration 2 | { 3 | /// 4 | /// Confiugration for access token 5 | /// 6 | public class AccessTokenSettings 7 | { 8 | /// 9 | /// The absolute path of the access token signing certificate including file name. Optional. 10 | /// 11 | public string AccessTokenSigningCertificateFullPath { get; set; } = null; 12 | 13 | /// 14 | /// The folder where the signing keys are stored. Cannot be null or only "/". Use empty string for current directory. 15 | /// 16 | public string AccessTokenSigningKeysFolder { get; set; } = "accesstoken/"; 17 | 18 | /// 19 | /// The lifetime for a token 20 | /// 21 | public int TokenLifetimeInSeconds { get; set; } = 300; 22 | 23 | /// 24 | /// Specify the number of seconds to add (or subtract) to the current time when determining 25 | /// when the access token should be considered valid 26 | /// 27 | public int ValidFromAdjustmentSeconds { get; set; } = -5; 28 | 29 | /// 30 | /// The name of the cert for access token signing 31 | /// 32 | public string AccessTokenSigningCertificateFileName { get; set; } = "accesstokencredentials.pfx"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/AccessTokenRequirement.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Altinn.Common.AccessToken; 4 | 5 | /// 6 | /// The requirement used in an authorization policy to verify an access token. 7 | /// 8 | public class AccessTokenRequirement : IAccessTokenRequirement 9 | { 10 | /// 11 | /// Gets the list of approved issuers to validate against. 12 | /// 13 | public string[] ApprovedIssuers { get; } 14 | 15 | /// 16 | /// Initializes a new instance of the class with no specified issuer. 17 | /// 18 | public AccessTokenRequirement() 19 | { 20 | ApprovedIssuers = new string[] { }; 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class with a single issuer. 25 | /// 26 | /// The issuer to validate against. 27 | public AccessTokenRequirement(string issuer) 28 | { 29 | ApprovedIssuers = new string[] { issuer }; 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class with multiple approved issuers. 34 | /// 35 | /// The list of approved issuers to validate against. 36 | public AccessTokenRequirement(string[] approvedIssuers) 37 | { 38 | ApprovedIssuers = approvedIssuers; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017, Altinn 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of Altinn nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) 10 | preview.0 11 | true 12 | 10.0 13 | 14 | 15 | 16 | 17 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 18 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 19 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BuildNumber) 20 | 21 | 22 | 23 | 24 | true 25 | true 26 | true 27 | snupkg 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | true 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/README.md: -------------------------------------------------------------------------------- 1 | ## .NET Library for Access token authorization policy enforcement 2 | 3 | This .NET library is used for setting up Access token requirements for API endpoints and enforcing the policy. 4 | 5 | ### Installation 6 | Install the nuget with `dotnet add package Altinn.Common.AccessToken` or similar. 7 | 8 | 9 | ### Usage 10 | 11 | This library provides classes for registering a policy for 12 | requiring access token. 13 | 14 | 1. Add a new policy to your _AddAuthorization_ method in your Program.cs file. 15 | 16 | ```cs 17 | using Altinn.Common.AccessToken; 18 | (...) 19 | services.AddAuthorization(options => 20 | { 21 | options.AddPolicy( 22 | "POLICY_PLATFORM_ACCESS", 23 | policy => policy.Requirements.Add(new AccessTokenRequirement())); 24 | }); 25 | ``` 26 | 27 | 2. Add required services for AccessTokenHandler in Program.cs 28 | 29 | ```cs 30 | using Altinn.Common.AccessToken; 31 | using Altinn.Common.AccessToken.Services; 32 | (...) 33 | 34 | // required service dependencies 35 | services.AddMemoryCache(); 36 | services.AddSingleton(); 37 | services.AddSingleton(); 38 | 39 | // configuration 40 | services.Configure(config.GetSection("kvSetting")); 41 | 42 | // authorization handler 43 | services.AddSingleton(); 44 | ``` 45 | 46 | 3. To invoke the requirement on a controller endpoint decorate the endpoint with the authorize attribute 47 | 48 | ```cs 49 | [Authorize(Policy = "POLICY_PLATFORM_ACCESS")] 50 | ``` 51 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | // ACTION REQUIRED: This file was automatically added to your project, but it 3 | // will not take effect until additional steps are taken to enable it. See the 4 | // following page for additional information: 5 | // 6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md 7 | 8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 9 | "settings": { 10 | "documentationRules": { 11 | "companyName": "PlaceholderCompany" 12 | }, 13 | "orderingRules": { 14 | "usingDirectivesPlacement": "outsideNamespace", 15 | "systemUsingDirectivesFirst": true, 16 | "blankLinesBetweenUsingGroups": "allow" 17 | }, 18 | "namingRules": { 19 | "allowCommonHungarianPrefixes": true, 20 | "allowedHungarianPrefixes": [ 21 | "as", 22 | "d", 23 | "db", 24 | "dn", 25 | "do", 26 | "dr", 27 | "ds", 28 | "dt", 29 | "e", 30 | "e2", 31 | "er", 32 | "f", 33 | "fs", 34 | "go", 35 | "id", 36 | "if", 37 | "in", 38 | "ip", 39 | "is", 40 | "js", 41 | "li", 42 | "my", 43 | "no", 44 | "ns", 45 | "on", 46 | "or", 47 | "pi", 48 | "pv", 49 | "sa", 50 | "sb", 51 | "se", 52 | "si", 53 | "so", 54 | "sp", 55 | "tc", 56 | "to", 57 | "tr", 58 | "ui", 59 | "un", 60 | "wf", 61 | "ws", 62 | "x", 63 | "y", 64 | "j", 65 | "js" 66 | ] 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 'CodeQL' 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - 'src/**' 7 | - '.github/workflows/**' 8 | pull_request: 9 | branches: [main] 10 | types: [opened, synchronize, reopened] 11 | paths: 12 | - 'src/**' 13 | - '.github/workflows/**' 14 | schedule: 15 | - cron: '18 22 * * 3' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | permissions: 22 | actions: read 23 | contents: read 24 | security-events: write 25 | 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | include: 30 | - language: actions 31 | build-mode: none 32 | - language: csharp 33 | build-mode: autobuild 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 37 | - name: Setup .NET 9.0.* SDK 38 | if: matrix.language == 'csharp' 39 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 40 | with: 41 | dotnet-version: 9.0.x 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 44 | with: 45 | languages: ${{ matrix.language }} 46 | build-mode: ${{ matrix.build-mode }} 47 | # If you wish to specify custom queries, you can do so here or in a config file. 48 | # By default, queries listed here will override any specified in a config file. 49 | # Prefix the list here with "+" to use these queries and those in the config file. 50 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 54 | with: 55 | category: '/language:${{matrix.language}}' 56 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Altinn.Common.AccessToken.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0 5 | latest 6 | Library 7 | true 8 | true 9 | 10 | true 11 | Altinn.Common.AccessToken 12 | Altinn;AccessToken 13 | 14 | Package to verify Access Tokens from client. Require public certificates stored in Azure KeyVault. 15 | 16 | 17 | 18 | Altinn Platform Contributors 19 | git 20 | https://github.com/Altinn/altinn-accesstoken 21 | true 22 | Altinn.Common.AccessToken- 23 | 24 | 25 | {C219A8A8-B936-453C-AC34-01454A0D1792} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | 39 | 40 | stylecop.json 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Altinn.Common.AccessTokenClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | latest 6 | Library 7 | true 8 | true 9 | 10 | Altinn.Common.AccessTokenClient 11 | Altinn;AccessTokenClient 12 | 13 | Package to generate access tokens to use against Altinn Platform. 14 | Can be used by applications and other components that need to access. 15 | 16 | 17 | 18 | Altinn Platform Contributors 19 | git 20 | https://github.com/Altinn/altinn-accesstoken 21 | true 22 | true 23 | Altinn.Common.AccessTokenClient- 24 | 25 | 26 | {454ED0A7-05B0-4D99-A05E-0B526219DC05} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | all 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | 40 | 41 | stylecop.json 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/Altinn.AccessToken.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | stylecop.json 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | PreserveNewest 42 | 43 | 44 | PreserveNewest 45 | 46 | 47 | 48 | 49 | true 50 | $(NoWarn);1591 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/Mock/AccessTokenCreator.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Security.Claims; 3 | using System.Security.Cryptography.X509Certificates; 4 | 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace Altinn.AccessToken.Tests.Mock 8 | { 9 | /// 10 | /// Represents a mechanism for creating JSON Web tokens for use in tests. 11 | /// 12 | public static class AccessTokenCreator 13 | { 14 | /// 15 | /// Generates a token with a self signed certificate included in the test project. 16 | /// 17 | /// A new token 18 | public static string GenerateToken(ClaimsPrincipal principal, int notBeforeSeconds, int expiresSeconds, string issuer) 19 | { 20 | JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); 21 | SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor 22 | { 23 | Subject = new ClaimsIdentity(principal.Identity), 24 | NotBefore = DateTime.UtcNow.AddSeconds(notBeforeSeconds), 25 | Expires = DateTime.UtcNow.AddSeconds(expiresSeconds), 26 | SigningCredentials = GetSigningCredentials(issuer), 27 | Audience = "altinn.no", 28 | Issuer = issuer 29 | }; 30 | 31 | SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); 32 | string tokenstring = tokenHandler.WriteToken(token); 33 | 34 | return tokenstring; 35 | } 36 | 37 | private static SigningCredentials GetSigningCredentials(string issuer) 38 | { 39 | string certPath = $"{issuer}-org.pfx"; 40 | 41 | #if NET9_0_OR_GREATER 42 | X509Certificate2 cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, string.Empty); 43 | #elif NET8_0 44 | X509Certificate2 cert = new X509Certificate2(certPath); 45 | #else 46 | #error This code block does not match csproj TargetFrameworks list 47 | #endif 48 | return new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/publish-nugets.yml: -------------------------------------------------------------------------------- 1 | name: Pack and publish nugets 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | build-pack: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | steps: 15 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Install dotnet6 20 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 21 | with: 22 | dotnet-version: | 23 | 8.0.x 24 | 9.0.x 25 | - name: Install deps 26 | run: | 27 | dotnet restore 28 | - name: Build AccessTokenClient 29 | if: startsWith(github.ref, 'refs/tags/Altinn.Common.AccessTokenClient-') 30 | run: | 31 | cd src/Altinn.Common.AccessTokenClient 32 | dotnet build --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} 33 | - name: Pack and publish AccessTokenClient 34 | if: startsWith(github.ref, 'refs/tags/Altinn.Common.AccessTokenClient-') 35 | run: | 36 | cd src/Altinn.Common.AccessTokenClient 37 | dotnet pack Altinn.Common.AccessTokenClient.csproj --configuration Release --no-restore --no-build -p:BuildNumber=${{ github.run_number }} -p:Deterministic=true 38 | dotnet nuget push bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} 39 | - name: Build AccessToken 40 | if: startsWith(github.ref, 'refs/tags/Altinn.Common.AccessToken-') 41 | run: | 42 | cd src/Altinn.Common.AccessToken 43 | dotnet build --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} 44 | - name: Pack and publish AccessToken 45 | if: startsWith(github.ref, 'refs/tags/Altinn.Common.AccessToken-') 46 | run: | 47 | cd src/Altinn.Common.AccessToken 48 | dotnet pack Altinn.Common.AccessToken.csproj --configuration Release --no-restore --no-build -p:BuildNumber=${{ github.run_number }} -p:Deterministic=true 49 | dotnet nuget push bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} 50 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Services/AccessTokenValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Threading.Tasks; 4 | 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.IdentityModel.Tokens; 7 | 8 | namespace Altinn.Common.AccessToken.Services; 9 | 10 | /// 11 | /// Service for access token validation 12 | /// 13 | public class AccessTokenValidator : IAccessTokenValidator 14 | { 15 | private readonly IPublicSigningKeyProvider _publicSigningKeyProvider; 16 | private readonly ILogger _logger; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The signing keys resolver 22 | /// The logger 23 | public AccessTokenValidator( 24 | IPublicSigningKeyProvider publicSigningKeyProvider, 25 | ILogger logger) 26 | { 27 | _publicSigningKeyProvider = publicSigningKeyProvider; 28 | _logger = logger; 29 | } 30 | 31 | /// 32 | public async Task Validate(string token) 33 | { 34 | JwtSecurityTokenHandler validator = new JwtSecurityTokenHandler(); 35 | 36 | if (!validator.CanReadToken(token)) 37 | { 38 | return false; 39 | } 40 | 41 | JwtSecurityToken jwt = validator.ReadJwtToken(token); 42 | TokenValidationParameters validationParameters = await GetTokenValidationParameters(jwt.Issuer); 43 | 44 | try 45 | { 46 | validator.ValidateToken(token, validationParameters, out _); 47 | return true; 48 | } 49 | catch (Exception ex) 50 | { 51 | _logger.LogWarning(ex, "Failed to validate token from issuer {Issuer}.", jwt.Issuer); 52 | } 53 | 54 | return false; 55 | } 56 | 57 | private async Task GetTokenValidationParameters(string issuer) 58 | { 59 | TokenValidationParameters tokenValidationParameters = new TokenValidationParameters 60 | { 61 | ValidateIssuerSigningKey = true, 62 | ValidateIssuer = false, 63 | ValidateAudience = false, 64 | RequireExpirationTime = true, 65 | ValidateLifetime = true, 66 | ClockSkew = TimeSpan.FromSeconds(60) 67 | }; 68 | 69 | tokenValidationParameters.IssuerSigningKeys = await _publicSigningKeyProvider.GetSigningKeys(issuer); 70 | return tokenValidationParameters; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Altinn.Common.AccessToken.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Common.AccessToken", "src\Altinn.Common.AccessToken\Altinn.Common.AccessToken.csproj", "{C219A8A8-B936-453C-AC34-01454A0D1792}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Common.AccessTokenClient", "src\Altinn.Common.AccessTokenClient\Altinn.Common.AccessTokenClient.csproj", "{454ED0A7-05B0-4D99-A05E-0B526219DC05}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.AccessToken.Tests", "test\Altinn.AccessToken.Tests\Altinn.AccessToken.Tests.csproj", "{03267570-7DE0-492F-BAF3-13DD82BEB249}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F70D999-73FF-48F1-B254-869B4121261D}" 13 | ProjectSection(SolutionItems) = preProject 14 | CONTRIBUTING.md = CONTRIBUTING.md 15 | Directory.Build.props = Directory.Build.props 16 | Directory.Build.targets = Directory.Build.targets 17 | LICENSE.md = LICENSE.md 18 | README.md = README.md 19 | EndProjectSection 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {C219A8A8-B936-453C-AC34-01454A0D1792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {C219A8A8-B936-453C-AC34-01454A0D1792}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {C219A8A8-B936-453C-AC34-01454A0D1792}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {C219A8A8-B936-453C-AC34-01454A0D1792}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {454ED0A7-05B0-4D99-A05E-0B526219DC05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {454ED0A7-05B0-4D99-A05E-0B526219DC05}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {454ED0A7-05B0-4D99-A05E-0B526219DC05}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {454ED0A7-05B0-4D99-A05E-0B526219DC05}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {03267570-7DE0-492F-BAF3-13DD82BEB249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {03267570-7DE0-492F-BAF3-13DD82BEB249}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {03267570-7DE0-492F-BAF3-13DD82BEB249}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {03267570-7DE0-492F-BAF3-13DD82BEB249}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(SolutionProperties) = preSolution 41 | HideSolutionNode = FALSE 42 | EndGlobalSection 43 | GlobalSection(ExtensibilityGlobals) = postSolution 44 | SolutionGuid = {3DC593E5-528B-4D92-B552-EFB0C5B279A2} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Altinn 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible. 4 | However we do recommend that you check our pages on 5 | [how to contribute to our development](https://docs.altinn.studio/community/contributing/) first. 6 | 7 | You can also contribute by: 8 | - Reporting a bug 9 | - Discussing the current state of the code 10 | - Proposing new features 11 | - Telling us what you want us to prioritise - by adding a :+1: to the issue. 12 | 13 | ## We develop with GitHub 14 | 15 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 16 | 17 | ## How to Pull Request 18 | 19 | Once you've finished developing something, Pull requests are the best way to propose changes to the codebase. 20 | We actively welcome your pull requests - even more so if you follow our [development guidelines](https://docs.altinn.studio/community/contributing/). 21 | 22 | 1. Fork the repo and create your branch from `master`. 23 | 2. If you've added code that should be tested, add tests. 24 | 3. If you've changed APIs, update the documentation. 25 | 4. Ensure the test suite passes. 26 | 5. Make sure your code lints. 27 | 6. Issue that pull request! 28 | 7. Be part of the exclusive [external-contribution-❤️](https://github.com/Altinn/altinn-studio/pulls?q=is%3Apr+label%3Aexternal-contribution-%E2%9D%A4%EF%B8%8F+) list. 29 | 30 | ## Any contributions you make will be under the 3-Clause BSD License Software License 31 | 32 | In short, when you submit code changes, your submissions are understood to be under the same liberal [3-Clause BSD License](https://github.com/Altinn/altinn-studio/blob/master/LICENSE.md) that covers the project. Feel free to contact the maintainers if that's a concern. 33 | 34 | ## Report bugs using GitHub issues 35 | 36 | We use GitHub [issues](https://github.com/Altinn/altinn-studio/issues) to track public bugs. 37 | Report a bug by [opening a new issue](https://github.com/Altinn/altinn-studio/issues/new) and use the `bug` template; it's that easy! 38 | 39 | ### Write bug reports with detail, background, and sample code 40 | 41 | **Great Bug Reports** tend to have: 42 | 43 | - A quick summary and/or background 44 | - Steps to reproduce 45 | - Be specific! 46 | - Give sample code if you can (and if relevant). 47 | - Provide screenshots (if relevant). 48 | - What you expected would happen 49 | - What actually happens 50 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 51 | 52 | People *love* thorough bug reports. I'm not even kidding. 53 | 54 | ## License 55 | 56 | By contributing, you agree that your contributions will be licensed under its [3-Clause BSD License](https://github.com/Altinn/altinn-studio/blob/master/LICENSE.md). 57 | -------------------------------------------------------------------------------- /.github/workflows/build-and-analyze-fork.yml: -------------------------------------------------------------------------------- 1 | name: Code test and analysis (fork) 2 | permissions: 3 | contents: read 4 | on: 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | types: [opened, synchronize, reopened, ready_for_review] 10 | jobs: 11 | test: 12 | if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true 13 | name: Build and Test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 18 | with: 19 | dotnet-version: | 20 | 8.0.x 21 | 9.0.x 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | with: 24 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 25 | 26 | - name: dotnet build 27 | run: dotnet build Altinn.Common.AccessToken.sln -v m 28 | 29 | - name: dotnet test 30 | run: dotnet test Altinn.Common.AccessToken.sln --results-directory TestResults/ --collect:"XPlat Code Coverage" -v m 31 | 32 | - name: Generate coverage results 33 | run: | 34 | dotnet tool install --global dotnet-reportgenerator-globaltool 35 | reportgenerator -reports:TestResults/**/coverage.cobertura.xml -targetdir:TestResults/Output/CoverageReport -reporttypes:Cobertura 36 | 37 | - name: Archive code coverage results 38 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 39 | with: 40 | name: code-coverage-report 41 | path: TestResults/Output/CoverageReport/ 42 | 43 | code-coverage: 44 | if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true 45 | name: Report code coverage 46 | runs-on: ubuntu-latest 47 | needs: test 48 | steps: 49 | - name: Download Coverage Results 50 | uses: actions/download-artifact@master 51 | with: 52 | name: code-coverage-report 53 | path: dist/ 54 | - name: Create Coverage Summary Report 55 | uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 56 | with: 57 | filename: dist/Cobertura.xml 58 | badge: true 59 | fail_below_min: true 60 | format: markdown 61 | hide_branch_rate: false 62 | hide_complexity: true 63 | indicators: true 64 | output: both 65 | thresholds: '60 80' 66 | 67 | # Step disabled until workaround available for commenting PR 68 | # - name: Add Coverage PR Comment 69 | # uses: marocchino/sticky-pull-request-comment@v2 70 | # with: 71 | # recreate: true 72 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | # path: code-coverage-results.md 74 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Services/SigningCredentialsResolver.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Security.Cryptography.X509Certificates; 3 | using Altinn.Common.AccessTokenClient.Configuration; 4 | using Microsoft.Extensions.Options; 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace Altinn.Common.AccessTokenClient.Services 8 | { 9 | /// 10 | /// Class to resolve certificate to sign JWT token uses as Access token 11 | /// 12 | public class SigningCredentialsResolver : ISigningCredentialsResolver 13 | { 14 | private readonly AccessTokenSettings _accessTokenSettings; 15 | private static X509SigningCredentials _x509SigningCredentials = null; 16 | private static readonly object _lockObject = new object(); 17 | 18 | /// 19 | /// Default constructor 20 | /// 21 | /// Access token settings 22 | public SigningCredentialsResolver(IOptions accessTokenSettings) 23 | { 24 | _accessTokenSettings = accessTokenSettings.Value; 25 | } 26 | 27 | /// 28 | /// Find the configured 29 | /// 30 | /// 31 | public SigningCredentials GetSigningCredentials() 32 | { 33 | return GetSigningCredentials(_accessTokenSettings); 34 | } 35 | 36 | // Static method to make sonarcloud happy (not update static field from instance method) 37 | private static SigningCredentials GetSigningCredentials(AccessTokenSettings accessTokenSettings) 38 | { 39 | if (_x509SigningCredentials == null) 40 | { 41 | lock (_lockObject) 42 | { 43 | if (_x509SigningCredentials == null) 44 | { 45 | string certPath; 46 | if (!string.IsNullOrEmpty(accessTokenSettings.AccessTokenSigningCertificateFullPath)) 47 | { 48 | certPath = accessTokenSettings.AccessTokenSigningCertificateFullPath; 49 | } 50 | else 51 | { 52 | string basePath = Directory.GetParent(Directory.GetCurrentDirectory()).FullName; 53 | certPath = Path.Combine(basePath, accessTokenSettings.AccessTokenSigningKeysFolder, accessTokenSettings.AccessTokenSigningCertificateFileName); 54 | } 55 | 56 | X509Certificate2 cert = new X509Certificate2(certPath); 57 | _x509SigningCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256); 58 | } 59 | } 60 | } 61 | 62 | return _x509SigningCredentials; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/build-and-analyze.yml: -------------------------------------------------------------------------------- 1 | name: Code test and analysis 2 | permissions: 3 | contents: read 4 | checks: write 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | types: [opened, synchronize, reopened] 11 | jobs: 12 | build-and-test: 13 | name: Build and Test 14 | if: ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'push') && github.repository_owner == 'Altinn' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 20 | with: 21 | dotnet-version: | 22 | 8.0.x 23 | 9.0.x 24 | - name: Build & Test 25 | run: | 26 | dotnet build Altinn.Common.AccessToken.sln -v m 27 | dotnet test Altinn.Common.AccessToken.sln -v m 28 | analyze: 29 | if: ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'push') && github.repository_owner == 'Altinn' 30 | name: Analyze 31 | runs-on: windows-latest 32 | steps: 33 | - name: Setup .NET 34 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1 35 | with: 36 | dotnet-version: | 37 | 8.0.x 38 | 9.0.x 39 | - name: Set up JDK 17 40 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 41 | with: 42 | distribution: 'microsoft' 43 | java-version: 17 44 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 45 | with: 46 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 47 | - name: Install SonarCloud scanner 48 | shell: powershell 49 | run: | 50 | dotnet tool install --global dotnet-sonarscanner 51 | - name: Analyze 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 54 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 55 | shell: powershell 56 | run: | 57 | dotnet-sonarscanner begin /k:"Altinn_altinn-accesstoken" /o:"altinn" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vstest.reportsPaths="**/*.trx" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.coverage.exclusions="src/**/Program.cs" 58 | 59 | dotnet build Altinn.Common.AccessToken.sln 60 | dotnet test Altinn.Common.AccessToken.sln ` 61 | --no-build ` 62 | --results-directory TestResults/ ` 63 | --collect:"XPlat Code Coverage" ` 64 | -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover 65 | 66 | dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" 67 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/Services/PublicSigningKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Threading.Tasks; 6 | 7 | using Altinn.Common.AccessToken.Configuration; 8 | using Azure.Identity; 9 | using Azure.Security.KeyVault.Secrets; 10 | 11 | using Microsoft.Extensions.Caching.Memory; 12 | using Microsoft.Extensions.Options; 13 | using Microsoft.IdentityModel.Tokens; 14 | 15 | namespace Altinn.Common.AccessToken.Services; 16 | 17 | /// 18 | /// An implementation of that will look for the public key of a 19 | /// given issuer in a key Vault. The public key is cached for a configurable time to avoid unnecessary 20 | /// calls to the key vault. 21 | /// 22 | [ExcludeFromCodeCoverage] 23 | public class PublicSigningKeyProvider : IPublicSigningKeyProvider 24 | { 25 | private readonly AccessTokenSettings _accessTokenSettings; 26 | private readonly IMemoryCache _memoryCache; 27 | private readonly SecretClient _secretClient; 28 | 29 | /// 30 | /// Initializes a new instance of the class with the 31 | /// given settings and memory cache object. 32 | /// 33 | /// The keyvault settings 34 | /// Settings for access token 35 | /// Memory cache instance 36 | public PublicSigningKeyProvider( 37 | IOptions keyVaultSettings, 38 | IOptions accessTokenSettings, 39 | IMemoryCache memoryCache) 40 | { 41 | _accessTokenSettings = accessTokenSettings.Value; 42 | _memoryCache = memoryCache; 43 | 44 | if (Environment.GetEnvironmentVariable("AZURE_CLIENT_ID") is null) 45 | { 46 | Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", keyVaultSettings.Value.ClientId); 47 | Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", keyVaultSettings.Value.ClientSecret); 48 | Environment.SetEnvironmentVariable("AZURE_TENANT_ID", keyVaultSettings.Value.TenantId); 49 | } 50 | 51 | _secretClient = new SecretClient(new Uri(keyVaultSettings.Value.SecretUri), new DefaultAzureCredential()); 52 | } 53 | 54 | /// 55 | /// Returns the public key of the given issuer as a 56 | /// 57 | /// The issuer 58 | /// The public key of the issuer 59 | public async Task> GetSigningKeys(string issuer) 60 | { 61 | List signingKeys = new List(); 62 | X509Certificate2 cert = await GetSigningCertFromKeyVault(issuer); 63 | 64 | SecurityKey key = new X509SecurityKey(cert); 65 | signingKeys.Add(key); 66 | 67 | return signingKeys; 68 | } 69 | 70 | /// 71 | /// Get the public key of the given issuer from a key vault and cache it for a configurable time. 72 | /// 73 | /// The token issuer 74 | /// Returns the issuer public key as a x509 sertificate object. 75 | private async Task GetSigningCertFromKeyVault(string issuer) 76 | { 77 | string cacheKey = $"cert-access-token-{issuer}"; 78 | 79 | if (!_memoryCache.TryGetValue(cacheKey, out X509Certificate2 cert)) 80 | { 81 | string secretName = $"{issuer}-access-token-public-cert"; 82 | 83 | KeyVaultSecret keyVaultSecret = await _secretClient.GetSecretAsync(secretName); 84 | 85 | byte[] certBytes = Convert.FromBase64String(keyVaultSecret.Value); 86 | 87 | #if NET9_0_OR_GREATER 88 | cert = X509CertificateLoader.LoadCertificate(certBytes); 89 | #elif NET8_0 90 | cert = new X509Certificate2(certBytes); 91 | #else 92 | #error This code block does not match csproj TargetFrameworks list 93 | #endif 94 | 95 | MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() 96 | .SetPriority(CacheItemPriority.High) 97 | .SetAbsoluteExpiration(new TimeSpan(0, 0, _accessTokenSettings.CacheCertLifetimeInSeconds)); 98 | 99 | _memoryCache.Set(cacheKey, cert, cacheEntryOptions); 100 | } 101 | 102 | return cert; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessTokenClient/Services/AccessTokenGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IdentityModel.Tokens.Jwt; 4 | using System.Runtime.Caching; 5 | using System.Security.Claims; 6 | using System.Security.Cryptography.X509Certificates; 7 | using Altinn.Common.AccessTokenClient.Configuration; 8 | using Altinn.Common.AccessTokenClient.Constants; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Options; 11 | using Microsoft.IdentityModel.Tokens; 12 | 13 | namespace Altinn.Common.AccessTokenClient.Services 14 | { 15 | /// 16 | /// Access token generator creating access tokens for accessing platform components 17 | /// 18 | public class AccessTokenGenerator : IAccessTokenGenerator 19 | { 20 | private readonly AccessTokenSettings _accessTokenSettings; 21 | private readonly ISigningCredentialsResolver _signingKeysResolver; 22 | private readonly ILogger _logger; 23 | private readonly MemoryCache _memoryCache; 24 | 25 | /// 26 | /// Default constructor. 27 | /// 28 | /// The logger 29 | /// Settings for access token 30 | /// The signingkeys resolver 31 | public AccessTokenGenerator(ILogger logger, IOptions accessTokenSettings, ISigningCredentialsResolver signingKeysResolver = null) 32 | { 33 | _memoryCache = MemoryCache.Default; 34 | _accessTokenSettings = accessTokenSettings.Value; 35 | _signingKeysResolver = signingKeysResolver; 36 | _logger = logger; 37 | } 38 | 39 | /// 40 | /// Generates a access token for apps in altinn apps or platform components needing to access other platform components. 41 | /// 42 | /// Can be a app or platform component 43 | /// The application creating token (app or component) 44 | /// 45 | public string GenerateAccessToken(string issuer, string app) 46 | { 47 | try 48 | { 49 | SigningCredentials credentials = _signingKeysResolver.GetSigningCredentials(); 50 | return GenerateAccessToken(issuer, app, credentials); 51 | } 52 | catch (Exception ex) 53 | { 54 | _logger.LogWarning(ex, "Not able to generate access token"); 55 | return null; 56 | } 57 | } 58 | 59 | /// 60 | /// Generates a access token for anyone needing to access other platform components. 61 | /// 62 | /// Can be a app or platform component 63 | /// The application creating token (app or component) 64 | /// Certificate to generate SigningCredentials 65 | /// Accesstoken 66 | public string GenerateAccessToken(string issuer, string app, X509Certificate2 certificate) 67 | { 68 | return GenerateAccessToken(issuer, app, new X509SigningCredentials(certificate, SecurityAlgorithms.RsaSha256)); 69 | } 70 | 71 | private string GenerateAccessToken(string issuer, string app, SigningCredentials signingCredentials) 72 | { 73 | string uniqueCacheKey = $"{issuer}:{app}:{signingCredentials.Kid}"; 74 | 75 | string tokenstring; 76 | if ((tokenstring = _memoryCache[uniqueCacheKey] as string) != null) 77 | { 78 | return tokenstring; 79 | } 80 | 81 | try 82 | { 83 | List claims = new List(); 84 | if (!string.IsNullOrEmpty(app)) 85 | { 86 | claims.Add(new Claim(AccessTokenClaimTypes.App, app, ClaimValueTypes.String, issuer)); 87 | } 88 | 89 | ClaimsIdentity identity = new ClaimsIdentity("AccessToken"); 90 | identity.AddClaims(claims); 91 | ClaimsPrincipal principal = new ClaimsPrincipal(identity); 92 | 93 | JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); 94 | SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor 95 | { 96 | Subject = new ClaimsIdentity(principal.Identity), 97 | NotBefore = DateTime.UtcNow.AddSeconds(_accessTokenSettings.ValidFromAdjustmentSeconds), 98 | Expires = DateTime.UtcNow.AddSeconds(_accessTokenSettings.TokenLifetimeInSeconds), 99 | SigningCredentials = signingCredentials, 100 | Audience = "platform.altinn.no", 101 | Issuer = issuer 102 | }; 103 | 104 | SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); 105 | tokenstring = tokenHandler.WriteToken(token); 106 | 107 | _memoryCache.Set(new CacheItem(uniqueCacheKey, tokenstring), new CacheItemPolicy() 108 | { 109 | Priority = CacheItemPriority.NotRemovable, 110 | AbsoluteExpiration = new(DateTime.Now.AddSeconds(_accessTokenSettings.TokenLifetimeInSeconds - 5)) 111 | }); 112 | 113 | return tokenstring; 114 | } 115 | catch (Exception ex) 116 | { 117 | _logger.LogWarning(ex, "Not able to generate access token"); 118 | } 119 | 120 | return null; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Altinn.Common.AccessToken/AccessTokenHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | 6 | using Altinn.Common.AccessToken.Configuration; 7 | using Altinn.Common.AccessToken.Constants; 8 | using Altinn.Common.AccessToken.Services; 9 | 10 | using Microsoft.AspNetCore.Authorization; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.Options; 14 | using Microsoft.Extensions.Primitives; 15 | using Microsoft.IdentityModel.Tokens; 16 | 17 | namespace Altinn.Common.AccessToken; 18 | 19 | /// 20 | /// Authorization handler to verify that request contains access token 21 | /// 22 | public class AccessTokenHandler : AuthorizationHandler 23 | { 24 | private readonly IHttpContextAccessor _httpContextAccessor; 25 | private readonly ILogger _logger; 26 | private readonly AccessTokenSettings _accessTokenSettings; 27 | private readonly IPublicSigningKeyProvider _publicSigningKeyProvider; 28 | 29 | /// 30 | /// Initializes a new instance of the class with the given parameters. 31 | /// 32 | /// A service that provides access to the current HttpContext. 33 | /// A logger 34 | /// The access token settings 35 | /// The resolver for signing keys 36 | public AccessTokenHandler( 37 | IHttpContextAccessor httpContextAccessor, 38 | ILogger logger, 39 | IOptions accessTokenSettings, 40 | IPublicSigningKeyProvider publicSigningKeyProvider) 41 | { 42 | _httpContextAccessor = httpContextAccessor; 43 | _logger = logger; 44 | _accessTokenSettings = accessTokenSettings.Value; 45 | _publicSigningKeyProvider = publicSigningKeyProvider; 46 | } 47 | 48 | /// 49 | /// Handles verification of AccessTokens. Enabled with Policy on API controllers 50 | /// 51 | /// The current authorization handler context. 52 | /// The requirement for the given operation. 53 | /// A representing the asynchronous operation. 54 | protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IAccessTokenRequirement requirement) 55 | { 56 | StringValues tokens = GetAccessTokens(); 57 | 58 | if (tokens.Count == 0 && _accessTokenSettings.DisableAccessTokenVerification) 59 | { 60 | _logger.LogInformation("Token is missing and function is turned off"); 61 | context.Succeed(requirement); 62 | return; 63 | } 64 | 65 | if (tokens.Count == 0) 66 | { 67 | _logger.LogInformation("There is no access token"); 68 | return; 69 | } 70 | 71 | if (tokens.Count > 1) 72 | { 73 | _logger.LogWarning("There should be one accesss token"); 74 | return; 75 | } 76 | 77 | try 78 | { 79 | bool isValid = await ValidateAccessToken(tokens[0], requirement.ApprovedIssuers); 80 | 81 | if (isValid) 82 | { 83 | context.Succeed(requirement); 84 | } 85 | } 86 | catch (Exception ex) 87 | { 88 | _logger.LogWarning(ex, "Validation of Access Token Failed"); 89 | 90 | if (_accessTokenSettings.DisableAccessTokenVerification) 91 | { 92 | context.Succeed(requirement); 93 | } 94 | } 95 | } 96 | 97 | /// 98 | /// This validates the access token available in 99 | /// 100 | /// The access token 101 | /// The list of approved issuers 102 | /// 103 | private async Task ValidateAccessToken(string token, string[] approvedIssuers) 104 | { 105 | JwtSecurityTokenHandler validator = new JwtSecurityTokenHandler(); 106 | 107 | if (!validator.CanReadToken(token)) 108 | { 109 | return false; 110 | } 111 | 112 | // Read JWT token to extract Issuer 113 | JwtSecurityToken jwt = validator.ReadJwtToken(token); 114 | 115 | // When no exact match on token issuer against approved issuers 116 | if (approvedIssuers.Length > 0 && Array.IndexOf(approvedIssuers, jwt.Issuer) < 0) 117 | { 118 | return false; 119 | } 120 | 121 | TokenValidationParameters validationParameters = await GetTokenValidationParameters(jwt.Issuer); 122 | 123 | SecurityToken validatedToken; 124 | try 125 | { 126 | ClaimsPrincipal prinicpal = validator.ValidateToken(token, validationParameters, out validatedToken); 127 | SetAccessTokenCredential(validatedToken.Issuer, prinicpal); 128 | return true; 129 | } 130 | catch (Exception ex) 131 | { 132 | _logger.LogWarning(ex, "Failed to validate token from issuer {Issuer}.", jwt.Issuer); 133 | } 134 | 135 | return false; 136 | } 137 | 138 | private async Task GetTokenValidationParameters(string issuer) 139 | { 140 | TokenValidationParameters tokenValidationParameters = new TokenValidationParameters 141 | { 142 | ValidateIssuerSigningKey = true, 143 | ValidateIssuer = false, 144 | ValidateAudience = false, 145 | RequireExpirationTime = true, 146 | ValidateLifetime = true, 147 | ClockSkew = new TimeSpan(0, 0, 10) 148 | }; 149 | 150 | tokenValidationParameters.IssuerSigningKeys = await _publicSigningKeyProvider.GetSigningKeys(issuer); 151 | return tokenValidationParameters; 152 | } 153 | 154 | private StringValues GetAccessTokens() 155 | { 156 | if (_httpContextAccessor.HttpContext.Request.Headers.ContainsKey(_accessTokenSettings.AccessTokenHeaderId)) 157 | { 158 | return _httpContextAccessor.HttpContext.Request.Headers[_accessTokenSettings.AccessTokenHeaderId]; 159 | } 160 | 161 | return StringValues.Empty; 162 | } 163 | 164 | private void SetAccessTokenCredential(string issuer, ClaimsPrincipal claimsPrincipal) 165 | { 166 | string appClaim = string.Empty; 167 | foreach (Claim claim in claimsPrincipal.Claims) 168 | { 169 | if (claim.Type.Equals(AccessTokenClaimTypes.App)) 170 | { 171 | appClaim = claim.Value; 172 | break; 173 | } 174 | } 175 | 176 | _httpContextAccessor.HttpContext.Items.Add(_accessTokenSettings.AccessTokenHttpContextId, issuer + "/" + appClaim); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | ### VisualStudio ### 4 | 5 | # Tool Runtime Dir 6 | [Tt]ools/ 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # Entity Framework 15 | Migrations/ 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | build/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | msbuild.log 29 | 30 | # Cross building rootfs 31 | cross/rootfs/ 32 | 33 | # Visual Studio 2015 34 | .vs/ 35 | 36 | # Visual Studio 2015 Pre-CTP6 37 | *.sln.ide 38 | *.ide/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | #NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | #JUNIT 49 | junit.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | *_i.c 57 | *_p.c 58 | *_i.h 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.svclog 79 | *.scc 80 | 81 | # Chutzpah Test files 82 | _Chutzpah* 83 | 84 | # Visual C++ cache files 85 | ipch/ 86 | *.aps 87 | *.ncb 88 | *.opendb 89 | *.opensdf 90 | *.sdf 91 | *.cachefile 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | 98 | # TFS 2012 Local Workspace 99 | $tf/ 100 | 101 | # Guidance Automation Toolkit 102 | *.gpState 103 | 104 | # ReSharper is a .NET coding add-in 105 | _ReSharper*/ 106 | *.[Rr]e[Ss]harper 107 | *.DotSettings.user 108 | 109 | # JustCode is a .NET coding addin-in 110 | .JustCode 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # NCrunch 119 | _NCrunch_* 120 | .*crunch*.local.xml 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | *.pubxml 149 | *.publishproj 150 | 151 | # NuGet Packages 152 | *.nuget.props 153 | *.nuget.targets 154 | *.nupkg 155 | **/packages/* 156 | 157 | # Allow frontend packages 158 | !**/frontend/packages/* 159 | 160 | # NuGet package restore lockfiles 161 | project.lock.json 162 | 163 | # yarn lock file 164 | /yarn.lock 165 | package-lock.json 166 | 167 | # Windows Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Windows Store app package directory 172 | AppPackages/ 173 | 174 | # Others 175 | *.Cache 176 | ClientBin/ 177 | ~$* 178 | *.dbmdl 179 | *.dbproj.schemaview 180 | *.publishsettings 181 | node_modules/ 182 | *.metaproj 183 | *.metaproj.tmp 184 | 185 | # RIA/Silverlight projects 186 | Generated_Code/ 187 | 188 | # Backup & report files from converting an old project file 189 | # to a newer Visual Studio version. Backup files are not needed, 190 | # because we have git ;-) 191 | _UpgradeReport_Files/ 192 | Backup*/ 193 | UpgradeLog*.XML 194 | UpgradeLog*.htm 195 | 196 | # SQL Server files 197 | *.mdf 198 | *.ldf 199 | 200 | # Business Intelligence projects 201 | *.rdl.data 202 | *.bim.layout 203 | *.bim_*.settings 204 | 205 | # Microsoft Fakes 206 | FakesAssemblies/ 207 | 208 | ### MonoDevelop ### 209 | 210 | *.pidb 211 | *.userprefs 212 | 213 | ### Windows ### 214 | 215 | # Windows image file caches 216 | Thumbs.db 217 | ehthumbs.db 218 | 219 | # Folder config file 220 | Desktop.ini 221 | 222 | # Recycle Bin used on file shares 223 | $RECYCLE.BIN/ 224 | 225 | # Windows Installer files 226 | *.cab 227 | *.msi 228 | *.msm 229 | *.msp 230 | 231 | # Windows shortcuts 232 | *.lnk 233 | 234 | ### Linux ### 235 | 236 | *~ 237 | 238 | # KDE directory preferences 239 | .directory 240 | 241 | ### OSX ### 242 | 243 | .DS_Store 244 | .AppleDouble 245 | .LSOverride 246 | 247 | # Icon must end with two \r 248 | Icon 249 | 250 | # Thumbnails 251 | ._* 252 | 253 | # Files that might appear on external disk 254 | .Spotlight-V100 255 | .Trashes 256 | 257 | # Directories potentially created on remote AFP share 258 | .AppleDB 259 | .AppleDesktop 260 | Network Trash Folder 261 | Temporary Items 262 | .apdisk 263 | 264 | # vim temporary files 265 | [._]*.s[a-w][a-z] 266 | [._]s[a-w][a-z] 267 | *.un~ 268 | Session.vim 269 | .netrwhist 270 | *~ 271 | /AltinnPoCExperiments 272 | 273 | # Sonarqube 274 | .scannerwork 275 | 276 | # TSLint report 277 | tslint_report.json 278 | 279 | 280 | 281 | ## -- Repositories -- 282 | 283 | # Binaries for programs and plugins 284 | *.exe 285 | *.dll 286 | *.so 287 | *.dylib 288 | 289 | # Test binary, build with `go test -c` 290 | *.test 291 | 292 | # Output of the go coverage tool, specifically when used with LiteIDE 293 | *.out 294 | 295 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 296 | .glide/ 297 | postgres/* 298 | !src/AltinnRepositories/gitea-data/ 299 | src/AltinnRepositories/gitea-data/* 300 | !src/AltinnRepositories/gitea-data/gitea/ 301 | src/AltinnRepositories/gitea-data/gitea/* 302 | !src/AltinnRepositories/gitea-data/gitea/conf/ 303 | !src/AltinnRepositories/gitea-data/gitea/options/ 304 | !src/AltinnRepositories/gitea-data/gitea/public/ 305 | !src/AltinnRepositories/gitea-data/gitea/templates/ 306 | 307 | ######### Built react apps for receipt ########### 308 | src/Altinn.Platform/Altinn.Platform.Receipt/Receipt/wwwroot/receipt/js/react 309 | src/Altinn.Platform/Altinn.Platform.Receipt/Receipt/wwwroot/receipt/css 310 | 311 | ######### Built react apps for designer ########### 312 | src/studio/src/designer/backend/wwwroot/designer/css/react 313 | src/studio/src/designer/backend/wwwroot/designer/js/react 314 | src/studio/src/designer/backend/wwwroot/designer/js/lib 315 | src/studio/src/designer/backend/wwwroot/designer/css/lib 316 | src/studio/src/designer/backend/wwwroot/designer/css/font-awesome 317 | src/studio/src/designer/backend/wwwroot/designer/css/fonts 318 | src/studio/src/designer/backend/wwwroot/designer/css/bootstrap*.css 319 | src/studio/src/designer/backend/wwwroot/designer/frontend 320 | 321 | ######### Testdata created by testrun ######### 322 | src/Altinn.Platform/Altinn.Platform.Authorization/IntegrationTests/Data/blobs/output/ 323 | 324 | # Jest test coverage 325 | coverage 326 | /deploy/kubernetes/helm-charts/altinn-dataservice-0.1.0.tgz 327 | /deploy/kubernetes/altinn-dbsettings-secret.json 328 | 329 | /src/Altinn.Platform/Altinn.Platform.Storage/Storage/values.dev.yaml 330 | /src/Altinn.Platform/Altinn.Platform.Storage/Storage.Interface/Storage.Interface.xml 331 | /src/Altinn.Platform/Altinn.Platform.Profile/Profile/charts/profile 332 | /src/Altinn.Platform/Altinn.Platform.Profile/Profile/azds.yaml 333 | /src/AltinnCore/UnitTest/coverage.opencover.xml 334 | /src/Altinn.Platform/Altinn.Platform.Authorization/Altinn.Authorization.ABAC/Altinn.Authorization.ABAC.xml 335 | 336 | ## Java 337 | *.class 338 | .mtj.tmp/ 339 | *.jar 340 | *.war 341 | *.ear 342 | hs_err_pid* 343 | 344 | ## Maven 345 | target/ 346 | pom.xml.tag 347 | pom.xml.releaseBackup 348 | pom.xml.versionsBackup 349 | pom.xml.next 350 | release.properties 351 | 352 | ## Eclipse 353 | .metadata 354 | .classpath 355 | .project 356 | .settings/ 357 | bin/ 358 | tmp/ 359 | *.tmp 360 | *.bak 361 | *.swp 362 | *~.nib 363 | local.properties 364 | .loadpath 365 | 366 | ## NetBeans 367 | nbproject/private/ 368 | build/ 369 | nbbuild/ 370 | dist/ 371 | nbdist/ 372 | nbactions.xml 373 | nb-configuration.xml 374 | 375 | ## IntelliJ 376 | .idea 377 | src/Altinn.Apps/AppTemplates/AspNet/App/Properties/launchSettings.json 378 | *.iml 379 | 380 | ## Test repositories 381 | src/studio/src/designer/backend.Tests/_TestData/Repositories/*/*/test-repo_*/** 382 | src/studio/src/designer/backend.Tests/_TestData/Remote/*/test-repo_*/** 383 | src/studio/.env 384 | src/studio/.devcontainer/** 385 | -------------------------------------------------------------------------------- /Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | preprocessor, pre-processor 5 | shortlived, short-lived 6 | 7 | 8 | altinn 9 | arbeidsgiveravgift 10 | aspx 11 | BankID 12 | brreg 13 | Buypass 14 | Commfides 15 | compat 16 | Compat.browser 17 | Creuna 18 | css 19 | dequeue 20 | Dequeue 21 | deserializing 22 | Determinator 23 | enum 24 | en-US 25 | formset 26 | Functoid 27 | ID-Porten 28 | js 29 | leveranse 30 | linq 31 | msdn 32 | oppgave 33 | orid 34 | participant 35 | Porten 36 | psa 37 | referer 38 | reportee 39 | sone 40 | ssn 41 | subform 42 | subforms 43 | virksomhet 44 | Winnovative 45 | xfd 46 | xsd 47 | Guid 48 | Api 49 | OAuth 50 | Auth 51 | mpcId 52 | mpc 53 | Sdp 54 | Difi 55 | Difis 56 | Rijndael 57 | eq 58 | orderby 59 | Oppgaveregister 60 | Seres 61 | reportees 62 | 63 | 10000 64 | 65 | 66 | 67 | 68 | 69 | 70 | False 71 | 72 | 73 | 74 | 75 | False 76 | 77 | 78 | 79 | 80 | False 81 | 82 | 83 | 84 | 85 | False 86 | 87 | 88 | 89 | 90 | False 91 | 92 | 93 | 94 | 95 | False 96 | 97 | 98 | 99 | 100 | False 101 | 102 | 103 | 104 | 105 | True 106 | 107 | 108 | 109 | 110 | 111 | 112 | False 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | False 123 | 124 | 125 | 126 | 127 | False 128 | 129 | 130 | 131 | 132 | 133 | a1 134 | as 135 | at 136 | d 137 | db 138 | dn 139 | do 140 | dr 141 | ds 142 | dt 143 | e 144 | e2 145 | er 146 | f 147 | fs 148 | go 149 | id 150 | if 151 | in 152 | ip 153 | is 154 | js 155 | li 156 | my 157 | no 158 | ns 159 | on 160 | or 161 | pi 162 | pv 163 | sa 164 | sb 165 | se 166 | si 167 | so 168 | sp 169 | tc 170 | to 171 | tr 172 | ui 173 | un 174 | wf 175 | ws 176 | x 177 | y 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | False 186 | 187 | 188 | 189 | 190 | False 191 | 192 | 193 | 194 | 195 | False 196 | 197 | 198 | 199 | 200 | False 201 | 202 | 203 | 204 | 205 | False 206 | 207 | 208 | 209 | 210 | False 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | False 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | False 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /test/Altinn.AccessToken.Tests/AccessTokenHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | using Altinn.AccessToken.Tests.Mock; 4 | 5 | using Altinn.Common.AccessToken; 6 | using Altinn.Common.AccessToken.Configuration; 7 | using Altinn.Common.AccessToken.Services; 8 | 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | 14 | namespace Altinn.AccessToken.Tests 15 | { 16 | public class AccessTokenHandlerTests 17 | { 18 | private readonly Mock _httpContextAccessor = new(); 19 | private readonly Mock> _logger = new(); 20 | private readonly Mock> _options = new(); 21 | private readonly PublicSigningKeyProviderMock _signingKeysResolver = new(); 22 | 23 | private readonly List _reqs = new List 24 | { 25 | new AccessTokenRequirement() 26 | }; 27 | 28 | [Fact] 29 | public async Task HandleAsyncTest_TokenMissingAndVerificationDisabled_ResultSuccessful() 30 | { 31 | // Arrange 32 | AccessTokenSettings accessTokenSettings = new() { DisableAccessTokenVerification = true }; 33 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 34 | 35 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(new DefaultHttpContext()); 36 | 37 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 38 | 39 | var target = new AccessTokenHandler( 40 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 41 | 42 | // Act 43 | await target.HandleAsync(context); 44 | 45 | // Assert 46 | Assert.True(context.HasSucceeded); 47 | } 48 | 49 | [Fact] 50 | public async Task HandleAsyncTest_TokenMissingAndVerificationEnabled_ResultNotSuccessful() 51 | { 52 | // Arrange 53 | AccessTokenSettings accessTokenSettings = new(); 54 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 55 | 56 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(new DefaultHttpContext()); 57 | 58 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 59 | 60 | var target = new AccessTokenHandler( 61 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 62 | 63 | // Act 64 | await target.HandleAsync(context); 65 | 66 | // Assert 67 | Assert.False(context.HasSucceeded); 68 | } 69 | 70 | [Fact] 71 | public async Task HandleAsyncTest_TokenNotValidAndVerificationEnabled_ResultNotSuccessful() 72 | { 73 | // Arrange 74 | AccessTokenSettings accessTokenSettings = new(); 75 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 76 | 77 | DefaultHttpContext httpContext = new DefaultHttpContext(); 78 | httpContext.Request.Headers["PlatformAccessToken"] = "notatoken"; 79 | 80 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 81 | 82 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 83 | 84 | var target = new AccessTokenHandler( 85 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 86 | 87 | // Act 88 | await target.HandleAsync(context); 89 | 90 | // Assert 91 | Assert.False(context.HasSucceeded); 92 | } 93 | 94 | [Fact] 95 | public async Task HandleAsyncTest_TokenExpiredAndVerificationEnabled_ResultNotSuccessful() 96 | { 97 | // Arrange 98 | AccessTokenSettings accessTokenSettings = new(); 99 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 100 | 101 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 102 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, -11, "ttd"); 103 | 104 | DefaultHttpContext httpContext = new DefaultHttpContext(); 105 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 106 | 107 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 108 | 109 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 110 | 111 | var target = new AccessTokenHandler( 112 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 113 | 114 | // Act 115 | await target.HandleAsync(context); 116 | 117 | // Assert 118 | Assert.False(context.HasSucceeded); 119 | } 120 | 121 | [Fact] 122 | public async Task HandleAsyncTest_TokenValidAndVerificationEnabled_ResultSuccessful() 123 | { 124 | // Arrange 125 | AccessTokenSettings accessTokenSettings = new(); 126 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 127 | 128 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 129 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, 5, "ttd"); 130 | 131 | DefaultHttpContext httpContext = new DefaultHttpContext(); 132 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 133 | 134 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 135 | 136 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 137 | 138 | var target = new AccessTokenHandler( 139 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 140 | 141 | // Act 142 | await target.HandleAsync(context); 143 | 144 | // Assert 145 | Assert.True(context.HasSucceeded); 146 | Assert.True(httpContext.Items.ContainsKey("accesstokencontextid")); 147 | } 148 | 149 | [Fact] 150 | public async Task HandleAsyncTest_TokenNotYetValidAndVerificationEnabled_ResultNotSuccessful() 151 | { 152 | // Arrange 153 | AccessTokenSettings accessTokenSettings = new(); 154 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 155 | 156 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 157 | string accessToken = AccessTokenCreator.GenerateToken(principal, 15, 20, "ttd"); 158 | 159 | DefaultHttpContext httpContext = new DefaultHttpContext(); 160 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 161 | 162 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 163 | 164 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 165 | 166 | var target = new AccessTokenHandler( 167 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 168 | 169 | // Act 170 | await target.HandleAsync(context); 171 | 172 | // Assert 173 | Assert.False(context.HasSucceeded); 174 | } 175 | 176 | [Fact] 177 | public async Task HandleAsyncTest_ErrorObtainingKeysAndVerificationEnabled_ResultNotSuccessful() 178 | { 179 | // Arrange 180 | AccessTokenSettings accessTokenSettings = new(); 181 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 182 | 183 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 184 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, 5, "ttd"); 185 | 186 | DefaultHttpContext httpContext = new DefaultHttpContext(); 187 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 188 | 189 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 190 | 191 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 192 | 193 | var publicKeyProvider = new Mock(); 194 | publicKeyProvider.Setup(s => s.GetSigningKeys(It.IsAny())).ThrowsAsync(new Exception("omg!")); 195 | 196 | var target = new AccessTokenHandler( 197 | _httpContextAccessor.Object, _logger.Object, _options.Object, publicKeyProvider.Object); 198 | 199 | // Act 200 | await target.HandleAsync(context); 201 | 202 | // Assert 203 | Assert.False(context.HasSucceeded); 204 | } 205 | 206 | [Fact] 207 | public async Task HandleAsyncTest_ErrorObtainingKeysAndVerificationDisabled_ResultSuccessful() 208 | { 209 | // Arrange 210 | AccessTokenSettings accessTokenSettings = new() { DisableAccessTokenVerification = true }; 211 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 212 | 213 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 214 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, 5, "ttd"); 215 | 216 | DefaultHttpContext httpContext = new DefaultHttpContext(); 217 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 218 | 219 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 220 | 221 | var context = new AuthorizationHandlerContext(_reqs, PrincipalUtil.CreateClaimsPrincipal(), null); 222 | 223 | var publicKeyProvider = new Mock(); 224 | publicKeyProvider.Setup(s => s.GetSigningKeys(It.IsAny())).ThrowsAsync(new Exception("omg!")); 225 | 226 | var target = new AccessTokenHandler( 227 | _httpContextAccessor.Object, _logger.Object, _options.Object, publicKeyProvider.Object); 228 | 229 | // Act 230 | await target.HandleAsync(context); 231 | 232 | // Assert 233 | Assert.True(context.HasSucceeded); 234 | } 235 | 236 | [Theory] 237 | [InlineData("ttd", "ttd", true)] 238 | [InlineData("ttd", "ttd1", false)] 239 | public async Task HandleAsyncTest_WithSingleApprovedTokenIssuer(string tokenIssuer, string specifiedTokenIssuer, bool result) 240 | { 241 | // Arrange 242 | AccessTokenSettings accessTokenSettings = new(); 243 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 244 | 245 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 246 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, 5, tokenIssuer); 247 | 248 | DefaultHttpContext httpContext = new DefaultHttpContext(); 249 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 250 | 251 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 252 | 253 | List reqsWithSingleSpecifiedIssuer = new List 254 | { 255 | new AccessTokenRequirement(specifiedTokenIssuer) 256 | }; 257 | 258 | var context = new AuthorizationHandlerContext(reqsWithSingleSpecifiedIssuer, PrincipalUtil.CreateClaimsPrincipal(), null); 259 | 260 | var target = new AccessTokenHandler( 261 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 262 | 263 | // Act 264 | await target.HandleAsync(context); 265 | 266 | // Assert 267 | Assert.Equal(result, context.HasSucceeded); 268 | } 269 | 270 | [Theory] 271 | [InlineData("ttd", new string[] { "ttd", "ttd1", "ttd2" }, true)] 272 | [InlineData("ttd", new string[] { "ttd0", "ttd1", "ttd2" }, false)] 273 | public async Task HandleAsyncTest_WithMultipleApprovedTokenIssuer(string tokenIssuer, string[] specifiedTokenIssuers, bool result) 274 | { 275 | // Arrange 276 | AccessTokenSettings accessTokenSettings = new(); 277 | _options.Setup(s => s.Value).Returns(accessTokenSettings); 278 | 279 | ClaimsPrincipal principal = PrincipalUtil.CreateClaimsPrincipal(); 280 | string accessToken = AccessTokenCreator.GenerateToken(principal, -12, 5, tokenIssuer); 281 | 282 | DefaultHttpContext httpContext = new DefaultHttpContext(); 283 | httpContext.Request.Headers["PlatformAccessToken"] = accessToken; 284 | 285 | _httpContextAccessor.Setup(s => s.HttpContext).Returns(httpContext); 286 | 287 | List reqsWithSingleSpecifiedIssuer = new List 288 | { 289 | new AccessTokenRequirement(specifiedTokenIssuers) 290 | }; 291 | 292 | var context = new AuthorizationHandlerContext(reqsWithSingleSpecifiedIssuer, PrincipalUtil.CreateClaimsPrincipal(), null); 293 | 294 | var target = new AccessTokenHandler( 295 | _httpContextAccessor.Object, _logger.Object, _options.Object, _signingKeysResolver); 296 | 297 | // Act 298 | await target.HandleAsync(context); 299 | 300 | // Assert 301 | Assert.Equal(result, context.HasSucceeded); 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | #### Naming styles #### 5 | 6 | [*.cs] 7 | # Naming rules 8 | 9 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.severity = suggestion 10 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.symbols = private_or_internal_field 11 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with__.style = begins_with__ 12 | 13 | # Symbol specifications 14 | 15 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field 16 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected 17 | dotnet_naming_symbols.private_or_internal_field.required_modifiers = 18 | 19 | # Naming styles 20 | 21 | dotnet_naming_style.begins_with__.required_prefix = _ 22 | dotnet_naming_style.begins_with__.required_suffix = 23 | dotnet_naming_style.begins_with__.word_separator = 24 | dotnet_naming_style.begins_with__.capitalization = camel_case 25 | csharp_indent_labels = one_less_than_current 26 | csharp_using_directive_placement = outside_namespace:warning 27 | csharp_prefer_simple_using_statement = true:suggestion 28 | csharp_prefer_braces = true:silent 29 | csharp_style_namespace_declarations = block_scoped:silent 30 | csharp_style_prefer_method_group_conversion = true:silent 31 | csharp_style_prefer_top_level_statements = true:silent 32 | csharp_style_expression_bodied_methods = false:silent 33 | csharp_style_expression_bodied_constructors = false:silent 34 | csharp_style_expression_bodied_operators = false:silent 35 | csharp_style_expression_bodied_properties = true:silent 36 | csharp_style_expression_bodied_indexers = true:silent 37 | csharp_style_expression_bodied_accessors = true:silent 38 | csharp_style_expression_bodied_lambdas = true:silent 39 | csharp_style_expression_bodied_local_functions = false:silent 40 | csharp_style_throw_expression = true:suggestion 41 | csharp_style_prefer_null_check_over_type_check = true:suggestion 42 | csharp_prefer_simple_default_expression = true:suggestion 43 | csharp_space_around_binary_operators = before_and_after 44 | csharp_style_prefer_primary_constructors = true:suggestion 45 | 46 | [*.{cs,vb}] 47 | #### Naming styles #### 48 | 49 | # Naming rules 50 | 51 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 52 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 53 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 54 | 55 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 56 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 57 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 58 | 59 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 60 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 61 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 62 | 63 | # Symbol specifications 64 | 65 | dotnet_naming_symbols.interface.applicable_kinds = interface 66 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 67 | dotnet_naming_symbols.interface.required_modifiers = 68 | 69 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 70 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 71 | dotnet_naming_symbols.types.required_modifiers = 72 | 73 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 74 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 75 | dotnet_naming_symbols.non_field_members.required_modifiers = 76 | 77 | # Naming styles 78 | 79 | dotnet_naming_style.begins_with_i.required_prefix = I 80 | dotnet_naming_style.begins_with_i.required_suffix = 81 | dotnet_naming_style.begins_with_i.word_separator = 82 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 83 | 84 | dotnet_naming_style.pascal_case.required_prefix = 85 | dotnet_naming_style.pascal_case.required_suffix = 86 | dotnet_naming_style.pascal_case.word_separator = 87 | dotnet_naming_style.pascal_case.capitalization = pascal_case 88 | 89 | dotnet_naming_style.pascal_case.required_prefix = 90 | dotnet_naming_style.pascal_case.required_suffix = 91 | dotnet_naming_style.pascal_case.word_separator = 92 | dotnet_naming_style.pascal_case.capitalization = pascal_case 93 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 94 | tab_width = 4 95 | indent_size = 4 96 | end_of_line = crlf 97 | dotnet_style_coalesce_expression = true:suggestion 98 | dotnet_style_null_propagation = true:suggestion 99 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 100 | dotnet_style_prefer_auto_properties = true:silent 101 | dotnet_style_object_initializer = true:suggestion 102 | dotnet_style_collection_initializer = true:suggestion 103 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 104 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 105 | dotnet_style_prefer_conditional_expression_over_return = true:silent 106 | dotnet_style_explicit_tuple_names = true:suggestion 107 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 108 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 109 | dotnet_style_prefer_compound_assignment = true:suggestion 110 | dotnet_style_prefer_simplified_interpolation = true:suggestion 111 | dotnet_style_namespace_match_folder = true:suggestion 112 | 113 | # StyleCop.Analyzers 114 | [*.{cs,vb}] 115 | 116 | dotnet_diagnostic.SA0001.severity = error 117 | dotnet_diagnostic.SA0002.severity = none 118 | dotnet_diagnostic.SA1000.severity = warning 119 | dotnet_diagnostic.SA1001.severity = warning 120 | dotnet_diagnostic.SA1002.severity = warning 121 | dotnet_diagnostic.SA1003.severity = warning 122 | dotnet_diagnostic.SA1004.severity = warning 123 | dotnet_diagnostic.SA1005.severity = warning 124 | dotnet_diagnostic.SA1006.severity = warning 125 | dotnet_diagnostic.SA1007.severity = warning 126 | dotnet_diagnostic.SA1008.severity = warning 127 | dotnet_diagnostic.SA1009.severity = warning 128 | dotnet_diagnostic.SA1010.severity = warning 129 | dotnet_diagnostic.SA1011.severity = warning 130 | dotnet_diagnostic.SA1012.severity = warning 131 | dotnet_diagnostic.SA1013.severity = warning 132 | dotnet_diagnostic.SA1014.severity = warning 133 | dotnet_diagnostic.SA1015.severity = warning 134 | dotnet_diagnostic.SA1016.severity = warning 135 | dotnet_diagnostic.SA1017.severity = warning 136 | dotnet_diagnostic.SA1018.severity = warning 137 | dotnet_diagnostic.SA1019.severity = warning 138 | dotnet_diagnostic.SA1020.severity = warning 139 | dotnet_diagnostic.SA1021.severity = warning 140 | dotnet_diagnostic.SA1022.severity = warning 141 | dotnet_diagnostic.SA1023.severity = warning 142 | dotnet_diagnostic.SA1024.severity = none 143 | dotnet_diagnostic.SA1025.severity = warning 144 | dotnet_diagnostic.SA1026.severity = warning 145 | dotnet_diagnostic.SA1027.severity = warning 146 | dotnet_diagnostic.SA1028.severity = none 147 | dotnet_diagnostic.SA1100.severity = error 148 | dotnet_diagnostic.SA1101.severity = none 149 | dotnet_diagnostic.SA1102.severity = error 150 | dotnet_diagnostic.SA1103.severity = error 151 | dotnet_diagnostic.SA1104.severity = error 152 | dotnet_diagnostic.SA1105.severity = error 153 | dotnet_diagnostic.SA1106.severity = error 154 | dotnet_diagnostic.SA1107.severity = error 155 | dotnet_diagnostic.SA1108.severity = error 156 | dotnet_diagnostic.SA1110.severity = error 157 | dotnet_diagnostic.SA1111.severity = error 158 | dotnet_diagnostic.SA1112.severity = error 159 | dotnet_diagnostic.SA1113.severity = error 160 | dotnet_diagnostic.SA1114.severity = error 161 | dotnet_diagnostic.SA1115.severity = error 162 | dotnet_diagnostic.SA1116.severity = error 163 | dotnet_diagnostic.SA1117.severity = error 164 | dotnet_diagnostic.SA1118.severity = error 165 | dotnet_diagnostic.SA1119.severity = error 166 | dotnet_diagnostic.SA1120.severity = error 167 | dotnet_diagnostic.SA1121.severity = error 168 | dotnet_diagnostic.SA1122.severity = error 169 | dotnet_diagnostic.SA1123.severity = none 170 | dotnet_diagnostic.SA1124.severity = none 171 | dotnet_diagnostic.SA1125.severity = error 172 | dotnet_diagnostic.SA1127.severity = error 173 | dotnet_diagnostic.SA1128.severity = none 174 | dotnet_diagnostic.SA1129.severity = error 175 | dotnet_diagnostic.SA1130.severity = error 176 | dotnet_diagnostic.SA1131.severity = error 177 | dotnet_diagnostic.SA1132.severity = error 178 | dotnet_diagnostic.SA1133.severity = error 179 | dotnet_diagnostic.SA1134.severity = error 180 | dotnet_diagnostic.SA1135.severity = error 181 | dotnet_diagnostic.SA1136.severity = error 182 | dotnet_diagnostic.SA1137.severity = warning 183 | dotnet_diagnostic.SA1139.severity = none 184 | dotnet_diagnostic.SA1200.severity = none 185 | dotnet_diagnostic.SA1201.severity = none 186 | dotnet_diagnostic.SA1202.severity = none 187 | dotnet_diagnostic.SA1203.severity = none 188 | dotnet_diagnostic.SA1204.severity = none 189 | dotnet_diagnostic.SA1205.severity = none 190 | dotnet_diagnostic.SA1206.severity = none 191 | dotnet_diagnostic.SA1207.severity = none 192 | dotnet_diagnostic.SA1208.severity = error 193 | dotnet_diagnostic.SA1209.severity = error 194 | dotnet_diagnostic.SA1210.severity = error 195 | dotnet_diagnostic.SA1211.severity = error 196 | dotnet_diagnostic.SA1212.severity = none 197 | dotnet_diagnostic.SA1213.severity = none 198 | dotnet_diagnostic.SA1214.severity = none 199 | dotnet_diagnostic.SA1216.severity = error 200 | dotnet_diagnostic.SA1217.severity = error 201 | dotnet_diagnostic.SA1300.severity = error 202 | dotnet_diagnostic.SA1302.severity = error 203 | dotnet_diagnostic.SA1303.severity = error 204 | dotnet_diagnostic.SA1304.severity = error 205 | dotnet_diagnostic.SA1305.severity = error 206 | dotnet_diagnostic.SA1306.severity = error 207 | dotnet_diagnostic.SA1307.severity = error 208 | dotnet_diagnostic.SA1308.severity = error 209 | dotnet_diagnostic.SA1309.severity = none 210 | dotnet_diagnostic.SA1310.severity = none 211 | dotnet_diagnostic.SA1311.severity = error 212 | dotnet_diagnostic.SA1312.severity = error 213 | dotnet_diagnostic.SA1313.severity = none 214 | dotnet_diagnostic.SA1314.severity = error 215 | dotnet_diagnostic.SA1400.severity = warning 216 | dotnet_diagnostic.SA1401.severity = error 217 | dotnet_diagnostic.SA1402.severity = none 218 | dotnet_diagnostic.SA1403.severity = error 219 | dotnet_diagnostic.SA1404.severity = error 220 | dotnet_diagnostic.SA1405.severity = none 221 | dotnet_diagnostic.SA1406.severity = none 222 | dotnet_diagnostic.SA1407.severity = error 223 | dotnet_diagnostic.SA1408.severity = error 224 | dotnet_diagnostic.SA1410.severity = none 225 | dotnet_diagnostic.SA1411.severity = none 226 | dotnet_diagnostic.SA1413.severity = none 227 | dotnet_diagnostic.SA1500.severity = error 228 | dotnet_diagnostic.SA1501.severity = error 229 | dotnet_diagnostic.SA1502.severity = error 230 | dotnet_diagnostic.SA1503.severity = error 231 | dotnet_diagnostic.SA1504.severity = error 232 | dotnet_diagnostic.SA1505.severity = error 233 | dotnet_diagnostic.SA1506.severity = error 234 | dotnet_diagnostic.SA1507.severity = error 235 | dotnet_diagnostic.SA1508.severity = warning 236 | dotnet_diagnostic.SA1509.severity = warning 237 | dotnet_diagnostic.SA1510.severity = error 238 | dotnet_diagnostic.SA1511.severity = error 239 | dotnet_diagnostic.SA1512.severity = warning 240 | dotnet_diagnostic.SA1513.severity = error 241 | dotnet_diagnostic.SA1514.severity = error 242 | dotnet_diagnostic.SA1515.severity = error 243 | dotnet_diagnostic.SA1516.severity = error 244 | dotnet_diagnostic.SA1517.severity = error 245 | dotnet_diagnostic.SA1518.severity = error 246 | dotnet_diagnostic.SA1519.severity = error 247 | dotnet_diagnostic.SA1520.severity = error 248 | dotnet_diagnostic.SA1600.severity = error 249 | dotnet_diagnostic.SA1601.severity = error 250 | dotnet_diagnostic.SA1602.severity = none 251 | dotnet_diagnostic.SA1604.severity = error 252 | dotnet_diagnostic.SA1605.severity = error 253 | dotnet_diagnostic.SA1606.severity = error 254 | dotnet_diagnostic.SA1607.severity = error 255 | dotnet_diagnostic.SA1608.severity = error 256 | dotnet_diagnostic.SA1610.severity = error 257 | dotnet_diagnostic.SA1611.severity = none 258 | dotnet_diagnostic.SA1612.severity = error 259 | dotnet_diagnostic.SA1613.severity = error 260 | dotnet_diagnostic.SA1614.severity = error 261 | dotnet_diagnostic.SA1615.severity = none 262 | dotnet_diagnostic.SA1616.severity = none 263 | dotnet_diagnostic.SA1617.severity = error 264 | dotnet_diagnostic.SA1618.severity = none 265 | dotnet_diagnostic.SA1619.severity = none 266 | dotnet_diagnostic.SA1620.severity = none 267 | dotnet_diagnostic.SA1621.severity = error 268 | dotnet_diagnostic.SA1622.severity = none 269 | dotnet_diagnostic.SA1623.severity = none 270 | dotnet_diagnostic.SA1624.severity = error 271 | dotnet_diagnostic.SA1625.severity = error 272 | dotnet_diagnostic.SA1626.severity = warning 273 | dotnet_diagnostic.SA1627.severity = error 274 | dotnet_diagnostic.SA1629.severity = none 275 | dotnet_diagnostic.SA1633.severity = none 276 | dotnet_diagnostic.SA1634.severity = none 277 | dotnet_diagnostic.SA1635.severity = none 278 | dotnet_diagnostic.SA1636.severity = none 279 | dotnet_diagnostic.SA1637.severity = none 280 | dotnet_diagnostic.SA1638.severity = none 281 | dotnet_diagnostic.SA1640.severity = none 282 | dotnet_diagnostic.SA1641.severity = none 283 | dotnet_diagnostic.SA1642.severity = none 284 | dotnet_diagnostic.SA1643.severity = none 285 | dotnet_diagnostic.SA1648.severity = error 286 | dotnet_diagnostic.SA1649.severity = error 287 | dotnet_diagnostic.SA1651.severity = error 288 | insert_final_newline = true 289 | --------------------------------------------------------------------------------