Skip to content
This repository has been archived by the owner on Nov 22, 2018. It is now read-only.

Commit

Permalink
[Fixes #30] Updated UID generation in DefaultClaimUidExtractor
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaybhargavb committed Feb 16, 2016
1 parent ac107b5 commit 220479c
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -60,16 +61,17 @@ public AntiforgeryToken GenerateRequestToken(
};

var isIdentityAuthenticated = false;
var identity = httpContext.User?.Identity as ClaimsIdentity;

// populate Username and ClaimUid
if (identity != null && identity.IsAuthenticated)
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
if (authenticatedIdentity != null)
{
isIdentityAuthenticated = true;
requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity));
requestToken.ClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));

if (requestToken.ClaimUid == null)
{
requestToken.Username = identity.Name;
requestToken.Username = authenticatedIdentity.Name;
}
}

Expand All @@ -87,7 +89,7 @@ public AntiforgeryToken GenerateRequestToken(
// Application says user is authenticated, but we have no identifier for the user.
throw new InvalidOperationException(
Resources.FormatAntiforgeryTokenValidator_AuthenticatedUserWithoutUsername(
identity.GetType(),
authenticatedIdentity.GetType(),
nameof(IIdentity.IsAuthenticated),
"true",
nameof(IIdentity.Name),
Expand Down Expand Up @@ -148,13 +150,13 @@ public bool TryValidateTokenSet(
var currentUsername = string.Empty;
BinaryBlob currentClaimUid = null;

var identity = httpContext.User?.Identity as ClaimsIdentity;
if (identity != null && identity.IsAuthenticated)
var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);
if (authenticatedIdentity != null)
{
currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(identity));
currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));
if (currentClaimUid == null)
{
currentUsername = identity.Name ?? string.Empty;
currentUsername = authenticatedIdentity.Name ?? string.Empty;
}
}

Expand Down Expand Up @@ -200,5 +202,37 @@ private static BinaryBlob GetClaimUidBlob(string base64ClaimUid)

return new BinaryBlob(256, Convert.FromBase64String(base64ClaimUid));
}

private static ClaimsIdentity GetAuthenticatedIdentity(ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal == null)
{
return null;
}

var identitiesList = claimsPrincipal.Identities as List<ClaimsIdentity>;
if (identitiesList != null)
{
for (var i = 0; i < identitiesList.Count; i++)
{
if (identitiesList[i].IsAuthenticated)
{
return identitiesList[i];
}
}
}
else
{
foreach (var identity in claimsPrincipal.Identities)
{
if (identity.IsAuthenticated)
{
return identity;
}
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Security.Claims;
using Microsoft.Extensions.ObjectPool;

Expand All @@ -22,42 +22,99 @@ public DefaultClaimUidExtractor(ObjectPool<AntiforgerySerializationContext> pool
}

/// <inheritdoc />
public string ExtractClaimUid(ClaimsIdentity claimsIdentity)
public string ExtractClaimUid(ClaimsPrincipal claimsPrincipal)
{
if (claimsIdentity == null || !claimsIdentity.IsAuthenticated)
Debug.Assert(claimsPrincipal != null);

var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsPrincipal.Identities);
if (uniqueIdentifierParameters == null)
{
// Skip anonymous users
// No authenticated identities containing claims found.
return null;
}

var uniqueIdentifierParameters = GetUniqueIdentifierParameters(claimsIdentity);
var claimUidBytes = ComputeSha256(uniqueIdentifierParameters);
return Convert.ToBase64String(claimUidBytes);
}

// Internal for testing
internal static IEnumerable<string> GetUniqueIdentifierParameters(ClaimsIdentity claimsIdentity)
public static IList<string> GetUniqueIdentifierParameters(IEnumerable<ClaimsIdentity> claimsIdentities)
{
var nameIdentifierClaim = claimsIdentity.FindFirst(
claim => string.Equals(ClaimTypes.NameIdentifier, claim.Type, StringComparison.Ordinal));
if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value))
var identitiesList = claimsIdentities as List<ClaimsIdentity>;
if (identitiesList == null)
{
identitiesList = new List<ClaimsIdentity>(claimsIdentities);
}

for (var i = 0; i < identitiesList.Count; i++)
{
return new string[]
var identity = identitiesList[i];
if (!identity.IsAuthenticated)
{
continue;
}

var subClaim = identity.FindFirst(
claim => string.Equals("sub", claim.Type, StringComparison.Ordinal));
if (subClaim != null && !string.IsNullOrEmpty(subClaim.Value))
{
return new string[]
{
subClaim.Type,
subClaim.Value,
subClaim.Issuer
};
}

var nameIdentifierClaim = identity.FindFirst(
claim => string.Equals(ClaimTypes.NameIdentifier, claim.Type, StringComparison.Ordinal));
if (nameIdentifierClaim != null && !string.IsNullOrEmpty(nameIdentifierClaim.Value))
{
ClaimTypes.NameIdentifier,
nameIdentifierClaim.Value
};
return new string[]
{
nameIdentifierClaim.Type,
nameIdentifierClaim.Value,
nameIdentifierClaim.Issuer
};
}

var upnClaim = identity.FindFirst(
claim => string.Equals(ClaimTypes.Upn, claim.Type, StringComparison.Ordinal));
if (upnClaim != null && !string.IsNullOrEmpty(upnClaim.Value))
{
return new string[]
{
upnClaim.Type,
upnClaim.Value,
upnClaim.Issuer
};
}
}

// We do not understand any of the ClaimsIdentity instances, fallback on serializing all claims in every claims Identity.
var allClaims = new List<Claim>();
for (var i = 0; i < identitiesList.Count; i++)
{
if (identitiesList[i].IsAuthenticated)
{
allClaims.AddRange(identitiesList[i].Claims);
}
}

if (allClaims.Count == 0)
{
// No authenticated identities containing claims found.
return null;
}

// We do not understand this ClaimsIdentity, fallback on serializing the entire claims Identity.
var claims = claimsIdentity.Claims.ToList();
claims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));
allClaims.Sort((a, b) => string.Compare(a.Type, b.Type, StringComparison.Ordinal));

var identifierParameters = new List<string>(claims.Count * 2);
foreach (var claim in claims)
var identifierParameters = new List<string>(allClaims.Count * 3);
for (var i = 0; i < allClaims.Count; i++)
{
var claim = allClaims[i];
identifierParameters.Add(claim.Type);
identifierParameters.Add(claim.Value);
identifierParameters.Add(claim.Issuer);
}

return identifierParameters;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Security.Claims;

namespace Microsoft.AspNetCore.Antiforgery.Internal
{
/// <summary>
/// This interface can extract unique identifers for a claims-based identity.
/// This interface can extract unique identifers for a <see cref="ClaimsPrincipal"/>.
/// </summary>
public interface IClaimUidExtractor
{
/// <summary>
/// Extracts claims identifier.
/// </summary>
/// <param name="identity">The <see cref="ClaimsIdentity"/>.</param>
/// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/>.</param>
/// <returns>The claims identifier.</returns>
string ExtractClaimUid(ClaimsIdentity identity);
string ExtractClaimUid(ClaimsPrincipal claimsPrincipal);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Http.Internal;
Expand Down Expand Up @@ -157,7 +158,7 @@ public void GenerateRequestToken_ClaimsBasedIdentity()
var expectedClaimUid = new BinaryBlob(256, data);

var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
.Returns(base64ClaimUId);

var tokenProvider = new DefaultAntiforgeryTokenGenerator(
Expand Down Expand Up @@ -410,7 +411,7 @@ public void TryValidateTokenSet_UsernameMismatch(string identityUsername, string
};

var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
.Returns((string)null);

var tokenProvider = new DefaultAntiforgeryTokenGenerator(
Expand Down Expand Up @@ -448,7 +449,7 @@ public void TryValidateTokenSet_ClaimUidMismatch()

var differentToken = new BinaryBlob(256);
var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
.Returns(Convert.ToBase64String(differentToken.GetData()));

var tokenProvider = new DefaultAntiforgeryTokenGenerator(
Expand Down Expand Up @@ -590,7 +591,7 @@ public void TryValidateTokenSet_Success_ClaimsBasedUser()
};

var mockClaimUidExtractor = new Mock<IClaimUidExtractor>();
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(identity))
mockClaimUidExtractor.Setup(o => o.ExtractClaimUid(It.Is<ClaimsPrincipal>(c => c.Identity == identity)))
.Returns(Convert.ToBase64String(fieldtoken.ClaimUid.GetData()));

var tokenProvider = new DefaultAntiforgeryTokenGenerator(
Expand Down
Loading

0 comments on commit 220479c

Please sign in to comment.