From 9dae365d9612e0336373d84822845586da9db34c Mon Sep 17 00:00:00 2001 From: Dhanalakshmi Gopalswamy <34273718+acn-dgopa@users.noreply.github.com> Date: Thu, 21 Sep 2023 08:25:51 +0200 Subject: [PATCH] Feature/implement evntlog refresh (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * Added eventlog implementation for refresh and token exchange operations * refactored eventloghelper, authenticationhelper and updated logging implementation and tests * Added mapping authentication event from a authenticated suer model method * Added additional asserts in existing tests to verify if the log method is triggered and the expected authentication event object is same as actual * Added logging assert på logout * updated comments for event logging --------- Co-authored-by: acn-dgopa --- .../Controllers/AuthenticationController.cs | 9 +- .../Controllers/LogoutController.cs | 8 +- .../Enum/AuthenticationEventType.cs | 4 +- .../Enum/AuthenticationMethod.cs | 12 +- .../Helpers/AuthenticationHelper.cs | 135 ++++++++++-------- src/Authentication/Helpers/EventlogHelper.cs | 115 +++++++++++++-- .../Model/UserAuthenticationModel.cs | 5 - .../Services/EventLogService.cs | 11 +- .../Services/Interfaces/IEventLog.cs | 4 +- .../appsettings.Development.json | 3 +- .../AuthenticationControllerTests.cs | 101 ++++++++++--- .../Controllers/LogoutControllerTests.cs | 98 ++++++++++++- .../Services/EventLogServiceTest.cs | 22 +-- .../appsettings.json | 5 +- 14 files changed, 401 insertions(+), 131 deletions(-) diff --git a/src/Authentication/Controllers/AuthenticationController.cs b/src/Authentication/Controllers/AuthenticationController.cs index c5187357..6244633c 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, userAuthentication, eventType); if (userAuthentication != null && userAuthentication.IsAuthenticated) { @@ -266,6 +269,7 @@ public async Task RefreshJwtCookie() string serializedToken = await GenerateToken(principal); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.Refresh); _logger.LogInformation("End of refreshing token"); return Ok(serializedToken); @@ -365,6 +369,7 @@ private async Task AuthenticateAltinnStudioToken(string originalTo ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange); return Ok(serializedToken); } catch (Exception ex) @@ -484,6 +489,7 @@ private async Task AuthenticateMaskinportenToken(string originalTo ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal); + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange); return Ok(serializedToken); } catch (Exception ex) @@ -617,6 +623,7 @@ private async Task AuthenticateIdPortenToken(string originalToken) ClaimsPrincipal principal = new ClaimsPrincipal(identity); string serializedToken = await GenerateToken(principal, token.ValidTo); + 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..b8182d38 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); } @@ -98,6 +96,8 @@ public ActionResult FrontchannelLogout() CookieOptions opt = new CookieOptions() { Domain = _generalSettings.HostName, Secure = true, HttpOnly = true }; Response.Cookies.Delete(_generalSettings.SblAuthCookieName, opt); Response.Cookies.Delete(_generalSettings.JwtCookieName, opt); + string tokenCookie = Request.Cookies[_generalSettings.JwtCookieName]; + EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, tokenCookie, AuthenticationEventType.Logout); return Ok(); } diff --git a/src/Authentication/Enum/AuthenticationEventType.cs b/src/Authentication/Enum/AuthenticationEventType.cs index 2457ede1..ba8c9b9a 100644 --- a/src/Authentication/Enum/AuthenticationEventType.cs +++ b/src/Authentication/Enum/AuthenticationEventType.cs @@ -7,6 +7,8 @@ public enum AuthenticationEventType { AuthenticationFailed, Authenticated, - Logout + Refresh, + TokenExchange, + Logout, } } diff --git a/src/Authentication/Enum/AuthenticationMethod.cs b/src/Authentication/Enum/AuthenticationMethod.cs index 415d5249..cf9c5756 100644 --- a/src/Authentication/Enum/AuthenticationMethod.cs +++ b/src/Authentication/Enum/AuthenticationMethod.cs @@ -93,6 +93,16 @@ public enum AuthenticationMethod /// /// User is logged in with help of IDPORTEN OTC /// - MinIDOTC = 15 + MinIDOTC = 15, + + /// + /// user is logged in with the help of maskinporten token + /// + MaskinPorten = 16, + + /// + /// user is logged in with the help of virksomhets bruker + /// + VirksomhetsBruker = 17, } } diff --git a/src/Authentication/Helpers/AuthenticationHelper.cs b/src/Authentication/Helpers/AuthenticationHelper.cs index d15697fb..8ed46991 100644 --- a/src/Authentication/Helpers/AuthenticationHelper.cs +++ b/src/Authentication/Helpers/AuthenticationHelper.cs @@ -26,85 +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; + } + + // 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 (userAuthenticationModel.AuthenticationMethod == AuthenticationMethod.NotDefined) + if (claim.Type.Equals("amr")) { - userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider?.DefaultAuthenticationMethod); + 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; } - + /// /// 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) { @@ -120,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) { @@ -138,6 +147,8 @@ private static AuthenticationMethod GetAuthenticationMethod(string amr) return Enum.AuthenticationMethod.BankIDMobil; case "eIDAS": return Enum.AuthenticationMethod.EIDAS; + case "maskinporten": + return Enum.AuthenticationMethod.MaskinPorten; } return Enum.AuthenticationMethod.NotDefined; diff --git a/src/Authentication/Helpers/EventlogHelper.cs b/src/Authentication/Helpers/EventlogHelper.cs index 8796b9cd..3860b0b4 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,111 @@ 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) + /// token in the authentication request + /// authentication event type + public async static Task CreateAuthenticationEvent(IFeatureManager featureManager, IEventLog eventLog, string jwtToken, AuthenticationEventType eventType) { - if (eventType != null) + if (await featureManager.IsEnabledAsync(FeatureFlags.AuditLog)) { - userAuthentication.EventType = eventType; + AuthenticationEvent authenticationEvent = MapAuthenticationEvent(jwtToken, eventType); + eventLog.CreateAuthenticationEvent(authenticationEvent); } - else + } + + /// + /// Creates an authentication event + /// + /// handler for feature manager service + /// handler for eventlog service + /// authenticat + /// authentication event type + public async static Task CreateAuthenticationEvent(IFeatureManager featureManager, IEventLog eventLog, UserAuthenticationModel authenticatedUser, AuthenticationEventType eventType) + { + if (await featureManager.IsEnabledAsync(FeatureFlags.AuditLog)) { - userAuthentication.EventType = userAuthentication.IsAuthenticated ? AuthenticationEventType.Authenticated : AuthenticationEventType.AuthenticationFailed; + AuthenticationEvent authenticationEvent = MapAuthenticationEvent(authenticatedUser, 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 MapAuthenticationEvent(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.OrgNumber: + authenticationEvent.OrgNumber = claim.Value; + break; + + case AltinnCoreClaimTypes.AuthenticateMethod: + AuthenticationMethod authenticationMethod; + authenticationEvent.AuthenticationMethod = System.Enum.TryParse(claim.Value, true, 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; + } + + /// + /// Maps authenticated user information to authentication event + /// + /// authenticated user + /// type of authentication event + /// authentication event + public static AuthenticationEvent MapAuthenticationEvent(UserAuthenticationModel authenticatedUser, AuthenticationEventType eventType) + { + AuthenticationEvent authenticationEvent = null; + if (authenticatedUser != null) + { + authenticationEvent = new AuthenticationEvent(); + authenticationEvent.AuthenticationMethod = authenticatedUser.AuthenticationMethod.ToString(); + authenticationEvent.AuthenticationLevel = authenticatedUser.AuthenticationLevel.ToString(); + authenticationEvent.UserId = authenticatedUser.UserID.ToString(); + authenticationEvent.EventType = eventType.ToString(); } + + return authenticationEvent; } } } diff --git a/src/Authentication/Model/UserAuthenticationModel.cs b/src/Authentication/Model/UserAuthenticationModel.cs index fa919507..639e6507 100644 --- a/src/Authentication/Model/UserAuthenticationModel.cs +++ b/src/Authentication/Model/UserAuthenticationModel.cs @@ -73,10 +73,5 @@ 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 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/src/Authentication/appsettings.Development.json b/src/Authentication/appsettings.Development.json index 5c3e7c03..5126f8b8 100644 --- a/src/Authentication/appsettings.Development.json +++ b/src/Authentication/appsettings.Development.json @@ -31,7 +31,8 @@ "OidcProviders": { "altinn": { "Issuer": "https://platform.at22.altinn.cloud/authentication/api/v1/openid/", - "WellKnownConfigEndpoint": "https://platform.at22.altinn.cloud/authentication/api/v1/openid/.well-known/openid-configuration" + "WellKnownConfigEndpoint": "https://platform.at22.altinn.cloud/authentication/api/v1/openid/.well-known/openid-configuration", + "AuthorizationEndpoint": "https://platform.at22.altinn.cloud/authentication/api/v1/openid/" }, "maskinporten-ver2": { "Issuer": "https://ver2.maskinporten.no/", diff --git a/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs b/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs index f278a089..b93b3d12 100644 --- a/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs +++ b/test/Altinn.Platform.Authentication.Tests/Controllers/AuthenticationControllerTests.cs @@ -9,9 +9,11 @@ using System.Threading.Tasks; using System.Web; using Altinn.Common.AccessToken.Services; +using Altinn.Platform.Authentication.Clients.Interfaces; using Altinn.Platform.Authentication.Configuration; using Altinn.Platform.Authentication.Controllers; using Altinn.Platform.Authentication.Enum; +using Altinn.Platform.Authentication.Helpers; using Altinn.Platform.Authentication.Model; using Altinn.Platform.Authentication.Services; using Altinn.Platform.Authentication.Services.Interfaces; @@ -23,6 +25,7 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; +using Microsoft.Azure.KeyVault; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -62,6 +65,7 @@ public AuthenticationControllerTests(WebApplicationFactory /// Test of method . + /// Event log : Audit log feature is turned on and the event is logged with expected claims and event type /// [Fact] public async Task AuthenticateOrganisation_RequestTokenWithValidExternalToken_ReturnsNewToken() @@ -88,7 +92,11 @@ public async Task AuthenticateOrganisation_RequestTokenWithValidExternalToken_Re string externalToken = JwtTokenMock.GenerateToken(externalPrincipal, TimeSpan.FromMinutes(2)); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.MaskinPorten, SecurityLevel.Sensitive, "974760223", AuthenticationEventType.TokenExchange); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", externalToken); @@ -105,6 +113,7 @@ public async Task AuthenticateOrganisation_RequestTokenWithValidExternalToken_Re Assert.NotNull(principal); Assert.False(principal.HasClaim(c => c.Type == "urn:altinn:org")); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// @@ -277,6 +286,7 @@ public async Task AuthenticateEnterpriseUser_RequestTokenWithInvalidPassword_Ret /// /// Test of method . + /// Event log : Audit log feature is turned on and the event is logged with expected claims and event type /// [Fact] public async Task AuthenticateEnterpriseUser_RequestToken_ReturnsOK() @@ -303,7 +313,11 @@ public async Task AuthenticateEnterpriseUser_RequestToken_ReturnsOK() string externalToken = JwtTokenMock.GenerateToken(externalPrincipal, TimeSpan.FromMinutes(2)); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.VirksomhetsBruker, SecurityLevel.Sensitive, "974760223", AuthenticationEventType.TokenExchange, "1234"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", externalToken); client.DefaultRequestHeaders.Add("X-Altinn-EnterpriseUser-Authentication", "VmFsaWRVc2VyOlZhbGlkUGFzc3dvcmQ="); @@ -315,10 +329,12 @@ public async Task AuthenticateEnterpriseUser_RequestToken_ReturnsOK() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// /// Test of method . + /// Event log : Audit log feature is turned on and the event is logged with expected claims and event type /// [Fact] public async Task AuthenticateEnterpriseUser_RequestToken_PasswordContainsColon_ReturnsOK() @@ -345,18 +361,23 @@ public async Task AuthenticateEnterpriseUser_RequestToken_PasswordContainsColon_ string externalToken = JwtTokenMock.GenerateToken(externalPrincipal, TimeSpan.FromMinutes(2)); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.VirksomhetsBruker, SecurityLevel.Sensitive, "974760223", AuthenticationEventType.TokenExchange, "1234"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", externalToken); client.DefaultRequestHeaders.Add("X-Altinn-EnterpriseUser-Authentication", "VmFsaWRVc2VyMjpWYWxpZDpQYXNzd29yZA=="); - HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/exchange/maskinporten"); + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/exchange/maskinporten"); // Act HttpResponseMessage response = await client.SendAsync(requestMessage); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// @@ -458,6 +479,7 @@ public async Task AuthenticateOrganisationWithSoScope_RequestTestTokenWithValidE /// /// Test of method . + /// Event log : Audit log feature is turned on and the event is logged with expected claims and event type /// [Fact] public async Task AuthenticateUser_RequestTokenWithValidAltinnCookie_ReturnsNewToken() @@ -475,7 +497,11 @@ public async Task AuthenticateUser_RequestTokenWithValidAltinnCookie_ReturnsNewT _cookieDecryptionService.Setup(s => s.DecryptTicket(It.IsAny())).ReturnsAsync(userAuthenticationModel); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.AltinnPIN, SecurityLevel.QuiteSensitive, null, AuthenticationEventType.Authenticated, "434"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); string url = "/authentication/api/v1/authentication?goto=http%3A%2F%2Flocalhost"; HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, url); @@ -528,6 +554,7 @@ public async Task AuthenticateUser_RequestTokenWithValidAltinnCookie_ReturnsNewT Assert.True(httpOnly); Assert.True(sessionCookie); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// @@ -563,7 +590,7 @@ public async Task AuthenticateUserWithOIDC_NoTokenPortalParametersIncludedOIDCDe { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -596,7 +623,7 @@ public async Task AuthenticateUserWithOIDC_NoTokenPortalParametersIncludedOIDCDe { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true, "idporten"); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true, "idporten"); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -631,13 +658,19 @@ public async Task AuthenticateUserWithOIDC_NoTokenPortalParametersIncludedOIDCDe /// 8. Authentication compoment verifies token and create authentication info /// 9. Authentication component creates altinn 3 token and puts it in to a cookie /// 10. Redirects back to original app + /// 11. Verify the authentication event is logged with expected claims and event type /// [Fact] public async Task AuthenticateUserWithOIDC_FullProcess_RedirectsToOIDCAndBackWithValidToken() { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.BankIDMobil, SecurityLevel.VerySensitive, null, AuthenticationEventType.Authenticated, "1337"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -683,6 +716,7 @@ public async Task AuthenticateUserWithOIDC_FullProcess_RedirectsToOIDCAndBackWit Assert.NotNull(platformToken); ClaimsPrincipal claimPrincipal = JwtTokenMock.ValidateToken(platformToken); Assert.NotNull(claimPrincipal); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// @@ -710,7 +744,7 @@ public async Task AuthenticateUserWithOIDCExternalIDentity_FullProcess_Redirects UserProfile userProfile = new UserProfile { UserId = 234234, PartyId = 234234, UserName = "steph" }; _userProfileService.Setup(u => u.GetUser(It.IsAny())).ReturnsAsync(userProfileNotFound); _userProfileService.Setup(u => u.CreateUser(It.IsAny())).ReturnsAsync(userProfile); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null,true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication?iss=uidp"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true&iss=uidp"; @@ -791,7 +825,7 @@ public async Task AuthenticateUserWithOIDCExternalIDentityRelogin_FullProcess_Re UserProfile userProfile = new UserProfile { UserId = 234235, PartyId = 234235, UserName = "steph" }; _userProfileService.Setup(u => u.GetUser(It.IsAny())).ReturnsAsync(userProfile); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication?iss=uidp"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true&iss=uidp"; @@ -862,7 +896,7 @@ public async Task AuthenticateUserWithOIDC_InvalidNonce_BadRequest() { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -915,7 +949,7 @@ public async Task AuthenticateUserWithOIDC_InvalidState_BadRequest() { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -963,7 +997,7 @@ public async Task AuthenticateUserWithOIDCEnabledAndDefault_IdportenProviderRequ { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true&iss=idporten"; @@ -996,7 +1030,7 @@ public async Task AuthenticateUserWithOIDCEnabled_IdportenProviderRequested_Redi { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, true); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, true); string redirectUri = "http://localhost/authentication/api/v1/authentication"; string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true&iss=idporten"; @@ -1029,7 +1063,7 @@ public async Task AuthenticateUserWithOIDCDisabled_IdportenProviderRequested_Red { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, false, false); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, false, false); string redirectUri = "http://localhost/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl); string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true&iss=idporten"; @@ -1056,7 +1090,7 @@ public async Task AuthenticateUserWithOIDCEnabled_IdportenProviderNotRequested_R { // Arrange string gotoUrl = "http://ttd.apps.localhost/ttd/testapp"; - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, true, false); + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, null, true, false); string redirectUri = "http://localhost/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl); string url = "/authentication/api/v1/authentication?goto=" + HttpUtility.UrlEncode(gotoUrl) + "&DontChooseReportee=true"; @@ -1171,6 +1205,7 @@ public async Task AuthenticateExternalSystemToken_InvalidTokenProvider_NotAuthor /// /// Test of method . + /// Event log : Audit log feature is turned on and the event is logged with expected claims and event type /// [Fact] public async Task AuthenticateEndUser_RequestTokenWithValidExternalToken_ReturnsNewToken() @@ -1181,7 +1216,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)); @@ -1195,7 +1230,11 @@ public async Task AuthenticateEndUser_RequestTokenWithValidExternalToken_Returns UserProfile userProfile = new UserProfile { UserId = 20000, PartyId = 50001, UserName = "steph" }; _userProfileService.Setup(u => u.GetUser(It.IsAny())).ReturnsAsync(userProfile); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.MinIDPin, SecurityLevel.VerySensitive, null, AuthenticationEventType.TokenExchange, "20000"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); string externalToken = JwtTokenMock.GenerateToken(externalPrincipal, TimeSpan.FromMinutes(2)); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", externalToken); @@ -1217,6 +1256,7 @@ public async Task AuthenticateEndUser_RequestTokenWithValidExternalToken_Returns Assert.True(principal.HasClaim(c => c.Type == "pid")); Assert.Equal(expectedAuthLevel, principal.FindFirstValue("urn:altinn:authlevel")); Assert.Equal(securityTokenExternal.ValidTo, securityToken.ValidTo); + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent); } /// @@ -1231,7 +1271,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)); @@ -1408,7 +1448,7 @@ public async Task AuthenticateStudioToken_InvalidToken_ReturnsUnauthorized() Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } - private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionService, IUserProfileService userProfileService, bool enableOidc = false, bool forceOidc = false, string defaultOidc = "altinn") + private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionService, IUserProfileService userProfileService, IEventLog eventLog = null, bool enableOidc = false, bool forceOidc = false, string defaultOidc = "altinn") { HttpClient client = _factory.WithWebHostBuilder(builder => { @@ -1440,6 +1480,10 @@ private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionSer services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + if (eventLog != null) + { + services.AddSingleton(eventLog); + } }); }).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); @@ -1590,5 +1634,22 @@ private static void ValidateGoToCookie(HttpResponseMessage redirectToOidcProvide Assert.NotEmpty(setCookieHeaders); Assert.True(HasCookieValue(setCookieHeaders, "authngoto", gotoUrl)); } + + private static AuthenticationEvent GetAuthenticationEvent(AuthenticationMethod authMethod, SecurityLevel authLevel, string orgNumber, AuthenticationEventType authEventType, string userId = null) + { + AuthenticationEvent authenticationEvent = new AuthenticationEvent(); + authenticationEvent.AuthenticationMethod = authMethod.ToString(); + authenticationEvent.AuthenticationLevel = authLevel.ToString(); + authenticationEvent.OrgNumber = orgNumber; + authenticationEvent.EventType = authEventType.ToString(); + authenticationEvent.UserId = userId; + + return authenticationEvent; + } + + private static void AssertAuthenticationEvent(Mock eventQueue, AuthenticationEvent expectedAuthenticationEvent) + { + eventQueue.Verify(e => e.CreateAuthenticationEvent(It.Is(q => q.AuthenticationMethod == expectedAuthenticationEvent.AuthenticationMethod && q.AuthenticationLevel == expectedAuthenticationEvent.AuthenticationLevel && q.OrgNumber == expectedAuthenticationEvent.OrgNumber && q.UserId == expectedAuthenticationEvent.UserId && q.EventType == expectedAuthenticationEvent.EventType)), Times.Once()); + } } } diff --git a/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs b/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs index 9f7fd65a..ad0c1639 100644 --- a/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs +++ b/test/Altinn.Platform.Authentication.Tests/Controllers/LogoutControllerTests.cs @@ -25,6 +25,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Microsoft.FeatureManagement; using Moq; using Xunit; @@ -131,6 +132,7 @@ public async Task Logout_LogedIn_RedirectToSBL_SelfIdentifiedUser() /// /// Validates that a user that is not authenticated is forward to SBL logout (not possible to identify any issorg) + /// Event log : Audit log feature is turned on and the event is logged with expected claims /// [Fact] public async Task Logout_LogedIn_RedirectToSBL_ExternalAuthenticationMethod() @@ -139,11 +141,15 @@ public async Task Logout_LogedIn_RedirectToSBL_ExternalAuthenticationMethod() 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)); + claims.Add(new Claim("acr", "Level4", ClaimValueTypes.String, issuer)); string token = PrincipalUtil.GetToken(1337, claims); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.BankID, SecurityLevel.VerySensitive, null, AuthenticationEventType.Logout, "1337"); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object); HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/logout"); SetupUtil.AddAuthCookie(requestMessage, token); @@ -159,6 +165,8 @@ public async Task Logout_LogedIn_RedirectToSBL_ExternalAuthenticationMethod() { Assert.Equal("https://idporten.azurewebsites.net/api/v1/logout", values.First()); } + + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent, Moq.Times.Once()); } /// @@ -199,6 +207,7 @@ public async Task Logout_LogedIn_RedirectToIss() /// /// Frontchannel logout + /// Event log : Audit log feature is turned on and the event is logged /// [Fact] public async Task Logout_FrontChannelOK() @@ -209,7 +218,16 @@ public async Task Logout_FrontChannelOK() string token = PrincipalUtil.GetToken(1337, claims); - HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object); + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.AltinnPIN, SecurityLevel.QuiteSensitive, null, AuthenticationEventType.Logout, "1337"); + + Mock featureManageMock = new Mock(); + featureManageMock + .Setup(m => m.IsEnabledAsync("AuditLog")) + .Returns(Task.FromResult(true)); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object, featureManageMock.Object); HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/frontchannel_logout"); SetupUtil.AddAuthCookie(requestMessage, token); @@ -227,9 +245,55 @@ public async Task Logout_FrontChannelOK() Assert.Equal(".ASPXAUTH=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=localhost; path=/; secure; httponly", values.First()); Assert.Equal("AltinnStudioRuntime=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=localhost; path=/; secure; httponly", values.Last()); } + + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent, Moq.Times.Once()); } - private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionService, IUserProfileService userProfileService, bool enableOidc = false, bool forceOidc = false, string defaultOidc = "altinn") + /// + /// Frontchannel logout with event log feature turned off + /// Event log : Audit log feature is turned off and the event is not logged + /// + [Fact] + public async Task Logout_FrontChannelOK_Auditlog_off() + { + List claims = new List(); + string issuer = "www.altinn.no"; + claims.Add(new Claim("originaliss", "uidp", ClaimValueTypes.String, issuer)); + + string token = PrincipalUtil.GetToken(1337, claims); + + Mock eventQueue = new Mock(); + eventQueue.Setup(q => q.CreateAuthenticationEvent(It.IsAny())); + AuthenticationEvent expectedAuthenticationEvent = GetAuthenticationEvent(AuthenticationMethod.AltinnPIN, SecurityLevel.QuiteSensitive, null, AuthenticationEventType.Logout, "1337"); + + Mock featureManageMock = new Mock(); + featureManageMock + .Setup(m => m.IsEnabledAsync("AuditLog")) + .Returns(Task.FromResult(false)); + + HttpClient client = GetTestClient(_cookieDecryptionService.Object, _userProfileService.Object, eventQueue.Object, featureManageMock.Object); + + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "/authentication/api/v1/frontchannel_logout"); + SetupUtil.AddAuthCookie(requestMessage, token); + + // Act + HttpResponseMessage response = await client.SendAsync(requestMessage); + + // Assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + + IEnumerable values; + + if (response.Headers.TryGetValues("Set-Cookie", out values)) + { + Assert.Equal(".ASPXAUTH=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=localhost; path=/; secure; httponly", values.First()); + Assert.Equal("AltinnStudioRuntime=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=localhost; path=/; secure; httponly", values.Last()); + } + + AssertAuthenticationEvent(eventQueue, expectedAuthenticationEvent, Moq.Times.Never()); + } + + private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionService, IUserProfileService userProfileService, IEventLog eventLog = null, IFeatureManager featureManager = null, bool enableOidc = false, bool forceOidc = false, bool auditLog = true, string defaultOidc = "altinn") { HttpClient client = _factory.WithWebHostBuilder(builder => { @@ -261,6 +325,15 @@ private HttpClient GetTestClient(ISblCookieDecryptionService cookieDecryptionSer services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + if (featureManager != null) + { + services.AddSingleton(featureManager); + } + + if (eventLog != null) + { + services.AddSingleton(eventLog); + } }); }).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); @@ -272,5 +345,22 @@ private static string GetConfigPath() string unitTestFolder = Path.GetDirectoryName(new Uri(typeof(AuthenticationControllerTests).Assembly.Location).LocalPath); return Path.Combine(unitTestFolder, $"../../../appsettings.json"); } + + private static AuthenticationEvent GetAuthenticationEvent(AuthenticationMethod authMethod, SecurityLevel authLevel, string orgNumber, AuthenticationEventType authEventType, string userId = null) + { + AuthenticationEvent authenticationEvent = new AuthenticationEvent(); + authenticationEvent.AuthenticationMethod = authMethod.ToString(); + authenticationEvent.AuthenticationLevel = authLevel.ToString(); + authenticationEvent.OrgNumber = orgNumber; + authenticationEvent.EventType = authEventType.ToString(); + authenticationEvent.UserId = userId; + + return authenticationEvent; + } + + private static void AssertAuthenticationEvent(Mock eventQueue, AuthenticationEvent expectedAuthenticationEvent, Moq.Times invocationsTime) + { + eventQueue.Verify(e => e.CreateAuthenticationEvent(It.Is(q => q.AuthenticationMethod == expectedAuthenticationEvent.AuthenticationMethod && q.AuthenticationLevel == expectedAuthenticationEvent.AuthenticationLevel && q.OrgNumber == expectedAuthenticationEvent.OrgNumber && q.UserId == expectedAuthenticationEvent.UserId && q.EventType == expectedAuthenticationEvent.EventType)), invocationsTime); + } } } 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; } } } diff --git a/test/Altinn.Platform.Authentication.Tests/appsettings.json b/test/Altinn.Platform.Authentication.Tests/appsettings.json index bb4bbf16..13e4e42b 100644 --- a/test/Altinn.Platform.Authentication.Tests/appsettings.json +++ b/test/Altinn.Platform.Authentication.Tests/appsettings.json @@ -36,7 +36,7 @@ "TokenEndpoint": "https://idporten.azurewebsites.net/api/token", "WellKnownConfigEndpoint": "https://idporten.azurewebsites.net/api/v1/openid/.well-known/openid-configuration", "ClientId": "345345s", - "ProviderClaims": [ "sub", "locale", "role"] + "ProviderClaims": [ "sub", "locale", "role" ] }, "uidp": { "Issuer": "https://uidp-qa.udir.no", @@ -51,5 +51,8 @@ "ProviderClaims": [ "locale", "urn:feide:role", "sub" ] } + }, + "FeatureManagement": { + "AuditLog": true } }