├── .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 |
--------------------------------------------------------------------------------