From c301465ceb0a49d5046d87b6bbee6091ce799320 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:14:40 +0200 Subject: [PATCH 1/6] chore(deps): bump Microsoft.IdentityModel.Protocols.OpenIdConnect (#312) Bumps [Microsoft.IdentityModel.Protocols.OpenIdConnect](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.32.1 to 6.32.2. - [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) - [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md) - [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.32.1...6.32.2) --- updated-dependencies: - dependency-name: Microsoft.IdentityModel.Protocols.OpenIdConnect dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/Authentication/Altinn.Platform.Authentication.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Altinn.Platform.Authentication.csproj b/src/Authentication/Altinn.Platform.Authentication.csproj index 930921ed..55350e43 100644 --- a/src/Authentication/Altinn.Platform.Authentication.csproj +++ b/src/Authentication/Altinn.Platform.Authentication.csproj @@ -22,7 +22,7 @@ - + From 5f7bbb1b7a85c5983ab01664dc418e383a220fca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:15:07 +0200 Subject: [PATCH 2/6] chore(deps): bump Azure.Storage.Queues (#311) Bumps [Azure.Storage.Queues](https://github.com/Azure/azure-sdk-for-net) from 12.14.0 to 12.15.0. - [Release notes](https://github.com/Azure/azure-sdk-for-net/releases) - [Commits](https://github.com/Azure/azure-sdk-for-net/compare/Azure.Storage.Queues_12.14.0...Azure.Storage.Queues_12.15.0) --- updated-dependencies: - dependency-name: Azure.Storage.Queues dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/Authentication/Altinn.Platform.Authentication.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Altinn.Platform.Authentication.csproj b/src/Authentication/Altinn.Platform.Authentication.csproj index 55350e43..ba43d91e 100644 --- a/src/Authentication/Altinn.Platform.Authentication.csproj +++ b/src/Authentication/Altinn.Platform.Authentication.csproj @@ -16,7 +16,7 @@ - + From e4a00c708c5a9c82559c523158f890b95b484ffe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:16:08 +0200 Subject: [PATCH 3/6] chore(deps): bump Microsoft.NET.Test.Sdk (#310) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.1 to 17.7.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Altinn.Platform.Authentication.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Altinn.Platform.Authentication.Tests/Altinn.Platform.Authentication.Tests.csproj b/test/Altinn.Platform.Authentication.Tests/Altinn.Platform.Authentication.Tests.csproj index 25853453..0f2ed3bb 100644 --- a/test/Altinn.Platform.Authentication.Tests/Altinn.Platform.Authentication.Tests.csproj +++ b/test/Altinn.Platform.Authentication.Tests/Altinn.Platform.Authentication.Tests.csproj @@ -7,7 +7,7 @@ - + From 4775f33b96c89f29fc3ab98dc92d8214ae3ac8ee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:16:43 +0200 Subject: [PATCH 4/6] chore(deps): update dependency microsoft.identitymodel.protocols.openidconnect to v6.32.2 (#307) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../Authentication/Altinn.Common.Authentication.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jwtcookie/Authentication/Altinn.Common.Authentication.csproj b/src/jwtcookie/Authentication/Altinn.Common.Authentication.csproj index 3d8cf913..4eaf46e9 100644 --- a/src/jwtcookie/Authentication/Altinn.Common.Authentication.csproj +++ b/src/jwtcookie/Authentication/Altinn.Common.Authentication.csproj @@ -31,7 +31,7 @@ - + From a5e8e2c3cc2a806a48633431a95c0bff9a10325d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:24:52 +0200 Subject: [PATCH 5/6] chore(deps): bump actions/checkout from 3 to 4 (#315) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-and-analyze-fork.yml | 2 +- .github/workflows/build-and-analyze.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/container-scan.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-analyze-fork.yml b/.github/workflows/build-and-analyze-fork.yml index d78b5d52..d8f92193 100644 --- a/.github/workflows/build-and-analyze-fork.yml +++ b/.github/workflows/build-and-analyze-fork.yml @@ -15,7 +15,7 @@ jobs: dotnet-version: | 7.0.x 3.1.x - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis diff --git a/.github/workflows/build-and-analyze.yml b/.github/workflows/build-and-analyze.yml index 4ea8f387..f95c778e 100644 --- a/.github/workflows/build-and-analyze.yml +++ b/.github/workflows/build-and-analyze.yml @@ -12,7 +12,7 @@ jobs: if: ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || github.event_name == 'push') && github.repository_owner == 'Altinn' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set inotify watchers run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - name: Set inotify instances @@ -41,7 +41,7 @@ jobs: with: distribution: 'microsoft' java-version: 17 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73724dea..6cc82c6f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup .NET 7.0.* SDK uses: actions/setup-dotnet@v3 with: diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index 57e95e02..e9cf7a77 100644 --- a/.github/workflows/container-scan.yml +++ b/.github/workflows/container-scan.yml @@ -18,7 +18,7 @@ jobs: scan: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build the Docker image run: docker build . --tag altinn-authentication:${{github.sha}} From 46d5b1ff6028fd23c689236b14878a5fed3b6f32 Mon Sep 17 00:00:00 2001 From: Dhanalakshmi Gopalswamy <34273718+acn-dgopa@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:53:22 +0200 Subject: [PATCH 6/6] Implemented event logging for logout operation (#306) * Implemented event logging for logout operation * Refactored code for getuserfromtoken method * Removed the helper method and updated authenticationhelper to use method from helper * Added more tests to increase code coverage --------- Co-authored-by: acn-dgopa --- .../Clients/EventsQueueClient.cs | 30 ++-- .../Controllers/AuthenticationController.cs | 138 +---------------- .../Controllers/LogoutController.cs | 20 ++- .../AuthenticationEventType.cs | 5 +- .../Helpers/AuthenticationHelper.cs | 146 ++++++++++++++++++ src/Authentication/Helpers/EventlogHelper.cs | 12 +- .../Model/UserAuthenticationModel.cs | 5 + .../Services/EventLogService.cs | 3 +- .../Controllers/LogoutControllerTests.cs | 60 +++++++ .../Utils/PrincipalUtil.cs | 8 +- 10 files changed, 267 insertions(+), 160 deletions(-) rename src/Authentication/{Model => Enum}/AuthenticationEventType.cs (66%) create mode 100644 src/Authentication/Helpers/AuthenticationHelper.cs diff --git a/src/Authentication/Clients/EventsQueueClient.cs b/src/Authentication/Clients/EventsQueueClient.cs index cdd39ce8..355447a3 100644 --- a/src/Authentication/Clients/EventsQueueClient.cs +++ b/src/Authentication/Clients/EventsQueueClient.cs @@ -8,6 +8,7 @@ using Azure; using Azure.Storage.Queues; using Azure.Storage.Queues.Models; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Altinn.Platform.Authentication.Clients @@ -21,14 +22,19 @@ public class EventsQueueClient : IEventsQueueClient private readonly QueueStorageSettings _settings; private QueueClient _authenticationEventQueueClient; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The queue storage settings - public EventsQueueClient(IOptions settings) + /// the logger handler + public EventsQueueClient( + IOptions settings, + ILogger logger) { _settings = settings.Value; + _logger = logger; } /// @@ -39,9 +45,10 @@ public async Task EnqueueAuthenticationEvent(string content) QueueClient client = await GetAuthenticationEventQueueClient(); await client.SendMessageAsync(Convert.ToBase64String(Encoding.UTF8.GetBytes(content))); } - catch (Exception e) + catch (Exception ex) { - return new QueuePostReceipt { Success = false, Exception = e }; + _logger.LogError(ex, ex.Message); + return new QueuePostReceipt { Success = false, Exception = ex }; } return new QueuePostReceipt { Success = true }; @@ -49,20 +56,13 @@ public async Task EnqueueAuthenticationEvent(string content) private async Task GetAuthenticationEventQueueClient() { - try + if (_authenticationEventQueueClient == null) { - if (_authenticationEventQueueClient == null) - { - _authenticationEventQueueClient = new QueueClient(_settings.EventLogConnectionString, _settings.AuthenticationEventQueueName); - await _authenticationEventQueueClient.CreateIfNotExistsAsync(); - } - - return _authenticationEventQueueClient; - } - catch (Exception) - { - throw; + _authenticationEventQueueClient = new QueueClient(_settings.EventLogConnectionString, _settings.AuthenticationEventQueueName); + await _authenticationEventQueueClient.CreateIfNotExistsAsync(); } + + return _authenticationEventQueueClient; } } } diff --git a/src/Authentication/Controllers/AuthenticationController.cs b/src/Authentication/Controllers/AuthenticationController.cs index cc3ff455..c5187357 100644 --- a/src/Authentication/Controllers/AuthenticationController.cs +++ b/src/Authentication/Controllers/AuthenticationController.cs @@ -194,7 +194,7 @@ public async Task AuthenticateUser([FromQuery] string goTo, [FromQ OidcCodeResponse oidcCodeResponse = await _oidcProvider.GetTokens(code, provider, GetRedirectUri(provider)); JwtSecurityToken jwtSecurityToken = await ValidateAndExtractOidcToken(oidcCodeResponse.IdToken, provider.WellKnownConfigEndpoint); - userAuthentication = GetUserFromToken(jwtSecurityToken, provider); + userAuthentication = AuthenticationHelper.GetUserFromToken(jwtSecurityToken, provider); if (!ValidateNonce(HttpContext, userAuthentication.Nonce)) { return BadRequest("Invalid nonce"); @@ -237,7 +237,7 @@ public async Task AuthenticateUser([FromQuery] string goTo, [FromQ } } - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, null); if (userAuthentication != null && userAuthentication.IsAuthenticated) { @@ -799,95 +799,7 @@ private X509Certificate2 GetLatestCertificateWithRolloverDelay( .OrderByDescending(c => c.NotBefore) .FirstOrDefault(); } - - private static UserAuthenticationModel GetUserFromToken(JwtSecurityToken jwtSecurityToken, OidcProvider provider) - { - UserAuthenticationModel userAuthenticationModel = new UserAuthenticationModel() - { - IsAuthenticated = true, - ProviderClaims = new Dictionary>(), - Iss = provider.IssuerKey, - AuthenticationMethod = AuthenticationMethod.NotDefined - }; - - foreach (Claim claim in jwtSecurityToken.Claims) - { - // General OIDC claims - if (claim.Type.Equals("nonce")) - { - userAuthenticationModel.Nonce = claim.Value; - continue; - } - - // Altinn Specific claims - if (claim.Type.Equals(AltinnCoreClaimTypes.UserId)) - { - userAuthenticationModel.UserID = Convert.ToInt32(claim.Value); - continue; - } - - if (claim.Type.Equals(AltinnCoreClaimTypes.PartyID)) - { - userAuthenticationModel.PartyID = Convert.ToInt32(claim.Value); - continue; - } - - if (claim.Type.Equals(AltinnCoreClaimTypes.AuthenticateMethod)) - { - userAuthenticationModel.AuthenticationMethod = (Enum.AuthenticationMethod)System.Enum.Parse(typeof(Enum.AuthenticationMethod), claim.Value); - continue; - } - - if (claim.Type.Equals(AltinnCoreClaimTypes.AuthenticationLevel)) - { - userAuthenticationModel.AuthenticationLevel = (Enum.SecurityLevel)System.Enum.Parse(typeof(Enum.SecurityLevel), claim.Value); - continue; - } - - // ID-porten specific claims - if (claim.Type.Equals("pid")) - { - userAuthenticationModel.SSN = claim.Value; - continue; - } - - if (claim.Type.Equals("amr")) - { - userAuthenticationModel.AuthenticationMethod = GetAuthenticationMethod(claim.Value); - continue; - } - - if (claim.Type.Equals("acr")) - { - userAuthenticationModel.AuthenticationLevel = GetAuthenticationLevel(claim.Value); - continue; - } - - if (!string.IsNullOrEmpty(provider.ExternalIdentityClaim) && claim.Type.Equals(provider.ExternalIdentityClaim)) - { - userAuthenticationModel.ExternalIdentity = claim.Value; - } - - // General claims handling - if (provider.ProviderClaims != null && provider.ProviderClaims.Contains(claim.Type)) - { - if (!userAuthenticationModel.ProviderClaims.ContainsKey(claim.Type)) - { - userAuthenticationModel.ProviderClaims.Add(claim.Type, new List()); - } - - userAuthenticationModel.ProviderClaims[claim.Type].Add(claim.Value); - } - } - - if (userAuthenticationModel.AuthenticationMethod == AuthenticationMethod.NotDefined) - { - userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider.DefaultAuthenticationMethod); - } - - return userAuthenticationModel; - } - + private async Task IdentifyOrCreateAltinnUser(UserAuthenticationModel userAuthenticationModel, OidcProvider provider) { UserProfile profile; @@ -929,50 +841,6 @@ private static string CreateUserName(UserAuthenticationModel userAuthenticationM return provider.UserNamePrefix + hashedIdentity.ToLower() + DateTime.Now.Millisecond; } - /// - /// Converts IDporten acr claim �Authentication Context Class Reference� - The security level of assurance for the - /// authentication. Possible values are Level3 (i.e. MinID was used) or Level4 (other eIDs). - /// The level must be validated by the client. - /// - private static SecurityLevel GetAuthenticationLevel(string acr) - { - switch (acr) - { - case "Level3": - return Enum.SecurityLevel.Sensitive; - case "Level4": - return Enum.SecurityLevel.VerySensitive; - } - - return SecurityLevel.SelfIdentifed; - } - - /// - /// Converts external methods to internal Minid-PIN, Minid-OTC, Commfides, Buypass, BankID, BankID Mobil or eIDAS - /// - private static AuthenticationMethod GetAuthenticationMethod(string amr) - { - switch (amr) - { - case "Minid-PIN": - return Enum.AuthenticationMethod.MinIDPin; - case "Minid-OTC": - return Enum.AuthenticationMethod.MinIDOTC; - case "Commfides": - return Enum.AuthenticationMethod.Commfides; - case "Buypass": - return Enum.AuthenticationMethod.BuyPass; - case "BankID": - return Enum.AuthenticationMethod.BankID; - case "BankID Mobil": - return Enum.AuthenticationMethod.BankIDMobil; - case "eIDAS": - return Enum.AuthenticationMethod.EIDAS; - } - - return Enum.AuthenticationMethod.NotDefined; - } - private async Task ValidateAndExtractOidcToken(string originalToken, string wellKnownConfigEndpoint, string alternativeWellKnownConfigEndpoint = null) { try diff --git a/src/Authentication/Controllers/LogoutController.cs b/src/Authentication/Controllers/LogoutController.cs index fee4cae0..e2cdfeca 100644 --- a/src/Authentication/Controllers/LogoutController.cs +++ b/src/Authentication/Controllers/LogoutController.cs @@ -1,8 +1,11 @@ +using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using Altinn.Platform.Authentication.Configuration; +using Altinn.Platform.Authentication.Enum; using Altinn.Platform.Authentication.Extensions; +using Altinn.Platform.Authentication.Helpers; using Altinn.Platform.Authentication.Model; using Altinn.Platform.Authentication.Services.Interfaces; using Microsoft.AspNetCore.Authorization; @@ -10,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.FeatureManagement; namespace Altinn.Platform.Authentication.Controllers { @@ -27,6 +31,9 @@ public class LogoutController : ControllerBase private readonly OidcProviderSettings _oidcProviderSettings; private readonly JwtSecurityTokenHandler _validator; + private readonly IEventLog _eventLog; + private readonly IFeatureManager _featureManager; + /// /// Defay /// @@ -34,11 +41,15 @@ public LogoutController( ILogger logger, IOptions generalSettings, IOptions oidcProviderSettings, - IOidcProvider oidcProvider) + IOidcProvider oidcProvider, + IEventLog eventLog, + IFeatureManager featureManager) { _generalSettings = generalSettings.Value; _oidcProviderSettings = oidcProviderSettings.Value; _validator = new JwtSecurityTokenHandler(); + _eventLog = eventLog; + _featureManager = featureManager; } /// @@ -49,17 +60,21 @@ public LogoutController( [HttpGet("logout")] public ActionResult Logout() { + UserAuthenticationModel userAuthentication; + JwtSecurityToken jwt = null; string orgIss = null; string tokenCookie = Request.Cookies[_generalSettings.JwtCookieName]; if (_validator.CanReadToken(tokenCookie)) { - JwtSecurityToken jwt = _validator.ReadJwtToken(tokenCookie); + jwt = _validator.ReadJwtToken(tokenCookie); orgIss = jwt.Claims.Where(c => c.Type.Equals(OriginalIssClaimName)).Select(c => c.Value).FirstOrDefault(); } OidcProvider provider = GetOidcProvider(orgIss); + userAuthentication = AuthenticationHelper.GetUserFromToken(jwt, provider); if (provider == null) { + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.Logout); return Redirect(_generalSettings.SBLLogoutEndpoint); } @@ -67,6 +82,7 @@ public ActionResult Logout() Response.Cookies.Delete(_generalSettings.SblAuthCookieName, opt); Response.Cookies.Delete(_generalSettings.JwtCookieName, opt); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.Logout); return Redirect(provider.LogoutEndpoint); } diff --git a/src/Authentication/Model/AuthenticationEventType.cs b/src/Authentication/Enum/AuthenticationEventType.cs similarity index 66% rename from src/Authentication/Model/AuthenticationEventType.cs rename to src/Authentication/Enum/AuthenticationEventType.cs index 1c555d96..2457ede1 100644 --- a/src/Authentication/Model/AuthenticationEventType.cs +++ b/src/Authentication/Enum/AuthenticationEventType.cs @@ -1,4 +1,4 @@ -namespace Altinn.Platform.Authentication.Model +namespace Altinn.Platform.Authentication.Enum { /// /// Enumeration for authentication event types @@ -6,6 +6,7 @@ public enum AuthenticationEventType { AuthenticationFailed, - Authenticated + Authenticated, + Logout } } diff --git a/src/Authentication/Helpers/AuthenticationHelper.cs b/src/Authentication/Helpers/AuthenticationHelper.cs new file mode 100644 index 00000000..d15697fb --- /dev/null +++ b/src/Authentication/Helpers/AuthenticationHelper.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using Altinn.Platform.Authentication.Enum; +using Altinn.Platform.Authentication.Model; +using AltinnCore.Authentication.Constants; + +namespace Altinn.Platform.Authentication.Helpers +{ + /// + /// Helper class for authentication process + /// + public static class AuthenticationHelper + { + /// + /// Get user information from the token + /// + /// jwt token + /// authentication provider + /// user information + public static UserAuthenticationModel GetUserFromToken(JwtSecurityToken jwtSecurityToken, OidcProvider provider) + { + UserAuthenticationModel userAuthenticationModel = new UserAuthenticationModel() + { + IsAuthenticated = true, + ProviderClaims = new Dictionary>(), + Iss = provider?.IssuerKey, + AuthenticationMethod = AuthenticationMethod.NotDefined + }; + + if (jwtSecurityToken != null) + { + foreach (Claim claim in jwtSecurityToken.Claims) + { + // Handle various claim types + switch (claim.Type) + { + // General OIDC claims + case "nonce": + userAuthenticationModel.Nonce = claim.Value; + break; + + // Altinn Specific claims + case AltinnCoreClaimTypes.UserId: + userAuthenticationModel.UserID = Convert.ToInt32(claim.Value); + break; + + case AltinnCoreClaimTypes.PartyID: + userAuthenticationModel.PartyID = Convert.ToInt32(claim.Value); + break; + + case AltinnCoreClaimTypes.AuthenticateMethod: + userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), claim.Value); + break; + + case AltinnCoreClaimTypes.AuthenticationLevel: + userAuthenticationModel.AuthenticationLevel = (SecurityLevel)System.Enum.Parse(typeof(SecurityLevel), claim.Value); + break; + + // ID-porten specific claims + case "pid": + userAuthenticationModel.SSN = claim.Value; + break; + + case "amr": + userAuthenticationModel.AuthenticationMethod = GetAuthenticationMethod(claim.Value); + break; + + case "acr": + userAuthenticationModel.AuthenticationLevel = GetAuthenticationLevel(claim.Value); + break; + + default: + // Check for external identity claim + if (!string.IsNullOrEmpty(provider?.ExternalIdentityClaim) && claim.Type.Equals(provider?.ExternalIdentityClaim)) + { + userAuthenticationModel.ExternalIdentity = claim.Value; + } + + // General claims handling + if (provider?.ProviderClaims != null && provider.ProviderClaims.Contains(claim.Type)) + { + userAuthenticationModel.ProviderClaims.TryAdd(claim.Type, new List()); + userAuthenticationModel.ProviderClaims[claim.Type].Add(claim.Value); + } + + break; + } + } + + if (userAuthenticationModel.AuthenticationMethod == AuthenticationMethod.NotDefined) + { + userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider?.DefaultAuthenticationMethod); + } + } + + return userAuthenticationModel; + } + + /// + /// Converts IDporten acr claim �Authentication Context Class Reference� - The security level of assurance for the + /// authentication. Possible values are Level3 (i.e. MinID was used) or Level4 (other eIDs). + /// The level must be validated by the client. + /// + private static SecurityLevel GetAuthenticationLevel(string acr) + { + switch (acr) + { + case "Level3": + return Enum.SecurityLevel.Sensitive; + case "Level4": + return Enum.SecurityLevel.VerySensitive; + } + + return SecurityLevel.SelfIdentifed; + } + + /// + /// Converts external methods to internal Minid-PIN, Minid-OTC, Commfides, Buypass, BankID, BankID Mobil or eIDAS + /// + private static AuthenticationMethod GetAuthenticationMethod(string amr) + { + switch (amr) + { + case "Minid-PIN": + return Enum.AuthenticationMethod.MinIDPin; + case "Minid-OTC": + return Enum.AuthenticationMethod.MinIDOTC; + case "Commfides": + return Enum.AuthenticationMethod.Commfides; + case "Buypass": + return Enum.AuthenticationMethod.BuyPass; + case "BankID": + return Enum.AuthenticationMethod.BankID; + case "BankID Mobil": + return Enum.AuthenticationMethod.BankIDMobil; + case "eIDAS": + return Enum.AuthenticationMethod.EIDAS; + } + + return Enum.AuthenticationMethod.NotDefined; + } + } +} diff --git a/src/Authentication/Helpers/EventlogHelper.cs b/src/Authentication/Helpers/EventlogHelper.cs index f3f45610..8796b9cd 100644 --- a/src/Authentication/Helpers/EventlogHelper.cs +++ b/src/Authentication/Helpers/EventlogHelper.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Altinn.Platform.Authentication.Configuration; +using Altinn.Platform.Authentication.Enum; using Altinn.Platform.Authentication.Model; using Altinn.Platform.Authentication.Services.Interfaces; using Microsoft.FeatureManagement; @@ -17,8 +18,17 @@ public static class EventlogHelper /// handler for feature manager service /// handler for eventlog service /// authentication object - public async static Task CreateAuthenticationEvent(IFeatureManager featureManager, IEventLog eventLog, UserAuthenticationModel userAuthentication) + public async static Task CreateAuthenticationEvent(IFeatureManager featureManager, IEventLog eventLog, UserAuthenticationModel userAuthentication, AuthenticationEventType? eventType) { + if (eventType != null) + { + userAuthentication.EventType = eventType; + } + else + { + userAuthentication.EventType = userAuthentication.IsAuthenticated ? AuthenticationEventType.Authenticated : AuthenticationEventType.AuthenticationFailed; + } + if (await featureManager.IsEnabledAsync(FeatureFlags.AuditLog)) { eventLog.CreateAuthenticationEvent(userAuthentication); diff --git a/src/Authentication/Model/UserAuthenticationModel.cs b/src/Authentication/Model/UserAuthenticationModel.cs index 639e6507..fa919507 100644 --- a/src/Authentication/Model/UserAuthenticationModel.cs +++ b/src/Authentication/Model/UserAuthenticationModel.cs @@ -73,5 +73,10 @@ public class UserAuthenticationModel /// The external identity /// public string ExternalIdentity { get; internal set; } + + /// + /// The authentication event type + /// + public AuthenticationEventType? EventType { get; set; } } } diff --git a/src/Authentication/Services/EventLogService.cs b/src/Authentication/Services/EventLogService.cs index 37212630..00a6f398 100644 --- a/src/Authentication/Services/EventLogService.cs +++ b/src/Authentication/Services/EventLogService.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Threading.Tasks; using Altinn.Platform.Authentication.Clients.Interfaces; +using Altinn.Platform.Authentication.Enum; using Altinn.Platform.Authentication.Model; using Altinn.Platform.Authentication.Services.Interfaces; using Azure.Messaging; @@ -36,7 +37,7 @@ public void CreateAuthenticationEvent(UserAuthenticationModel authenticatedUser) authenticationEvent.AuthenticationMethod = authenticatedUser.AuthenticationMethod.ToString(); authenticationEvent.AuthenticationLevel = authenticatedUser.AuthenticationLevel.ToString(); authenticationEvent.UserId = authenticatedUser.UserID.ToString(); - authenticationEvent.EventType = authenticatedUser.IsAuthenticated ? AuthenticationEventType.Authenticated.ToString() : AuthenticationEventType.AuthenticationFailed.ToString(); + authenticationEvent.EventType = authenticatedUser.EventType.ToString(); _queueClient.EnqueueAuthenticationEvent(JsonSerializer.Serialize(authenticationEvent)); } } diff --git a/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs b/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs index 2b9d22f7..9f7fd65a 100644 --- a/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs +++ b/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs @@ -4,12 +4,14 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Security; using System.Security.Claims; using System.Text.Json; using System.Threading.Tasks; using Altinn.Common.AccessToken.Services; using Altinn.Platform.Authentication.Configuration; using Altinn.Platform.Authentication.Controllers; +using Altinn.Platform.Authentication.Enum; using Altinn.Platform.Authentication.Model; using Altinn.Platform.Authentication.Services; using Altinn.Platform.Authentication.Services.Interfaces; @@ -101,6 +103,64 @@ public async Task Logout_LogedIn_RedirectToSBL() } } + /// + /// Validates that a user that is not authenticated is forward to SBL logout (not possible to identify any issorg) + /// + [Fact] + public async Task Logout_LogedIn_RedirectToSBL_SelfIdentifiedUser() + { + string token = PrincipalUtil.GetSelfIdentifiedUserToken("siusertest", "12345", "2345678"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/logout"); + SetupUtil.AddAuthCookie(requestMessage, token); + + // Act + HttpResponseMessage response = await client.SendAsync(requestMessage); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.Found, response.StatusCode); + + IEnumerable values; + if (response.Headers.TryGetValues("location", out values)) + { + Assert.Equal("http://localhost/ui/authentication/logout", values.First()); + } + } + + /// + /// Validates that a user that is not authenticated is forward to SBL logout (not possible to identify any issorg) + /// + [Fact] + public async Task Logout_LogedIn_RedirectToSBL_ExternalAuthenticationMethod() + { + List claims = new List(); + string issuer = "www.altinn.no"; + claims.Add(new Claim("originaliss", "uidp", ClaimValueTypes.String, issuer)); + claims.Add(new Claim("amr", AuthenticationMethod.BankID.ToString(), ClaimValueTypes.String, issuer)); + claims.Add(new Claim("acr", SecurityLevel.Sensitive.ToString(), ClaimValueTypes.String, issuer)); + + string token = PrincipalUtil.GetToken(1337, claims); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/logout"); + SetupUtil.AddAuthCookie(requestMessage, token); + + // Act + HttpResponseMessage response = await client.SendAsync(requestMessage); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.Found, response.StatusCode); + + IEnumerable values; + if (response.Headers.TryGetValues("location", out values)) + { + Assert.Equal("https://idporten.azurewebsites.net/api/v1/logout", values.First()); + } + } + /// /// Validates that a user that is not authenticated is forward to SBL logout (not possible to identify any issorg) /// diff --git a/test/Altinn.Platform.Authentication.Tests/Utils/PrincipalUtil.cs b/test/Altinn.Platform.Authentication.Tests/Utils/PrincipalUtil.cs index 0e33cf8a..e4f1634f 100644 --- a/test/Altinn.Platform.Authentication.Tests/Utils/PrincipalUtil.cs +++ b/test/Altinn.Platform.Authentication.Tests/Utils/PrincipalUtil.cs @@ -23,7 +23,7 @@ public static ClaimsPrincipal GetUserPrincipal(int userId, List extClaims claims.Add(new Claim(AltinnCoreClaimTypes.UserId, userId.ToString(), ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.UserName, "UserOne", ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.PartyID, userId.ToString(), ClaimValueTypes.Integer32, issuer)); - claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "Mock", ClaimValueTypes.String, issuer)); + claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "AltinnPIN", ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, authenticationLevel.ToString(), ClaimValueTypes.Integer32, issuer)); if (extClaims != null) @@ -42,7 +42,7 @@ public static string GetOrgToken(string org, int authenticationLevel = 3) List claims = new List(); string issuer = "www.altinn.no"; claims.Add(new Claim(AltinnCoreClaimTypes.Org, org, ClaimValueTypes.String, issuer)); - claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "Mock", ClaimValueTypes.String, issuer)); + claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "Buypass", ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, authenticationLevel.ToString(), ClaimValueTypes.Integer32, issuer)); ClaimsIdentity identity = new ClaimsIdentity("mock"); @@ -62,7 +62,7 @@ public static string GetSelfIdentifiedUserToken( claims.Add(new Claim(AltinnCoreClaimTypes.UserId, userId.ToString(), ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.UserName, username, ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.PartyID, partyId.ToString(), ClaimValueTypes.Integer32, issuer)); - claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "Mock", ClaimValueTypes.String, issuer)); + claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "SelfIdentified", ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, "0", ClaimValueTypes.Integer32, issuer)); ClaimsIdentity identity = new ClaimsIdentity("mock"); @@ -79,7 +79,7 @@ public static string GetOrgToken(string org, string orgNo, int authenticationLev string issuer = "www.altinn.no"; claims.Add(new Claim(AltinnCoreClaimTypes.Org, org, ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.OrgNumber, orgNo, ClaimValueTypes.String, issuer)); - claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "Mock", ClaimValueTypes.String, issuer)); + claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticateMethod, "BankID", ClaimValueTypes.String, issuer)); claims.Add(new Claim(AltinnCoreClaimTypes.AuthenticationLevel, authenticationLevel.ToString(), ClaimValueTypes.Integer32, issuer)); ClaimsIdentity identity = new ClaimsIdentity("mock");