From cbf7829377af2240632e19ca112f98651f74cda2 Mon Sep 17 00:00:00 2001 From: acn-dgopa Date: Tue, 12 Sep 2023 22:57:55 +0200 Subject: [PATCH] refactored eventloghelper, authenticationhelper and updated logging implementation and tests --- .../Controllers/AuthenticationController.cs | 17 +- .../Controllers/LogoutController.cs | 6 +- .../Helpers/AuthenticationHelper.cs | 147 ++++++++---------- src/Authentication/Helpers/EventlogHelper.cs | 87 +++++++++-- .../Services/EventLogService.cs | 11 +- .../Services/Interfaces/IEventLog.cs | 4 +- .../AuthenticationControllerTests.cs | 4 +- .../Services/EventLogServiceTest.cs | 22 +-- 8 files changed, 171 insertions(+), 127 deletions(-) diff --git a/src/Authentication/Controllers/AuthenticationController.cs b/src/Authentication/Controllers/AuthenticationController.cs index a07d1dbb..0f284671 100644 --- a/src/Authentication/Controllers/AuthenticationController.cs +++ b/src/Authentication/Controllers/AuthenticationController.cs @@ -144,6 +144,7 @@ public AuthenticationController( [HttpGet("authentication")] public async Task AuthenticateUser([FromQuery] string goTo, [FromQuery] bool dontChooseReportee) { + string originalToken = null; if (string.IsNullOrEmpty(goTo) && HttpContext.Request.Cookies[_generalSettings.AuthnGoToCookieName] != null) { goTo = HttpContext.Request.Cookies[_generalSettings.AuthnGoToCookieName]; @@ -193,6 +194,7 @@ public async Task AuthenticateUser([FromQuery] string goTo, [FromQ } OidcCodeResponse oidcCodeResponse = await _oidcProvider.GetTokens(code, provider, GetRedirectUri(provider)); + originalToken = oidcCodeResponse.IdToken; JwtSecurityToken jwtSecurityToken = await ValidateAndExtractOidcToken(oidcCodeResponse.IdToken, provider.WellKnownConfigEndpoint); userAuthentication = AuthenticationHelper.GetUserFromToken(jwtSecurityToken, provider); if (!ValidateNonce(HttpContext, userAuthentication.Nonce)) @@ -237,7 +239,8 @@ public async Task AuthenticateUser([FromQuery] string goTo, [FromQ } } - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, null); + AuthenticationEventType eventType = (userAuthentication != null && userAuthentication.IsAuthenticated) ? AuthenticationEventType.Authenticated : AuthenticationEventType.AuthenticationFailed; + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, originalToken, eventType); if (userAuthentication != null && userAuthentication.IsAuthenticated) { @@ -266,8 +269,7 @@ public async Task RefreshJwtCookie() string serializedToken = await GenerateToken(principal); - UserAuthenticationModel userAuthentication = AuthenticationHelper.GetUserFromToken(serializedToken, null); - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.Refresh); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.Refresh); _logger.LogInformation("End of refreshing token"); return Ok(serializedToken); @@ -367,8 +369,7 @@ private async Task AuthenticateAltinnStudioToken(string originalTo ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal); - UserAuthenticationModel userAuthentication = AuthenticationHelper.GetUserFromToken(serializedToken, null); - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.TokenExchange); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange); return Ok(serializedToken); } catch (Exception ex) @@ -488,8 +489,7 @@ private async Task AuthenticateMaskinportenToken(string originalTo ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal); - UserAuthenticationModel userAuthentication = AuthenticationHelper.GetUserFromToken(serializedToken, null); - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.TokenExchange); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange); return Ok(serializedToken); } catch (Exception ex) @@ -623,8 +623,7 @@ private async Task AuthenticateIdPortenToken(string originalToken) ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal, token.ValidTo); - UserAuthenticationModel userAuthentication = AuthenticationHelper.GetUserFromToken(serializedToken, null); - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.TokenExchange); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange); return Ok(serializedToken); } catch (Exception ex) diff --git a/src/Authentication/Controllers/LogoutController.cs b/src/Authentication/Controllers/LogoutController.cs index e2cdfeca..a95ecb3c 100644 --- a/src/Authentication/Controllers/LogoutController.cs +++ b/src/Authentication/Controllers/LogoutController.cs @@ -60,7 +60,6 @@ public LogoutController( [HttpGet("logout")] public ActionResult Logout() { - UserAuthenticationModel userAuthentication; JwtSecurityToken jwt = null; string orgIss = null; string tokenCookie = Request.Cookies[_generalSettings.JwtCookieName]; @@ -71,10 +70,9 @@ public ActionResult Logout() } OidcProvider provider = GetOidcProvider(orgIss); - userAuthentication = AuthenticationHelper.GetUserFromToken(jwt, provider); if (provider == null) { - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.Logout); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, tokenCookie, AuthenticationEventType.Logout); return Redirect(_generalSettings.SBLLogoutEndpoint); } @@ -82,7 +80,7 @@ public ActionResult Logout() Response.Cookies.Delete(_generalSettings.SblAuthCookieName, opt); Response.Cookies.Delete(_generalSettings.JwtCookieName, opt); - EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, userAuthentication, AuthenticationEventType.Logout); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, tokenCookie, AuthenticationEventType.Logout); return Redirect(provider.LogoutEndpoint); } diff --git a/src/Authentication/Helpers/AuthenticationHelper.cs b/src/Authentication/Helpers/AuthenticationHelper.cs index 34580c3b..8b5d1fad 100644 --- a/src/Authentication/Helpers/AuthenticationHelper.cs +++ b/src/Authentication/Helpers/AuthenticationHelper.cs @@ -26,103 +26,94 @@ public static UserAuthenticationModel GetUserFromToken(JwtSecurityToken jwtSecur { IsAuthenticated = true, ProviderClaims = new Dictionary>(), - Iss = provider?.IssuerKey, + Iss = provider.IssuerKey, AuthenticationMethod = AuthenticationMethod.NotDefined }; - if (jwtSecurityToken != null) + foreach (Claim claim in jwtSecurityToken.Claims) { - foreach (Claim claim in jwtSecurityToken.Claims) + // General OIDC claims + if (claim.Type.Equals("nonce")) { - // 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; - } + userAuthenticationModel.Nonce = claim.Value; + continue; } - if (userAuthenticationModel.AuthenticationMethod == AuthenticationMethod.NotDefined) + // Altinn Specific claims + if (claim.Type.Equals(AltinnCoreClaimTypes.UserId)) { - userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider?.DefaultAuthenticationMethod); + userAuthenticationModel.UserID = Convert.ToInt32(claim.Value); + continue; } - } - return userAuthenticationModel; - } + if (claim.Type.Equals(AltinnCoreClaimTypes.PartyID)) + { + userAuthenticationModel.PartyID = Convert.ToInt32(claim.Value); + continue; + } - /// - /// Get user information from the serializwd token string - /// - /// serialized jwt token string - /// token provider - /// - public static UserAuthenticationModel GetUserFromToken(string jwtToken, OidcProvider provider) - { - JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); - if (!string.IsNullOrEmpty(jwtToken)) + 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) { - JwtSecurityToken token = tokenHandler.ReadJwtToken(jwtToken); - return GetUserFromToken(token, provider); + userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider.DefaultAuthenticationMethod); } - return null; + 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) + public static SecurityLevel GetAuthenticationLevel(string acr) { switch (acr) { @@ -138,7 +129,7 @@ private static SecurityLevel GetAuthenticationLevel(string acr) /// /// Converts external methods to internal Minid-PIN, Minid-OTC, Commfides, Buypass, BankID, BankID Mobil or eIDAS /// - private static AuthenticationMethod GetAuthenticationMethod(string amr) + public static AuthenticationMethod GetAuthenticationMethod(string amr) { switch (amr) { diff --git a/src/Authentication/Helpers/EventlogHelper.cs b/src/Authentication/Helpers/EventlogHelper.cs index 8796b9cd..c6605e62 100644 --- a/src/Authentication/Helpers/EventlogHelper.cs +++ b/src/Authentication/Helpers/EventlogHelper.cs @@ -1,8 +1,14 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +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 AltinnCore.Authentication.Constants; using Microsoft.FeatureManagement; namespace Altinn.Platform.Authentication.Helpers @@ -17,22 +23,77 @@ 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, AuthenticationEventType? eventType) - { - if (eventType != null) - { - userAuthentication.EventType = eventType; - } - else + /// token in the authentication request + /// authentication event type + public async static Task CreateAuthenticationEvent(IFeatureManager featureManager, IEventLog eventLog, string jwtToken, AuthenticationEventType eventType) + { + if (await featureManager.IsEnabledAsync(FeatureFlags.AuditLog)) { - userAuthentication.EventType = userAuthentication.IsAuthenticated ? AuthenticationEventType.Authenticated : AuthenticationEventType.AuthenticationFailed; + AuthenticationEvent authenticationEvent = MapAuthenticationEventFromToken(jwtToken, eventType); + eventLog.CreateAuthenticationEvent(authenticationEvent); } - - if (await featureManager.IsEnabledAsync(FeatureFlags.AuditLog)) + } + + /// + /// Maps claims to the authentication event model + /// + /// authenticated token + /// authentication event type + /// authentication event + public static AuthenticationEvent MapAuthenticationEventFromToken(string jwtToken, AuthenticationEventType eventType) + { + JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); + AuthenticationEvent authenticationEvent = null; + if (!string.IsNullOrEmpty(jwtToken)) { - eventLog.CreateAuthenticationEvent(userAuthentication); + JwtSecurityToken token = tokenHandler.ReadJwtToken(jwtToken); + + if (token != null) + { + authenticationEvent = new AuthenticationEvent(); + foreach (Claim claim in token.Claims) + { + // Handle various claim types + switch (claim.Type) + { + case AltinnCoreClaimTypes.UserId: + authenticationEvent.UserId = claim.Value; + break; + + case AltinnCoreClaimTypes.Org: + authenticationEvent.OrgNumber = claim.Value; + break; + + case AltinnCoreClaimTypes.OrgNumber: + authenticationEvent.OrgNumber = claim.Value; + break; + + case AltinnCoreClaimTypes.AuthenticateMethod: + AuthenticationMethod authenticationMethod; + authenticationEvent.AuthenticationMethod = System.Enum.TryParse(claim.Value, false, out authenticationMethod) ? authenticationMethod.ToString() : AuthenticationMethod.NotDefined.ToString(); + break; + + case AltinnCoreClaimTypes.AuthenticationLevel: + authenticationEvent.AuthenticationLevel = ((SecurityLevel)System.Enum.Parse(typeof(SecurityLevel), claim.Value)).ToString(); + break; + + case "amr": + authenticationEvent.AuthenticationMethod = AuthenticationHelper.GetAuthenticationMethod(claim.Value).ToString(); + break; + + case "acr": + authenticationEvent.AuthenticationLevel = AuthenticationHelper.GetAuthenticationLevel(claim.Value).ToString(); + break; + } + } + + authenticationEvent.EventType = eventType.ToString(); + } + + return authenticationEvent; } + + return authenticationEvent; } } } diff --git a/src/Authentication/Services/EventLogService.cs b/src/Authentication/Services/EventLogService.cs index 00a6f398..6d7174c0 100644 --- a/src/Authentication/Services/EventLogService.cs +++ b/src/Authentication/Services/EventLogService.cs @@ -28,16 +28,11 @@ public EventLogService(IEventsQueueClient queueClient) /// /// Queues an authentication event to the logqueue /// - /// authentication information of the authenticated user - public void CreateAuthenticationEvent(UserAuthenticationModel authenticatedUser) + /// authentication event of the authenticated user + public void CreateAuthenticationEvent(AuthenticationEvent authenticationEvent) { - if (authenticatedUser != null) + if (authenticationEvent != null) { - AuthenticationEvent authenticationEvent = new AuthenticationEvent(); - authenticationEvent.AuthenticationMethod = authenticatedUser.AuthenticationMethod.ToString(); - authenticationEvent.AuthenticationLevel = authenticatedUser.AuthenticationLevel.ToString(); - authenticationEvent.UserId = authenticatedUser.UserID.ToString(); - authenticationEvent.EventType = authenticatedUser.EventType.ToString(); _queueClient.EnqueueAuthenticationEvent(JsonSerializer.Serialize(authenticationEvent)); } } diff --git a/src/Authentication/Services/Interfaces/IEventLog.cs b/src/Authentication/Services/Interfaces/IEventLog.cs index c14ad292..639411f3 100644 --- a/src/Authentication/Services/Interfaces/IEventLog.cs +++ b/src/Authentication/Services/Interfaces/IEventLog.cs @@ -11,7 +11,7 @@ public interface IEventLog /// /// Creates an authentication event in storage queue /// - /// authenticated user information - public void CreateAuthenticationEvent(UserAuthenticationModel authenticatedUser); + /// authentication user event + public void CreateAuthenticationEvent(AuthenticationEvent authenticationEvent); } } diff --git a/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs b/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs index f278a089..23b33e32 100644 --- a/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs +++ b/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs @@ -1181,7 +1181,7 @@ public async Task AuthenticateEndUser_RequestTokenWithValidExternalToken_Returns List claims = new List(); string pid = "19108000239"; - string amr = "MinId-PIN"; + string amr = "Minid-PIN"; string acr = "Level4"; claims.Add(new Claim("pid", pid)); @@ -1231,7 +1231,7 @@ public async Task AuthenticateEndUser_RequestTokenWithValidExternalTokenNewIdpor List claims = new List(); string pid = "19108000239"; - string amr = "MinId-PIN"; + string amr = "Minid-PIN"; string acr = "idporten-loa-high"; claims.Add(new Claim("pid", pid)); diff --git a/test/Altinn.Platform.Authentication.Tests/Services/EventLogServiceTest.cs b/test/Altinn.Platform.Authentication.Tests/Services/EventLogServiceTest.cs index ef025b07..2c3d9f28 100644 --- a/test/Altinn.Platform.Authentication.Tests/Services/EventLogServiceTest.cs +++ b/test/Altinn.Platform.Authentication.Tests/Services/EventLogServiceTest.cs @@ -21,7 +21,7 @@ public class EventLogServiceTest public async Task QueueAuthenticationEvent_OK() { // Arrange - UserAuthenticationModel userAuthenticationModel = GetUserAuthenticationModel(SecurityLevel.QuiteSensitive, AuthenticationMethod.AltinnPIN, true, 45321); + AuthenticationEvent authenticationEvent = GetAuthenticationEvent(SecurityLevel.QuiteSensitive, AuthenticationMethod.AltinnPIN, AuthenticationEventType.Authenticated, "45321"); Mock queueMock = new(); queueMock @@ -30,7 +30,7 @@ public async Task QueueAuthenticationEvent_OK() var service = GetEventLogService(queueMock: queueMock.Object); // Act - service.CreateAuthenticationEvent(userAuthenticationModel); + service.CreateAuthenticationEvent(authenticationEvent); queueMock.Verify(r => r.EnqueueAuthenticationEvent(It.IsAny()), Times.Once); } @@ -39,7 +39,7 @@ public async Task QueueAuthenticationEvent_OK() public async Task QueueAuthenticationEvent_Error() { // Arrange - UserAuthenticationModel userAuthenticationModel = null; + AuthenticationEvent authenticationEvent = null; Mock queueMock = new(); queueMock @@ -48,7 +48,7 @@ public async Task QueueAuthenticationEvent_Error() var service = GetEventLogService(queueMock: queueMock.Object); // Act - service.CreateAuthenticationEvent(userAuthenticationModel); + service.CreateAuthenticationEvent(authenticationEvent); queueMock.Verify(r => r.EnqueueAuthenticationEvent(It.IsAny()), Times.Never); } @@ -64,17 +64,17 @@ private static IEventLog GetEventLogService(IEventsQueueClient queueMock = null) return service; } - private static UserAuthenticationModel GetUserAuthenticationModel(SecurityLevel authenticationLevel, AuthenticationMethod authenticationMethod, bool isAuthenticated, int userId) + private static AuthenticationEvent GetAuthenticationEvent(SecurityLevel authenticationLevel, AuthenticationMethod authenticationMethod, AuthenticationEventType eventType, string userId) { - UserAuthenticationModel authenticationModel = new() + AuthenticationEvent authenticationEvent = new() { - AuthenticationLevel = authenticationLevel, - AuthenticationMethod = authenticationMethod, - IsAuthenticated = isAuthenticated, - UserID = userId + AuthenticationLevel = authenticationLevel.ToString(), + AuthenticationMethod = authenticationMethod.ToString(), + EventType = eventType.ToString(), + UserId = userId }; - return authenticationModel; + return authenticationEvent; } } }