Skip to content

Commit

Permalink
Feature/implement evntlog refresh (#316)
Browse files Browse the repository at this point in the history
* 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 <acn-dgopa@dev-acn-tje-14>
  • Loading branch information
acn-dgopa and acn-dgopa authored Sep 21, 2023
1 parent 46d5b1f commit 9dae365
Show file tree
Hide file tree
Showing 14 changed files with 401 additions and 131 deletions.
9 changes: 8 additions & 1 deletion src/Authentication/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public AuthenticationController(
[HttpGet("authentication")]
public async Task<ActionResult> 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];
Expand Down Expand Up @@ -193,6 +194,7 @@ public async Task<ActionResult> 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))
Expand Down Expand Up @@ -237,7 +239,8 @@ public async Task<ActionResult> 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);

Check warning on line 243 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 243 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 243 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

if (userAuthentication != null && userAuthentication.IsAuthenticated)
{
Expand Down Expand Up @@ -266,6 +269,7 @@ public async Task<ActionResult> RefreshJwtCookie()

string serializedToken = await GenerateToken(principal);

EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.Refresh);

Check warning on line 272 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 272 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 272 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
_logger.LogInformation("End of refreshing token");

return Ok(serializedToken);
Expand Down Expand Up @@ -365,6 +369,7 @@ private async Task<ActionResult> AuthenticateAltinnStudioToken(string originalTo
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string serializedToken = await GenerateToken(principal);
EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange);

Check warning on line 372 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 372 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 372 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Ok(serializedToken);
}
catch (Exception ex)
Expand Down Expand Up @@ -484,6 +489,7 @@ private async Task<ActionResult> AuthenticateMaskinportenToken(string originalTo
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string serializedToken = await GenerateToken(principal);
EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange);

Check warning on line 492 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 492 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 492 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Ok(serializedToken);
}
catch (Exception ex)
Expand Down Expand Up @@ -617,6 +623,7 @@ private async Task<ActionResult> AuthenticateIdPortenToken(string originalToken)
ClaimsPrincipal principal = new ClaimsPrincipal(identity);

string serializedToken = await GenerateToken(principal, token.ValidTo);
EventlogHelper.CreateAuthenticationEvent(_featureManager, _eventLog, serializedToken, AuthenticationEventType.TokenExchange);

Check warning on line 626 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 626 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 626 in src/Authentication/Controllers/AuthenticationController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Ok(serializedToken);
}
catch (Exception ex)
Expand Down
8 changes: 4 additions & 4 deletions src/Authentication/Controllers/LogoutController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -71,18 +70,17 @@ 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);

Check warning on line 75 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 75 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 75 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Redirect(_generalSettings.SBLLogoutEndpoint);
}

CookieOptions opt = new CookieOptions() { Domain = _generalSettings.HostName, Secure = true, HttpOnly = true };
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);

Check warning on line 83 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 83 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 83 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Redirect(provider.LogoutEndpoint);
}

Expand All @@ -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);

Check warning on line 100 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 100 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Analyze

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Check warning on line 100 in src/Authentication/Controllers/LogoutController.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
return Ok();
}

Expand Down
4 changes: 3 additions & 1 deletion src/Authentication/Enum/AuthenticationEventType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public enum AuthenticationEventType
{
AuthenticationFailed,
Authenticated,
Logout
Refresh,
TokenExchange,
Logout,
}
}
12 changes: 11 additions & 1 deletion src/Authentication/Enum/AuthenticationMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public enum AuthenticationMethod
/// <summary>
/// User is logged in with help of IDPORTEN OTC
/// </summary>
MinIDOTC = 15
MinIDOTC = 15,

/// <summary>
/// user is logged in with the help of maskinporten token
/// </summary>
MaskinPorten = 16,

/// <summary>
/// user is logged in with the help of virksomhets bruker
/// </summary>
VirksomhetsBruker = 17,
}
}
135 changes: 73 additions & 62 deletions src/Authentication/Helpers/AuthenticationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,85 +26,94 @@ public static UserAuthenticationModel GetUserFromToken(JwtSecurityToken jwtSecur
{
IsAuthenticated = true,
ProviderClaims = new Dictionary<string, List<string>>(),
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<string>());
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<string>());
}

userAuthenticationModel.ProviderClaims[claim.Type].Add(claim.Value);
}
}

if (userAuthenticationModel.AuthenticationMethod == AuthenticationMethod.NotDefined)
{
userAuthenticationModel.AuthenticationMethod = (AuthenticationMethod)System.Enum.Parse(typeof(AuthenticationMethod), provider.DefaultAuthenticationMethod);
}

return userAuthenticationModel;
}

/// <summary>
/// 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.
/// </summary>
private static SecurityLevel GetAuthenticationLevel(string acr)
public static SecurityLevel GetAuthenticationLevel(string acr)
{
switch (acr)
{
Expand All @@ -120,7 +129,7 @@ private static SecurityLevel GetAuthenticationLevel(string acr)
/// <summary>
/// Converts external methods to internal Minid-PIN, Minid-OTC, Commfides, Buypass, BankID, BankID Mobil or eIDAS
/// </summary>
private static AuthenticationMethod GetAuthenticationMethod(string amr)
public static AuthenticationMethod GetAuthenticationMethod(string amr)
{
switch (amr)
{
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 9dae365

Please sign in to comment.