From 39521bd818f5ce9ece589a32827a9d98da9dce7d Mon Sep 17 00:00:00 2001 From: Bill Boga Date: Tue, 1 May 2018 11:59:32 -0400 Subject: [PATCH] Add updated name/role-claim support New behavior allows `StuntmanUser` to work similar to `ClaimsIdentity` where a custom name/role-claim can be specified. This is exposed as additional properties and is used when creating the `ClaimsIdentity` in the middleware. --- src/Core/IAppBuilderExtensions.cs | 6 +- src/Core/IApplicationBuilderExtensions.cs | 2 +- src/Core/IServiceCollectionExtensions.cs | 4 +- src/Core/StuntmanUser.cs | 74 +++++++++++++++++++---- tests/Core.Tests/StuntmanUserTests.cs | 60 ++++++++++++++++++ 5 files changed, 129 insertions(+), 17 deletions(-) diff --git a/src/Core/IAppBuilderExtensions.cs b/src/Core/IAppBuilderExtensions.cs index 8329c52..0842ef5 100644 --- a/src/Core/IAppBuilderExtensions.cs +++ b/src/Core/IAppBuilderExtensions.cs @@ -70,10 +70,10 @@ await context.Response.WriteAsync( return; } - claims.Add(new Claim(ClaimTypes.Name, user.Name)); + claims.Add(new Claim(user.NameClaimType, user.Name)); claims.AddRange(user.Claims); - var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType); + var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType, user.NameClaimType, user.RoleClaimType); var authManager = context.Authentication; @@ -184,7 +184,7 @@ public override Task ValidateIdentity(OAuthValidateIdentityContext context) claims.Add(new Claim(ClaimTypes.Name, user.Name)); claims.AddRange(user.Claims); - var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType); + var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType, user.NameClaimType, user.RoleClaimType); context.Validated(identity); diff --git a/src/Core/IApplicationBuilderExtensions.cs b/src/Core/IApplicationBuilderExtensions.cs index e1032bc..2c300fc 100644 --- a/src/Core/IApplicationBuilderExtensions.cs +++ b/src/Core/IApplicationBuilderExtensions.cs @@ -53,7 +53,7 @@ await context.Response.WriteAsync( claims.Add(new Claim(ClaimTypes.Name, user.Name)); claims.AddRange(user.Claims); - var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType); + var identity = new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType, user.NameClaimType, user.RoleClaimType); await context.SignInAsync(Constants.StuntmanAuthenticationType, new ClaimsPrincipal(identity)); diff --git a/src/Core/IServiceCollectionExtensions.cs b/src/Core/IServiceCollectionExtensions.cs index fd60269..0c80031 100644 --- a/src/Core/IServiceCollectionExtensions.cs +++ b/src/Core/IServiceCollectionExtensions.cs @@ -104,10 +104,10 @@ await context.HttpContext.Response.WriteAsync( } } - claims.Add(new Claim(ClaimTypes.Name, user.Name)); + claims.Add(new Claim(user.NameClaimType, user.Name)); claims.AddRange(user.Claims); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType)); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Constants.StuntmanAuthenticationType, user.NameClaimType, user.RoleClaimType)); context.Success(); options.AfterBearerValidateIdentity?.Invoke(context); diff --git a/src/Core/StuntmanUser.cs b/src/Core/StuntmanUser.cs index b92edf1..d7c915d 100644 --- a/src/Core/StuntmanUser.cs +++ b/src/Core/StuntmanUser.cs @@ -1,35 +1,57 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Security.Claims; namespace RimDev.Stuntman.Core { public class StuntmanUser { + public const string DefaultNameClaimType = "name"; + public const string DefaultRoleClaimType = "role"; + [JsonConstructor] - public StuntmanUser(string id, string name) + public StuntmanUser( + string id, + string name, + string nameClaimType, + string roleClaimType) { if (id == null) throw new ArgumentNullException(nameof(id)); if (name == null) throw new ArgumentNullException(nameof(name)); + if (nameClaimType == null) throw new ArgumentNullException(nameof(nameClaimType)); + if (roleClaimType == null) throw new ArgumentNullException(nameof(roleClaimType)); - if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("id must not be empty or whitespace."); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name must not be empty or whitespace."); + if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException($"{nameof(id)} must not be empty or whitespace."); + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException($"{nameof(name)} must not be empty or whitespace."); + if (string.IsNullOrWhiteSpace(nameClaimType)) throw new ArgumentException($"{nameof(nameClaimType)} must not be empty or whitespace."); + if (string.IsNullOrWhiteSpace(roleClaimType)) throw new ArgumentException($"{nameof(roleClaimType)} must not be empty or whitespace."); Id = id; Name = name; - Claims = new List(); + NameClaimType = nameClaimType; + RoleClaimType = roleClaimType; } + public StuntmanUser(string id, string name) + : this( + id: id, + name: name, + nameClaimType: DefaultNameClaimType, + roleClaimType: DefaultRoleClaimType) + { } + /// /// Creates a new user with an auto-generated Id. /// public StuntmanUser(string name) - :this( - id: Guid.NewGuid().ToString("D"), - name: name) - { - } + : this( + id: Guid.NewGuid().ToString("D"), + name: name, + nameClaimType: DefaultNameClaimType, + roleClaimType: DefaultRoleClaimType) + { } public string AccessToken { get; private set; } @@ -37,10 +59,18 @@ public StuntmanUser(string name) public string Name { get; private set; } - public ICollection Claims { get; private set; } + [DefaultValue(DefaultNameClaimType)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public string NameClaimType { get; private set; } + + public ICollection Claims { get; private set; } = new List(); public string Description { get; private set; } + [DefaultValue(DefaultRoleClaimType)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public string RoleClaimType { get; private set; } + public string Source { get; private set; } public StuntmanUser AddClaim(string type, string value) @@ -55,6 +85,28 @@ public StuntmanUser AddClaim(string type, string value) return this; } + public StuntmanUser AddName(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException($"{nameof(name)} must not be empty or whitespace."); + + AddClaim(NameClaimType, name); + + return this; + } + + public StuntmanUser AddRole(string role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + if (string.IsNullOrWhiteSpace(role)) throw new ArgumentException($"{nameof(role)} must not be empty or whitespace."); + + AddClaim(RoleClaimType, role); + + return this; + } + public StuntmanUser SetAccessToken(string accessToken) { if (accessToken == null) throw new ArgumentNullException(nameof(accessToken)); diff --git a/tests/Core.Tests/StuntmanUserTests.cs b/tests/Core.Tests/StuntmanUserTests.cs index 21bf3bf..0b30060 100644 --- a/tests/Core.Tests/StuntmanUserTests.cs +++ b/tests/Core.Tests/StuntmanUserTests.cs @@ -51,6 +51,22 @@ public void SetsName() Assert.Equal("User 1", user.Name); } + [Fact] + public void SetsNameType() + { + var user = new StuntmanUser("user-1", "User 1"); + + Assert.Equal(StuntmanUser.DefaultNameClaimType, user.NameClaimType); + } + + [Fact] + public void SetsRoleType() + { + var user = new StuntmanUser("user-1", "User 1"); + + Assert.Equal(StuntmanUser.DefaultRoleClaimType, user.RoleClaimType); + } + [Fact] public void InitializesClaimsCollection() { @@ -115,6 +131,50 @@ public void AddsExpectedClaim() } } + public class AddName + { + [Fact] + public void AddsNameClaimUsingRoleClaimType() + { + var user = new StuntmanUser("user-1", "User 1") + .AddName("name1"); + + Assert.Equal("name1", user.Claims.Single(x => x.Type == user.NameClaimType).Value); + } + + [Fact] + public void ThrowsForEmptyValue() + { + var exception = Assert.Throws( + () => new StuntmanUser("user-1", "User 1") + .AddName(string.Empty)); + + Assert.Equal("name must not be empty or whitespace.", exception.Message); + } + } + + public class AddRole + { + [Fact] + public void AddsRoleClaimUsingRoleClaimType() + { + var user = new StuntmanUser("user-1", "User 1") + .AddRole("role1"); + + Assert.Equal("role1", user.Claims.Single(x => x.Type == user.RoleClaimType).Value); + } + + [Fact] + public void ThrowsForEmptyValue() + { + var exception = Assert.Throws( + () => new StuntmanUser("user-1", "User 1") + .AddRole(string.Empty)); + + Assert.Equal("role must not be empty or whitespace.", exception.Message); + } + } + public class SetAccessToken { [Fact]