diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs
new file mode 100644
index 0000000000..1b9fa3e8d8
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Creates a ClaimsIdentity from a User
+ ///
+ ///
+ ///
+ public class ClaimsIdentityFactory : IClaimsIdentityFactory
+ where TUser : class, IUser
+ where TKey : IEquatable
+ {
+ ///
+ /// ClaimType used for the security stamp by default
+ ///
+ public const string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp";
+
+ ///
+ /// Constructor
+ ///
+ public ClaimsIdentityFactory()
+ {
+ RoleClaimType = ClaimsIdentity.DefaultRoleClaimType;
+ UserIdClaimType = ClaimTypes.NameIdentifier;
+ UserNameClaimType = ClaimsIdentity.DefaultNameClaimType;
+ SecurityStampClaimType = DefaultSecurityStampClaimType;
+ }
+
+ ///
+ /// Claim type used for role claims
+ ///
+ public string RoleClaimType { get; set; }
+
+ ///
+ /// Claim type used for the user name
+ ///
+ public string UserNameClaimType { get; set; }
+
+ ///
+ /// Claim type used for the user id
+ ///
+ public string UserIdClaimType { get; set; }
+
+ ///
+ /// Claim type used for the user security stamp
+ ///
+ public string SecurityStampClaimType { get; set; }
+
+ ///
+ /// Create a ClaimsIdentity from a user
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task Create(UserManager manager, TUser user,
+ string authenticationType)
+ {
+ if (manager == null)
+ {
+ throw new ArgumentNullException("manager");
+ }
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
+ id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), ClaimValueTypes.String));
+ id.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
+ if (manager.SupportsUserSecurityStamp)
+ {
+ id.AddClaim(new Claim(SecurityStampClaimType,
+ await manager.GetSecurityStamp(user.Id).ConfigureAwait(false)));
+ }
+ if (manager.SupportsUserRole)
+ {
+ var roles = await manager.GetRoles(user.Id).ConfigureAwait(false);
+ foreach (var roleName in roles)
+ {
+ id.AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String));
+ }
+ }
+ if (manager.SupportsUserClaim)
+ {
+ id.AddClaims(await manager.GetClaims(user.Id).ConfigureAwait(false));
+ }
+ return id;
+ }
+
+ ///
+ /// Convert the key to a string, by default just calls .ToString()
+ ///
+ ///
+ ///
+ public virtual string ConvertIdToString(TKey key)
+ {
+ if (key == null || key.Equals(default(TKey)))
+ {
+ return null;
+ }
+ return key.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index 1d77de8716..40cedb7985 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -39,8 +39,8 @@ namespace Microsoft.AspNet.Identity
PasswordHasher = serviceProvider.GetService();
UserValidator = serviceProvider.GetService>();
PasswordValidator = serviceProvider.GetService();
+ ClaimsIdentityFactory = serviceProvider.GetService>();
//TODO: Store = serviceProvider.GetService>();
- //TODO: ClaimsIdentityFactory = serviceProvider.GetService>();
// TODO: maybe each optional store as well? Email and SMS services?
}
@@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Identity
Store = store;
UserValidator = new UserValidator();
PasswordHasher = new PasswordHasher();
- //TODO: ClaimsIdentityFactory = new ClaimsIdentityFactory();
+ ClaimsIdentityFactory = new ClaimsIdentityFactory();
}
///
diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs
index 91dffbc9ef..ee44d8ec47 100644
--- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs
+++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs
@@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
}
[Fact]
- public async Task ValidatorCanBlockCreate()
+ public async Task UserValidatorCanBlockCreate()
{
var manager = CreateManager();
var user = new InMemoryUser("CreateBlocked");
@@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
}
[Fact]
- public async Task ValidatorCanBlockUpdate()
+ public async Task UserValidatorCanBlockUpdate()
{
var manager = CreateManager();
var user = new InMemoryUser("UpdateBlocked");
@@ -51,6 +51,50 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
IdentityResultAssert.IsFailure(await manager.Update(user), AlwaysBadValidator.ErrorMessage);
}
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public async Task UserValidatorBlocksShortEmailsWhenRequiresUniqueEmail(string email)
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("UpdateBlocked") { Email = email };
+ manager.UserValidator = new UserValidator { RequireUniqueEmail = true };
+ IdentityResultAssert.IsFailure(await manager.Create(user), "Email cannot be null or empty.");
+ }
+
+#if NET45
+ [Theory]
+ [InlineData("@@afd")]
+ [InlineData("bogus")]
+ public async Task UserValidatorBlocksInvalidEmailsWhenRequiresUniqueEmail(string email)
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("UpdateBlocked") { Email = email };
+ manager.UserValidator = new UserValidator { RequireUniqueEmail = true };
+ IdentityResultAssert.IsFailure(await manager.Create(user), "Email '"+email+"' is invalid.");
+ }
+#endif
+
+ [Fact]
+ public async Task PasswordValidatorCanBlockAddPassword()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("AddPasswordBlocked");
+ IdentityResultAssert.IsSuccess(await manager.Create(user));
+ manager.PasswordValidator = new AlwaysBadValidator();
+ IdentityResultAssert.IsFailure(await manager.AddPassword(user.Id, "password"), AlwaysBadValidator.ErrorMessage);
+ }
+
+ [Fact]
+ public async Task PasswordValidatorCanBlockChangePassword()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("ChangePasswordBlocked");
+ IdentityResultAssert.IsSuccess(await manager.Create(user, "password"));
+ manager.PasswordValidator = new AlwaysBadValidator();
+ IdentityResultAssert.IsFailure(await manager.ChangePassword(user.Id, "password", "new"), AlwaysBadValidator.ErrorMessage);
+ }
+
[Fact]
public async Task CanCreateUserNoPassword()
{
@@ -291,6 +335,43 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.Null(usersQ.FirstOrDefault(u => u.UserName == "bogus"));
}
+ [Fact]
+ public async Task ClaimsIdentityCreatesExpectedClaims()
+ {
+ var manager = CreateManager();
+ var role = CreateRoleManager();
+ var user = new InMemoryUser("Hao");
+ IdentityResultAssert.IsSuccess(await manager.Create(user));
+ IdentityResultAssert.IsSuccess(await role.Create(new InMemoryRole("Admin")));
+ IdentityResultAssert.IsSuccess(await role.Create(new InMemoryRole("Local")));
+ IdentityResultAssert.IsSuccess(await manager.AddToRole(user.Id, "Admin"));
+ IdentityResultAssert.IsSuccess(await manager.AddToRole(user.Id, "Local"));
+ Claim[] userClaims =
+ {
+ new Claim("Whatever", "Value"),
+ new Claim("Whatever2", "Value2")
+ };
+ foreach (var c in userClaims)
+ {
+ IdentityResultAssert.IsSuccess(await manager.AddClaim(user.Id, c));
+ }
+
+ var identity = await manager.CreateIdentity(user, "test");
+ var claimsFactory = manager.ClaimsIdentityFactory as ClaimsIdentityFactory;
+ Assert.NotNull(claimsFactory);
+ var claims = identity.Claims;
+ Assert.NotNull(claims);
+ Assert.True(
+ claims.Any(c => c.Type == claimsFactory.UserNameClaimType && c.Value == user.UserName));
+ Assert.True(claims.Any(c => c.Type == claimsFactory.UserIdClaimType && c.Value == user.Id));
+ Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Admin"));
+ Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Local"));
+ foreach (var cl in userClaims)
+ {
+ Assert.True(claims.Any(c => c.Type == cl.Type && c.Value == cl.Value));
+ }
+ }
+
[Fact]
public async Task ConfirmEmailFalseByDefaultTest()
{
@@ -331,7 +412,6 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
}
}
-
[Fact]
public async Task CanResetPasswordWithStaticTokenProvider()
{
@@ -351,6 +431,43 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.NotEqual(stamp, user.SecurityStamp);
}
+ [Fact]
+ public async Task PasswordValidatorCanBlockResetPasswordWithStaticTokenProvider()
+ {
+ var manager = CreateManager();
+ manager.UserTokenProvider = new StaticTokenProvider();
+ var user = new InMemoryUser("ResetPasswordTest");
+ const string password = "password";
+ const string newPassword = "newpassword";
+ IdentityResultAssert.IsSuccess(await manager.Create(user, password));
+ var stamp = user.SecurityStamp;
+ Assert.NotNull(stamp);
+ var token = await manager.GeneratePasswordResetToken(user.Id);
+ Assert.NotNull(token);
+ manager.PasswordValidator = new AlwaysBadValidator();
+ IdentityResultAssert.IsFailure(await manager.ResetPassword(user.Id, token, newPassword), AlwaysBadValidator.ErrorMessage);
+ Assert.NotNull(await manager.Find(user.UserName, password));
+ Assert.Equal(user, await manager.Find(user.UserName, password));
+ Assert.Equal(stamp, user.SecurityStamp);
+ }
+
+ [Fact]
+ public async Task ResetPasswordWithStaticTokenProviderFailsWithWrongToken()
+ {
+ var manager = CreateManager();
+ manager.UserTokenProvider = new StaticTokenProvider();
+ var user = new InMemoryUser("ResetPasswordTest");
+ const string password = "password";
+ const string newPassword = "newpassword";
+ IdentityResultAssert.IsSuccess(await manager.Create(user, password));
+ var stamp = user.SecurityStamp;
+ Assert.NotNull(stamp);
+ IdentityResultAssert.IsFailure(await manager.ResetPassword(user.Id, "bogus", newPassword), "Invalid token.");
+ Assert.NotNull(await manager.Find(user.UserName, password));
+ Assert.Equal(user, await manager.Find(user.UserName, password));
+ Assert.Equal(stamp, user.SecurityStamp);
+ }
+
[Fact]
public async Task CanGenerateAndVerifyUserTokenWithStaticTokenProvider()
{
@@ -592,7 +709,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.True(await manager.RoleExists(role.Name));
}
- private class AlwaysBadValidator : IUserValidator, IRoleValidator
+ private class AlwaysBadValidator : IUserValidator, IRoleValidator, IPasswordValidator
{
public const string ErrorMessage = "I'm Bad.";
@@ -605,6 +722,11 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
{
return Task.FromResult(IdentityResult.Failed(ErrorMessage));
}
+
+ public Task Validate(string password)
+ {
+ return Task.FromResult(IdentityResult.Failed(ErrorMessage));
+ }
}
[Fact]
@@ -1041,6 +1163,16 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.True(await manager.VerifyTwoFactorToken(user.Id, factorId, token));
}
+ [Fact]
+ public async Task NotifyWithUnknownProviderFails()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("NotifyFail");
+ IdentityResultAssert.IsSuccess(await manager.Create(user));
+ await ExceptionAssert.ThrowsAsync(async () => await manager.NotifyTwoFactorToken(user.Id, "Bogus", "token"), "No IUserTwoFactorProvider for 'Bogus' is registered.");
+ }
+
+
//[Fact]
//public async Task EmailTokenFactorWithFormatTest()
//{
diff --git a/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs b/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs
new file mode 100644
index 0000000000..42f4fc2464
--- /dev/null
+++ b/test/Microsoft.AspNet.Identity.Test/ClaimsIdentityFactoryTest.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Identity;
+using Microsoft.AspNet.Testing;
+using Xunit;
+
+namespace Microsoft.AspNet.Identity.Test
+{
+ public class ClaimsIdentityFactoryTest
+ {
+ [Fact]
+ public async Task CreateIdentityNullChecks()
+ {
+ var factory = new ClaimsIdentityFactory();
+ var manager = new UserManager(new NoopUserStore());
+ await Assert.ThrowsAsync("manager",
+ async () => await factory.Create(null, null, "whatever"));
+ await Assert.ThrowsAsync("user",
+ async () => await factory.Create(manager, null, "whatever"));
+ await Assert.ThrowsAsync("value",
+ async () => await factory.Create(manager, new TestUser(), null));
+ }
+
+ [Fact]
+ public void ConvertIdToStringWithDefaultStringReturnsNull()
+ {
+ var factory = new ClaimsIdentityFactory();
+ Assert.Null(factory.ConvertIdToString(default(string)));
+ }
+
+ [Fact]
+ public void ConvertIdToStringWithDefaultIntReturnsNull()
+ {
+ var factory = new ClaimsIdentityFactory, int>();
+ Assert.Null(factory.ConvertIdToString(default(int)));
+ }
+
+ [Fact]
+ public void ConvertIdToStringWithDefaultGuidReturnsNull()
+ {
+ var factory = new ClaimsIdentityFactory, Guid>();
+ Assert.Null(factory.ConvertIdToString(default(Guid)));
+ }
+
+ // TODO: Need Mock (test in InMemory for now)
+ //[Fact]
+ //public async Task ClaimsIdentityTest()
+ //{
+ // var db = UnitTestHelper.CreateDefaultDb();
+ // var manager = new UserManager(new UserStore(db));
+ // var role = new RoleManager(new RoleStore(db));
+ // var user = new TestUser("Hao");
+ // UnitTestHelper.IsSuccess(await manager.CreateAsync(user));
+ // UnitTestHelper.IsSuccess(await role.CreateAsync(new IdentityRole("Admin")));
+ // UnitTestHelper.IsSuccess(await role.CreateAsync(new IdentityRole("Local")));
+ // UnitTestHelper.IsSuccess(await manager.AddToRoleAsync(user.Id, "Admin"));
+ // UnitTestHelper.IsSuccess(await manager.AddToRoleAsync(user.Id, "Local"));
+ // Claim[] userClaims =
+ // {
+ // new Claim("Whatever", "Value"),
+ // new Claim("Whatever2", "Value2")
+ // };
+ // foreach (var c in userClaims)
+ // {
+ // UnitTestHelper.IsSuccess(await manager.AddClaimAsync(user.Id, c));
+ // }
+
+ // var identity = await manager.CreateIdentityAsync(user, "test");
+ // var claimsFactory = manager.ClaimsIdentityFactory as ClaimsIdentityFactory;
+ // Assert.NotNull(claimsFactory);
+ // var claims = identity.Claims;
+ // Assert.NotNull(claims);
+ // Assert.True(
+ // claims.Any(c => c.Type == claimsFactory.UserNameClaimType && c.Value == user.UserName));
+ // Assert.True(claims.Any(c => c.Type == claimsFactory.UserIdClaimType && c.Value == user.Id));
+ // Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Admin"));
+ // Assert.True(claims.Any(c => c.Type == claimsFactory.RoleClaimType && c.Value == "Local"));
+ // Assert.True(
+ // claims.Any(
+ // c =>
+ // c.Type == ClaimsIdentityFactory.IdentityProviderClaimType &&
+ // c.Value == ClaimsIdentityFactory.DefaultIdentityProviderClaimValue));
+ // foreach (var cl in userClaims)
+ // {
+ // Assert.True(claims.Any(c => c.Type == cl.Type && c.Value == cl.Value));
+ // }
+ //}
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Identity.Test/TestServices.cs b/test/Microsoft.AspNet.Identity.Test/TestServices.cs
index 8ba4a45028..115a1ea98b 100644
--- a/test/Microsoft.AspNet.Identity.Test/TestServices.cs
+++ b/test/Microsoft.AspNet.Identity.Test/TestServices.cs
@@ -11,8 +11,7 @@ namespace Microsoft.AspNet.Identity.Test
where TUser : class,IUser
where TKey : IEquatable
{
- var serviceCollection = new ServiceCollection();
- serviceCollection.Add(TestServices.DefaultServices());
+ var serviceCollection = new ServiceCollection { DefaultServices() };
return serviceCollection.BuildServiceProvider();
}
@@ -25,6 +24,7 @@ namespace Microsoft.AspNet.Identity.Test
new ServiceDescriptor(),
new ServiceDescriptor, UserValidator>(),
new ServiceDescriptor(),
+ new ServiceDescriptor, ClaimsIdentityFactory>(),
};
}
@@ -52,30 +52,5 @@ namespace Microsoft.AspNet.Identity.Test
get { return null; }
}
}
-
- public class ServiceInstanceDescriptor : IServiceDescriptor
- {
- public ServiceInstanceDescriptor(object instance)
- {
- ImplementationInstance = instance;
- }
-
- public LifecycleKind Lifecycle
- {
- get { return LifecycleKind.Singleton; }
- }
-
- public Type ServiceType
- {
- get { return typeof(TService); }
- }
-
- public Type ImplementationType
- {
- get { return null; }
- }
-
- public object ImplementationInstance { get; private set; }
- }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Identity.Test/TestUser.cs b/test/Microsoft.AspNet.Identity.Test/TestUser.cs
index c27bfabac8..cf8ba9bf9c 100644
--- a/test/Microsoft.AspNet.Identity.Test/TestUser.cs
+++ b/test/Microsoft.AspNet.Identity.Test/TestUser.cs
@@ -1,9 +1,14 @@
namespace Microsoft.AspNet.Identity.Test
{
- public class TestUser : IUser
+ public class TestUser : TestUser
{
- public string Id { get; private set; }
+ }
+
+ public class TestUser : IUser
+ {
+ public TKey Id { get; private set; }
public string UserName { get; set; }
}
+
}
diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
index 5b27b5aa3c..c26d977060 100644
--- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Runtime;
using System.Security.Claims;
using Microsoft.AspNet.Testing;
using Moq;
@@ -38,6 +39,20 @@ namespace Microsoft.AspNet.Identity.Test
// store.VerifyAll();
//}
+ [Fact]
+ public async Task CheckPasswordWithNullUserReturnsFalse()
+ {
+ var manager = new UserManager(new EmptyStore());
+ Assert.False(await manager.CheckPassword(null, "whatevs"));
+ }
+
+ [Fact]
+ public async Task FindWithUnknownUserAndPasswordReturnsNull()
+ {
+ var manager = new UserManager(new EmptyStore());
+ Assert.Null(await manager.Find("bogus", "whatevs"));
+ }
+
[Fact]
public void UsersQueryableFailWhenStoreNotImplemented()
{
@@ -310,6 +325,7 @@ namespace Microsoft.AspNet.Identity.Test
{
var manager = new UserManager(new NoopUserStore());
manager.Dispose();
+ Assert.Throws(() => manager.ClaimsIdentityFactory);
await Assert.ThrowsAsync(() => manager.AddClaim("bogus", null));
await Assert.ThrowsAsync(() => manager.AddLogin("bogus", null));
await Assert.ThrowsAsync(() => manager.AddPassword("bogus", null));