Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions samples/OpenIdConnectSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Text.Encodings.Web;
Expand Down Expand Up @@ -44,6 +45,8 @@ public Startup(IHostingEnvironment env)

public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
Expand All @@ -56,9 +59,13 @@ public void ConfigureServices(IServiceCollection services)
o.ClientId = Configuration["oidc:clientid"];
o.ClientSecret = Configuration["oidc:clientsecret"]; // for code flow
o.Authority = Configuration["oidc:authority"];

o.ResponseType = OpenIdConnectResponseType.CodeIdToken;
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;

o.ClaimActions.MapAllExcept("aud", "iss", "iat", "nbf", "exp", "aio", "c_hash", "uti", "nonce");

o.Events = new OpenIdConnectEvents()
{
OnAuthenticationFailed = c =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ public static void MapCustomJson(this ClaimActionCollection collection, string c
collection.Add(new CustomJsonClaimAction(claimType, valueType, resolver));
}

/// <summary>
/// Clears any current ClaimsActions and maps all values from the json user data as claims, excluding duplicates.
/// </summary>
/// <param name="collection"></param>
public static void MapAll(this ClaimActionCollection collection)
{
collection.Clear();
collection.Add(new MapAllClaimsAction());
}

/// <summary>
/// Clears any current ClaimsActions and maps all values from the json user data as claims, excluding the specified types.
/// </summary>
/// <param name="collection"></param>
/// <param name="exclusions"></param>
public static void MapAllExcept(this ClaimActionCollection collection, params string[] exclusions)
{
collection.MapAll();
collection.DeleteClaims(exclusions);
}

/// <summary>
/// Delete all claims from the given ClaimsIdentity with the given ClaimType.
/// </summary>
Expand All @@ -96,5 +117,23 @@ public static void DeleteClaim(this ClaimActionCollection collection, string cla
{
collection.Add(new DeleteClaimAction(claimType));
}

/// <summary>
/// Delete all claims from the ClaimsIdentity with the given claimTypes.
/// </summary>
/// <param name="collection"></param>
/// <param name="claimTypes"></param>
public static void DeleteClaims(this ClaimActionCollection collection, params string[] claimTypes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People have been very confused by Remove vs Delete. We need to avoid aggravating that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could also just change DeleteClaim to take a params string[] right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, but it would be binary breaking.

{
if (claimTypes == null)
{
throw new ArgumentNullException(nameof(claimTypes));
}

foreach (var claimType in claimTypes)
{
collection.Add(new DeleteClaimAction(claimType));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider enhancing DeleteClaimAction type to take a list of claims so you don't have to add one action per type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance wise it's the same, you still need to loop through all of the names.

Adding them individually preserves the functionality of methods like ClaimActionsCollection.Remove(claimType) that removes the action by name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure that's exactly the behavior that's confusing today.

options.ClaimActions.Remove("something") you would think is the same as options.ClaimActions.DeleteClaim("something").

I think this api should be mostly focusing on making it easy to do the common things.

MapJsonKey
MapAll
Delete

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list of delegate/actions is fine as an implementation detail, but I really don't think generally developers should have to understand/deal with that... when all they want to do is include/ignore some set of claims. Only someone who is trying to plug in a complex mapping should have to understand the whole claim action stuff

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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;
using System.Security.Claims;
using Newtonsoft.Json.Linq;

namespace Microsoft.AspNetCore.Authentication.OAuth.Claims
{
/// <summary>
/// A ClaimAction that selects all top level values from the json user data and adds them as Claims.
/// This excludes duplicate sets of names and values.
/// </summary>
public class MapAllClaimsAction : ClaimAction
{
public MapAllClaimsAction() : base("All", ClaimValueTypes.String)
{
}

public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
if (userData == null)
{
return;
}
foreach (var pair in userData)
{
var claimValue = userData.TryGetValue(pair.Key, out var value) ? value.ToString() : null;

// Avoid adding a claim if there's a duplicate name and value. This often happens in OIDC when claims are
// retrieved both from the id_token and from the user-info endpoint.
var duplicate = identity.FindFirst(c => string.Equals(c.Type, pair.Key, StringComparison.OrdinalIgnoreCase)
&& string.Equals(c.Value, claimValue, StringComparison.Ordinal)) != null;

if (!duplicate)
{
identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, issuer));
}
}
}
}
}
57 changes: 57 additions & 0 deletions test/Microsoft.AspNetCore.Authentication.Test/ClaimActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,62 @@ public void CanMapArrayValueUserDataToClaims()
Assert.Equal("role", roleClaims[1].Type);
Assert.Equal("role2", roleClaims[1].Value);
}

[Fact]
public void MapAllSucceeds()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};

var identity = new ClaimsIdentity();
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");

Assert.Equal("name0", identity.FindFirst("name0").Type);
Assert.Equal("value0", identity.FindFirst("name0").Value);
Assert.Equal("name1", identity.FindFirst("name1").Type);
Assert.Equal("value1", identity.FindFirst("name1").Value);
}

[Fact]
public void MapAllAllowesDulicateKeysWithUniqueValues()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};

var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("name0", "value2"));
identity.AddClaim(new Claim("name1", "value3"));
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");

Assert.Equal(2, identity.FindAll("name0").Count());
Assert.Equal(2, identity.FindAll("name1").Count());
}

[Fact]
public void MapAllSkipsDuplicateValues()
{
var userData = new JObject
{
["name0"] = "value0",
["name1"] = "value1",
};

var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("name0", "value0"));
identity.AddClaim(new Claim("name1", "value1"));
var action = new MapAllClaimsAction();
action.Run(userData, identity, "iss");

Assert.Single(identity.FindAll("name0"));
Assert.Single(identity.FindAll("name1"));
}
}
}