diff --git a/build.cmd b/build.cmd
index 86ca5bbbf1..220a1ff561 100644
--- a/build.cmd
+++ b/build.cmd
@@ -19,10 +19,10 @@ IF EXIST packages\KoreBuild goto run
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
-IF "%SKIP_KRE_INSTALL%"=="1" goto run
-CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
-CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
+IF "%SKIP_DOTNET_INSTALL%"=="1" goto run
+CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86
+CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86
:run
-CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86
+CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*
diff --git a/build.sh b/build.sh
index c7873ef58e..350d7e389a 100644
--- a/build.sh
+++ b/build.sh
@@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then
fi
if ! type k > /dev/null 2>&1; then
- source packages/KoreBuild/build/kvm.sh
+ source packages/KoreBuild/build/dotnetsdk.sh
fi
if ! type k > /dev/null 2>&1; then
- kvm upgrade
+ dotnetsdk upgrade
fi
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
diff --git a/samples/IdentitySample.Mvc/Controllers/AccountController.cs b/samples/IdentitySample.Mvc/Controllers/AccountController.cs
index 728328eed1..48ad2824f4 100644
--- a/samples/IdentitySample.Mvc/Controllers/AccountController.cs
+++ b/samples/IdentitySample.Mvc/Controllers/AccountController.cs
@@ -1,11 +1,10 @@
-using Microsoft.AspNet.Http;
-using Microsoft.AspNet.Identity;
-using Microsoft.AspNet.Mvc;
-using Microsoft.AspNet.Mvc.Rendering;
-using System.Linq;
+using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
+using Microsoft.AspNet.Identity;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.Rendering;
namespace IdentitySample.Models
{
@@ -43,20 +42,23 @@ namespace IdentitySample.Models
ViewBag.ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
- var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
- switch (signInStatus)
+ var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
+ if (result.Succeeded)
{
- case SignInStatus.Success:
- return RedirectToLocal(returnUrl);
- case SignInStatus.LockedOut:
- ModelState.AddModelError("", "User is locked out, try again later.");
- return View(model);
- case SignInStatus.RequiresVerification:
- return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
- case SignInStatus.Failure:
- default:
- ModelState.AddModelError("", "Invalid username or password.");
- return View(model);
+ return RedirectToLocal(returnUrl);
+ }
+ if (result.RequiresTwoFactor)
+ {
+ return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
+ }
+ if (result.IsLockedOut)
+ {
+ return View("Lockout");
+ }
+ else
+ {
+ ModelState.AddModelError("", "Invalid username or password.");
+ return View(model);
}
}
@@ -143,22 +145,26 @@ namespace IdentitySample.Models
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey,
isPersistent: false);
- switch (result)
+ if (result.Succeeded)
{
- case SignInStatus.Success:
- return RedirectToLocal(returnUrl);
- case SignInStatus.LockedOut:
- return View("Lockout");
- case SignInStatus.RequiresVerification:
- return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
- case SignInStatus.Failure:
- default:
- // If the user does not have an account, then prompt the user to create an account
- ViewBag.ReturnUrl = returnUrl;
- ViewBag.LoginProvider = info.LoginProvider;
- // REVIEW: handle case where email not in claims?
- var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email);
- return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
+ return RedirectToLocal(returnUrl);
+ }
+ if (result.RequiresTwoFactor)
+ {
+ return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
+ }
+ if (result.IsLockedOut)
+ {
+ return View("Lockout");
+ }
+ else
+ {
+ // If the user does not have an account, then prompt the user to create an account
+ ViewBag.ReturnUrl = returnUrl;
+ ViewBag.LoginProvider = info.LoginProvider;
+ // REVIEW: handle case where email not in claims?
+ var email = info.ExternalIdentity.FindFirstValue(ClaimTypes.Email);
+ return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
}
}
@@ -380,15 +386,18 @@ namespace IdentitySample.Models
}
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser);
- switch (result)
+ if (result.Succeeded)
{
- case SignInStatus.Success:
- return RedirectToLocal(model.ReturnUrl);
- case SignInStatus.LockedOut:
- return View("Lockout");
- default:
- ModelState.AddModelError("", "Invalid code.");
- return View(model);
+ return RedirectToLocal(model.ReturnUrl);
+ }
+ if (result.IsLockedOut)
+ {
+ return View("Lockout");
+ }
+ else
+ {
+ ModelState.AddModelError("", "Invalid code.");
+ return View(model);
}
}
@@ -398,7 +407,7 @@ namespace IdentitySample.Models
{
foreach (var error in result.Errors)
{
- ModelState.AddModelError("", error);
+ ModelState.AddModelError("", error.Description);
}
}
diff --git a/samples/IdentitySample.Mvc/Controllers/ManageController.cs b/samples/IdentitySample.Mvc/Controllers/ManageController.cs
index e67342fb0a..5a4359dd11 100644
--- a/samples/IdentitySample.Mvc/Controllers/ManageController.cs
+++ b/samples/IdentitySample.Mvc/Controllers/ManageController.cs
@@ -346,7 +346,7 @@ namespace IdentitySample
{
foreach (var error in result.Errors)
{
- ModelState.AddModelError("", error);
+ ModelState.AddModelError("", error.Description);
}
}
diff --git a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
index 0c84b42be0..3cdababc47 100644
--- a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
+++ b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
@@ -1,4 +1,4 @@
-
+
14.0
@@ -12,6 +12,7 @@
2.0
+ 5131
-
+
\ No newline at end of file
diff --git a/samples/IdentitySample.Mvc/LocalConfig.json b/samples/IdentitySample.Mvc/LocalConfig.json
index 06b6876a05..49b1ac00a4 100644
--- a/samples/IdentitySample.Mvc/LocalConfig.json
+++ b/samples/IdentitySample.Mvc/LocalConfig.json
@@ -3,7 +3,7 @@
"DefaultAdminPassword": "YouShouldChangeThisPassword1!",
"Data": {
"IdentityConnection": {
- "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-11-24-14;Trusted_Connection=True;MultipleActiveResultSets=true"
+ "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-1-7-15;Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
"Identity": {
diff --git a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml
index 419bedfccb..65e438ad78 100644
--- a/samples/IdentitySample.Mvc/Views/Home/Index.cshtml
+++ b/samples/IdentitySample.Mvc/Views/Home/Index.cshtml
@@ -72,7 +72,7 @@
Add Social Logins
- Get more data about the user when they login suing Facebook
+ Get more data about the user when they log in using Facebook
@@ -102,7 +102,7 @@
Account Confirmation
When you register a new account, you will be sent an email confirmation.
- You can use an email service such as SendGrid which integrate nicely with Windows Azure and requires no configuration or
+ You can use an email service such as SendGrid which integrates nicely with Windows Azure and requires no configuration or
set up an SMTP server to send email.
You can send email using the EmailService which is registered in App_Start\IdentityConfig.cs
diff --git a/samples/IdentitySample.Mvc/web.config b/samples/IdentitySample.Mvc/web.config
index 7a210ad15b..71ebc78e72 100644
--- a/samples/IdentitySample.Mvc/web.config
+++ b/samples/IdentitySample.Mvc/web.config
@@ -9,7 +9,7 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs
index add648bd0b..5ea2553f6d 100644
--- a/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs
+++ b/src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs
@@ -30,45 +30,30 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
b.Key(u => u.Id);
b.ForRelational().Table("AspNetUsers");
+ b.Property(u => u.ConcurrencyStamp).ConcurrencyToken();
});
builder.Entity(b =>
{
b.Key(r => r.Id);
b.ForRelational().Table("AspNetRoles");
+ b.Property(r => r.ConcurrencyStamp).ConcurrencyToken();
});
builder.Entity>(b =>
{
b.Key(uc => uc.Id);
- b.ManyToOne().ForeignKey(uc => uc.UserId);
+ b.HasOne().WithMany().ForeignKey(uc => uc.UserId);
b.ForRelational().Table("AspNetUserClaims");
});
builder.Entity>(b =>
{
b.Key(rc => rc.Id);
- b.ManyToOne().ForeignKey(rc => rc.RoleId);
+ b.HasOne().WithMany().ForeignKey(rc => rc.RoleId);
b.ForRelational().Table("AspNetRoleClaims");
});
- var userType = builder.Model.GetEntityType(typeof(TUser));
- var roleType = builder.Model.GetEntityType(typeof(TRole));
- var userClaimType = builder.Model.GetEntityType(typeof(IdentityUserClaim));
- var roleClaimType = builder.Model.GetEntityType(typeof(IdentityRoleClaim));
- var userRoleType = builder.Model.GetEntityType(typeof(IdentityUserRole));
- //var ucfk = userClaimType.GetOrAddForeignKey(userType.GetPrimaryKey(), new[] { userClaimType.GetProperty("UserId") });
- //userType.AddNavigation(new Navigation(ucfk, "Claims", false));
- //userClaimType.AddNavigation(new Navigation(ucfk, "User", true));
- //var urfk = userRoleType.GetOrAddForeignKey(userType.GetPrimaryKey(), new[] { userRoleType.GetProperty("UserId") });
- //userType.AddNavigation(new Navigation(urfk, "Roles", false));
-
- //var urfk2 = userRoleType.GetOrAddForeignKey(roleType.GetPrimaryKey(), new[] { userRoleType.GetProperty("RoleId") });
- //roleType.AddNavigation(new Navigation(urfk2, "Users", false));
-
- var rcfk = roleClaimType.GetOrAddForeignKey(new[] { roleClaimType.GetProperty("RoleId") }, roleType.GetPrimaryKey());
- roleType.AddNavigation("Claims", rcfk, false);
-
builder.Entity>(b =>
{
b.Key(r => new { r.UserId, r.RoleId });
@@ -81,7 +66,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
builder.Entity>(b =>
{
b.Key(l => new { l.LoginProvider, l.ProviderKey });
- b.ManyToOne().ForeignKey(uc => uc.UserId);
+ b.HasOne().WithMany().ForeignKey(uc => uc.UserId);
b.ForRelational().Table("AspNetUserLogins");
});
}
diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
index 53cab96a6a..89cf656d5a 100644
--- a/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
+++ b/src/Microsoft.AspNet.Identity.EntityFramework/RoleStore.cs
@@ -4,41 +4,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Entity;
+using Microsoft.Data.Entity.Update;
namespace Microsoft.AspNet.Identity.EntityFramework
{
public class RoleStore : RoleStore
where TRole : IdentityRole
{
- public RoleStore(DbContext context) : base(context) { }
+ public RoleStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
public class RoleStore : RoleStore
where TRole : IdentityRole
where TContext : DbContext
{
- public RoleStore(TContext context) : base(context) { }
+ public RoleStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
- public class RoleStore :
- IQueryableRoleStore,
+ public class RoleStore :
+ IQueryableRoleStore,
IRoleClaimStore
where TRole : IdentityRole
where TKey : IEquatable
where TContext : DbContext
{
- public RoleStore(TContext context)
+ public RoleStore(TContext context, IdentityErrorDescriber describer = null)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
Context = context;
+ ErrorDescriber = describer ?? new IdentityErrorDescriber();
}
private bool _disposed;
@@ -46,6 +47,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework
public TContext Context { get; private set; }
+ ///
+ /// Used to generate public API error messages
+ ///
+ public IdentityErrorDescriber ErrorDescriber { get; set; }
+
///
/// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync
///
@@ -59,12 +65,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
}
- public virtual Task GetRoleAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken))
- {
- return Roles.FirstOrDefaultAsync(filter);
- }
-
- public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -74,9 +75,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
await Context.AddAsync(role, cancellationToken);
await SaveChanges(cancellationToken);
+ return IdentityResult.Success;
}
- public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -84,11 +86,21 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("role");
}
+ Context.Attach(role);
+ role.ConcurrencyStamp = Guid.NewGuid().ToString();
Context.Update(role);
- await SaveChanges(cancellationToken);
+ try
+ {
+ await SaveChanges(cancellationToken);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
+ }
+ return IdentityResult.Success;
}
- public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -97,7 +109,15 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("role");
}
Context.Remove(role);
- await SaveChanges(cancellationToken);
+ try
+ {
+ await SaveChanges(cancellationToken);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
+ }
+ return IdentityResult.Success;
}
public Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
@@ -163,20 +183,43 @@ namespace Microsoft.AspNet.Identity.EntityFramework
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var roleId = ConvertIdFromString(id);
- return GetRoleAggregate(u => u.Id.Equals(roleId), cancellationToken);
+ return Roles.FirstOrDefaultAsync(u => u.Id.Equals(roleId), cancellationToken);
}
///
- /// Find a role by name
+ /// Find a role by normalized name
///
- ///
+ ///
///
///
- public virtual Task FindByNameAsync(string name, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- return GetRoleAggregate(u => u.Name.ToUpper() == name.ToUpper(), cancellationToken);
+ return Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName, cancellationToken);
+ }
+
+ public virtual Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+ return Task.FromResult(role.NormalizedName);
+ }
+
+ public virtual Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (role == null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+ role.NormalizedName = normalizedName;
+ return Task.FromResult(0);
}
private void ThrowIfDisposed()
@@ -203,7 +246,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("role");
}
- return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync();
+ return await RoleClaims.Where(rc => rc.RoleId.Equals(role.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken);
}
public Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken))
@@ -218,7 +261,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("claim");
}
- return RoleClaims.AddAsync(new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value });
+ return RoleClaims.AddAsync(new IdentityRoleClaim { RoleId = role.Id, ClaimType = claim.Type, ClaimValue = claim.Value }, cancellationToken);
}
public async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken))
@@ -232,7 +275,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("claim");
}
- var claims = await RoleClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync();
+ var claims = await RoleClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken);
foreach (var c in claims)
{
RoleClaims.Remove(c);
diff --git a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
index 18e9b9d87f..74e6ddb234 100644
--- a/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
+++ b/src/Microsoft.AspNet.Identity.EntityFramework/UserStore.cs
@@ -5,23 +5,23 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Entity;
+using Microsoft.Data.Entity.Update;
namespace Microsoft.AspNet.Identity.EntityFramework
{
public class UserStore : UserStore
{
- public UserStore(DbContext context) : base(context) { }
+ public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
public class UserStore : UserStore
where TUser : IdentityUser, new()
{
- public UserStore(DbContext context) : base(context) { }
+ public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
public class UserStore : UserStore
@@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
where TRole : IdentityRole, new()
where TContext : DbContext
{
- public UserStore(TContext context) : base(context) { }
+ public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}
public class UserStore :
@@ -49,19 +49,25 @@ namespace Microsoft.AspNet.Identity.EntityFramework
where TKey : IEquatable
{
- public UserStore(TContext context)
+ public UserStore(TContext context, IdentityErrorDescriber describer = null)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
Context = context;
+ ErrorDescriber = describer ?? new IdentityErrorDescriber();
}
private bool _disposed;
public TContext Context { get; private set; }
+ ///
+ /// Used to generate public API error messages
+ ///
+ public IdentityErrorDescriber ErrorDescriber { get; set; }
+
///
/// If true will call SaveChanges after CreateAsync/UpdateAsync/DeleteAsync
///
@@ -72,15 +78,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return AutoSaveChanges ? Context.SaveChangesAsync(cancellationToken) : Task.FromResult(0);
}
- protected virtual Task GetUserAggregate(Expression> filter, CancellationToken cancellationToken = default(CancellationToken))
- {
- return Users.FirstOrDefaultAsync(filter, cancellationToken);
- // TODO: .Include(u => u.Roles)
- //.Include(u => u.Claims)
- //.Include(u => u.Logins);
- }
-
- public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetUserIdAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -91,7 +89,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(ConvertIdToString(user.Id));
}
- public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -102,7 +100,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.UserName);
}
- public Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -114,7 +112,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(0);
}
- public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -125,7 +123,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.NormalizedUserName);
}
- public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -137,7 +135,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(0);
}
- public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -147,9 +145,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
await Context.AddAsync(user, cancellationToken);
await SaveChanges(cancellationToken);
+ return IdentityResult.Success;
}
- public async virtual Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -157,11 +156,22 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("user");
}
+
+ Context.Attach(user);
+ user.ConcurrencyStamp = Guid.NewGuid().ToString();
Context.Update(user);
- await SaveChanges(cancellationToken);
+ try
+ {
+ await SaveChanges(cancellationToken);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
+ }
+ return IdentityResult.Success;
}
- public async virtual Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -169,8 +179,17 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("user");
}
+
Context.Remove(user);
- await SaveChanges(cancellationToken);
+ try
+ {
+ await SaveChanges(cancellationToken);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
+ }
+ return IdentityResult.Success;
}
///
@@ -184,7 +203,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var id = ConvertIdFromString(userId);
- return GetUserAggregate(u => u.Id.Equals(id), cancellationToken);
+ return Users.FirstOrDefaultAsync(u => u.Id.Equals(id), cancellationToken);
}
public virtual TKey ConvertIdFromString(string id)
@@ -215,7 +234,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- return GetUserAggregate(u => u.NormalizedUserName == normalizedUserName, cancellationToken);
+ return Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken);
}
public IQueryable Users
@@ -290,16 +309,13 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName");
}
- var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper());
+ var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken);
if (roleEntity == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.RoleNotFound, roleName));
}
var ur = new IdentityUserRole { UserId = user.Id, RoleId = roleEntity.Id };
- // TODO: rely on fixup?
await UserRoles.AddAsync(ur);
- user.Roles.Add(ur);
- roleEntity.Users.Add(ur);
}
///
@@ -321,14 +337,13 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName");
}
- var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper());
+ var roleEntity = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken);
if (roleEntity != null)
{
- var userRole = await UserRoles.FirstOrDefaultAsync(r => roleEntity.Id.Equals(r.RoleId) && r.UserId.Equals(user.Id));
+ var userRole = await UserRoles.FirstOrDefaultAsync(r => roleEntity.Id.Equals(r.RoleId) && r.UserId.Equals(user.Id), cancellationToken);
if (userRole != null)
{
UserRoles.Remove(userRole);
- user.Roles.Remove(userRole);
}
}
}
@@ -339,7 +354,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
///
///
///
- public virtual Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual async Task> GetRolesAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@@ -348,12 +363,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("user");
}
var userId = user.Id;
-// TODO: var query = from userRole in UserRoles
- var query = from userRole in user.Roles
+ var query = from userRole in UserRoles
join role in Roles on userRole.RoleId equals role.Id
+ where userRole.UserId.Equals(userId)
select role.Name;
- //return await query.ToListAsync();
- return Task.FromResult>(query.ToList());
+ return await query.ToListAsync();
}
///
@@ -371,18 +385,16 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("user");
}
- if (String.IsNullOrWhiteSpace(roleName))
+ if (string.IsNullOrWhiteSpace(roleName))
{
throw new ArgumentException(Resources.ValueCannotBeNullOrEmpty, "roleName");
}
- var role = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper());
+ var role = await Roles.SingleOrDefaultAsync(r => r.Name.ToUpper() == roleName.ToUpper(), cancellationToken);
if (role != null)
{
var userId = user.Id;
var roleId = role.Id;
- return user.Roles.Any(ur => ur.RoleId.Equals(roleId));
- //return await UserRoles.AnyAsync(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId));
- //return UserRoles.Any(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId));
+ return await UserRoles.AnyAsync(ur => ur.RoleId.Equals(roleId) && ur.UserId.Equals(userId));
}
return false;
}
@@ -408,7 +420,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework
private DbSet> UserRoles { get { return Context.Set>(); } }
private DbSet> UserLogins { get { return Context.Set>(); } }
- public async Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -416,10 +428,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("user");
}
- return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync();
+ return await UserClaims.Where(uc => uc.UserId.Equals(user.Id)).Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToListAsync(cancellationToken);
}
- public async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -432,11 +444,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
foreach (var claim in claims)
{
- await UserClaims.AddAsync(new IdentityUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value });
+ await UserClaims.AddAsync(new IdentityUserClaim { UserId = user.Id, ClaimType = claim.Type, ClaimValue = claim.Value }, cancellationToken);
}
}
- public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -452,15 +464,15 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("newClaim");
}
- var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync();
- foreach(var matchedClaim in matchedClaims)
+ var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken);
+ foreach (var matchedClaim in matchedClaims)
{
matchedClaim.ClaimValue = newClaim.Value;
matchedClaim.ClaimType = newClaim.Type;
}
}
- public async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
+ public async virtual Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
if (user == null)
@@ -471,8 +483,9 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("claims");
}
- foreach (var claim in claims) {
- var matchedClaims = await UserClaims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync();
+ foreach (var claim in claims)
+ {
+ var matchedClaims = await UserClaims.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type).ToListAsync(cancellationToken);
foreach (var c in matchedClaims)
{
UserClaims.Remove(c);
@@ -502,7 +515,6 @@ namespace Microsoft.AspNet.Identity.EntityFramework
};
// TODO: fixup so we don't have to update both
await UserLogins.AddAsync(l);
- user.Logins.Add(l);
}
public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey,
@@ -515,12 +527,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework
throw new ArgumentNullException("user");
}
var userId = user.Id;
- // todo: ensure logins loaded
- var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
+ var entry = await UserLogins.SingleOrDefaultAsync(l => l.UserId.Equals(userId) && l.LoginProvider == loginProvider && l.ProviderKey == providerKey, cancellationToken);
if (entry != null)
{
UserLogins.Remove(entry);
- user.Logins.Remove(entry);
}
}
@@ -532,13 +542,9 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
throw new ArgumentNullException("user");
}
- // todo: ensure logins loaded
- //IList result = user.Logins
- // .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToList();
var userId = user.Id;
-
return await UserLogins.Where(l => l.UserId.Equals(userId))
- .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync();
+ .Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.ProviderDisplayName)).ToListAsync(cancellationToken);
}
public async virtual Task FindByLoginAsync(string loginProvider, string providerKey,
@@ -546,12 +552,11 @@ namespace Microsoft.AspNet.Identity.EntityFramework
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- // todo: ensure logins loaded
var userLogin = await
- UserLogins.FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey);
+ UserLogins.FirstOrDefaultAsync(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey, cancellationToken);
if (userLogin != null)
{
- return await GetUserAggregate(u => u.Id.Equals(userLogin.UserId), cancellationToken);
+ return await Users.FirstOrDefaultAsync(u => u.Id.Equals(userLogin.UserId), cancellationToken);
}
return null;
}
@@ -628,19 +633,40 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(user.Email);
}
+ public virtual Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ return Task.FromResult(user.NormalizedEmail);
+ }
+
+ public virtual Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ user.NormalizedEmail = normalizedEmail;
+ return Task.FromResult(0);
+ }
+
///
/// Find an user by email
///
///
///
///
- public virtual Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken))
+ public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
- return GetUserAggregate(u => u.Email == email, cancellationToken);
- // todo: ToUpper blows up with Null Ref
- //return GetUserAggregate(u => u.Email.ToUpper() == email.ToUpper(), cancellationToken);
+ return Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken);
}
///
@@ -913,5 +939,58 @@ namespace Microsoft.AspNet.Identity.EntityFramework
}
return Task.FromResult(user.TwoFactorEnabled);
}
+
+ ///
+ /// Get all users with given claim
+ ///
+ ///
+ ///
+ ///
+ public async virtual Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (claim == null)
+ {
+ throw new ArgumentNullException("claim");
+ }
+
+ var query = from userclaims in UserClaims
+ join user in Users on userclaims.UserId equals user.Id
+ where userclaims.ClaimValue == claim.Value
+ && userclaims.ClaimType == claim.Type
+ select user;
+
+ return await query.ToListAsync(cancellationToken);
+ }
+
+ ///
+ /// Get all users in given role
+ ///
+ ///
+ ///
+ ///
+ public async virtual Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ThrowIfDisposed();
+ if (String.IsNullOrEmpty(roleName))
+ {
+ throw new ArgumentNullException("role");
+ }
+
+ var role = await Roles.Where(x => x.Name.Equals(roleName)).FirstOrDefaultAsync(cancellationToken);
+
+ if (role != null)
+ {
+ var query = from userrole in UserRoles
+ join user in Users on userrole.UserId equals user.Id
+ where userrole.RoleId.Equals(role.Id)
+ select user;
+
+ return await query.ToListAsync(cancellationToken);
+ }
+ return new List();
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs b/src/Microsoft.AspNet.Identity/BuilderExtensions.cs
index 4049f04ffd..d4d184e759 100644
--- a/src/Microsoft.AspNet.Identity/BuilderExtensions.cs
+++ b/src/Microsoft.AspNet.Identity/BuilderExtensions.cs
@@ -2,15 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+
using Microsoft.AspNet.Identity;
namespace Microsoft.AspNet.Builder
{
///
- /// Startup extensions
+ /// Identity extensions for .
///
public static class BuilderExtensions
{
+ ///
+ /// Enables the ASP.NET identity for the current application.
+ ///
+ /// The instance this method extends.
+ /// The instance this method extends.
public static IApplicationBuilder UseIdentity(this IApplicationBuilder app)
{
if (app == null)
diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs
index 63cd7b03f3..23655ddcff 100644
--- a/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs
+++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityExtensions.cs
@@ -6,15 +6,16 @@ using System.Security.Claims;
namespace System.Security.Principal
{
///
- /// Extensions making it easier to get the user name/user id claims off of an identity
+ /// Claims related extensions for .
///
public static class ClaimsIdentityExtensions
{
///
- /// Return the user name using the UserNameClaimType
+ /// Returns the Name claim value if present otherwise returns null.
///
- ///
- ///
+ /// The instance this method extends.
+ /// The Name claim value, or null if the claim is not present.
+ /// The name claim is identified by .
public static string GetUserName(this IIdentity identity)
{
if (identity == null)
@@ -26,10 +27,11 @@ namespace System.Security.Principal
}
///
- /// Return the user id using the UserIdClaimType
+ /// Returns the User ID claim value if present otherwise returns null.
///
- ///
- ///
+ /// The instance this method extends.
+ /// The User ID claim value, or null if the claim is not present.
+ /// The name claim is identified by .
public static string GetUserId(this IIdentity identity)
{
if (identity == null)
@@ -41,11 +43,11 @@ namespace System.Security.Principal
}
///
- /// Return the claim value for the first claim with the specified type if it exists, null otherwise
+ /// Returns the value for the first claim of the specified type otherwise null the claim is not present.
///
- ///
- ///
- ///
+ /// The instance this method extends.
+ /// The claim type whose first value should be returned.
+ /// The value of the first instance of the specifed claim type, or null if the claim is not present.
public static string FindFirstValue(this ClaimsIdentity identity, string claimType)
{
if (identity == null)
diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs
index 9fc5cb760f..bda8839ac8 100644
--- a/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs
+++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityFactory.cs
@@ -5,19 +5,29 @@ using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
+
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity
{
///
- /// Creates a ClaimsIdentity from a User
+ /// Provides methods to create a claims identity for a given user.
///
- ///
+ /// The type used to represent a user.
+ /// The type used to represent a role.
public class ClaimsIdentityFactory : IClaimsIdentityFactory
where TUser : class
where TRole : class
{
- public ClaimsIdentityFactory(UserManager userManager, RoleManager roleManager,
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to retrieve user information from.
+ /// The to retrieve a user's roles from.
+ /// The configured .
+ public ClaimsIdentityFactory(
+ UserManager userManager,
+ RoleManager roleManager,
IOptions optionsAccessor)
{
if (userManager == null)
@@ -37,19 +47,38 @@ namespace Microsoft.AspNet.Identity
Options = optionsAccessor.Options;
}
+ ///
+ /// Gets the for this factory.
+ ///
+ ///
+ /// The current for this factory instance.
+ ///
public UserManager UserManager { get; private set; }
+
+ ///
+ /// Gets the for this factory.
+ ///
+ ///
+ /// The current for this factory instance.
+ ///
public RoleManager RoleManager { get; private set; }
+
+ ///
+ /// Gets the for this factory.
+ ///
+ ///
+ /// The current for this factory instance.
+ ///
public IdentityOptions Options { get; private set; }
///
- /// CreateAsync a ClaimsIdentity from a user
+ /// Creates a populated for the specified .
///
- ///
- ///
- ///
- ///
- ///
- public virtual async Task CreateAsync(TUser user,
+ /// The user instance to create claims on.
+ /// A to observe while waiting for the tasks to complete.
+ /// A that represents the started task.
+ public virtual async Task CreateAsync(
+ TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (user == null)
diff --git a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs
index 9b8dd3c24f..45e4739bcc 100644
--- a/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs
+++ b/src/Microsoft.AspNet.Identity/ClaimsIdentityOptions.cs
@@ -5,28 +5,41 @@ using System.Security.Claims;
namespace Microsoft.AspNet.Identity
{
+ ///
+ /// Options for ClaimType names.
+ ///
public class ClaimsIdentityOptions
{
- public static readonly string DefaultSecurityStampClaimType = "AspNet.Identity.SecurityStamp";
-
///
- /// Claim type used for role claims
+ /// Gets the ClaimType used for a Role claim.
///
+ ///
+ /// This defaults to .
+ ///
public string RoleClaimType { get; set; } = ClaimTypes.Role;
///
- /// Claim type used for the user name
+ /// Gets the ClaimType used for the user name claim.
///
+ ///
+ /// This defaults to .
+ ///
public string UserNameClaimType { get; set; } = ClaimTypes.Name;
///
- /// Claim type used for the user id
+ /// Gets the ClaimType used for the user identifier claim.
///
+ ///
+ /// This defaults to .
+ ///
public string UserIdClaimType { get; set; } = ClaimTypes.NameIdentifier;
///
- /// Claim type used for the user security stamp
+ /// Gets the ClaimType used for the security stamp claim..
///
- public string SecurityStampClaimType { get; set; } = DefaultSecurityStampClaimType;
+ ///
+ /// This defaults to "AspNet.Identity.SecurityStamp".
+ ///
+ public string SecurityStampClaimType { get; set; } = "AspNet.Identity.SecurityStamp";
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs
index ae7bdfc960..988b569e32 100644
--- a/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs
+++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProvider.cs
@@ -1,51 +1,71 @@
-using Microsoft.AspNet.Security.DataProtection;
-using Microsoft.Framework.OptionsModel;
+// Copyright (c) Microsoft Open Technologies, Inc. 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.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNet.Security.DataProtection;
+using Microsoft.Framework.OptionsModel;
+
namespace Microsoft.AspNet.Identity
{
- public class DataProtectionTokenProviderOptions
- {
- public string Name { get; set; } = "DataProtection";
- public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1);
- }
-
///
- /// Token provider that uses an IDataProtector to generate encrypted tokens based off of the security stamp
+ /// Provides protection and validation of identity tokens.
///
+ /// The type used to represent a user.
public class DataProtectorTokenProvider : IUserTokenProvider where TUser : class
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The system data protection provider.
+ /// The configured .
public DataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions options)
{
- if (options == null || options.Options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
if (dataProtectionProvider == null)
{
throw new ArgumentNullException(nameof(dataProtectionProvider));
}
- Options = options.Options;
+ Options = options?.Options ?? new DataProtectionTokenProviderOptions();
// Use the Name as the purpose which should usually be distinct from others
Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider");
}
- public DataProtectionTokenProviderOptions Options { get; private set; }
- public IDataProtector Protector { get; private set; }
+ ///
+ /// Gets the for this instance.
+ ///
+ ///
+ /// The for this instance.
+ ///
+ protected DataProtectionTokenProviderOptions Options { get; private set; }
+ ///
+ /// Gets the for this instance.
+ ///
+ ///
+ /// The for this instance.
+ ///
+ protected IDataProtector Protector { get; private set; }
+
+ ///
+ /// Gets the name of this instance.
+ ///
+ ///
+ /// The name of this instance.
+ ///
public string Name { get { return Options.Name; } }
///
- /// Generate a protected string for a user
+ /// Generates a protected token for the specified .
///
- ///
- ///
- ///
- ///
+ /// The purpose the token will be used for.
+ /// The to retrieve user properties from.
+ /// The the token will be generated from.
+ /// A to observe while waiting for the tasks to complete.
+ /// A that contains the protected token.
public async Task GenerateAsync(string purpose, UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
@@ -72,13 +92,14 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Return false if the token is not valid
+ /// Validates the protected for the specified and .
///
- ///
- ///
- ///
- ///
- ///
+ /// The purpose the token was be used for.
+ /// The token to validate.
+ /// The to retrieve user properties from.
+ /// The the token was generated for.
+ /// A to observe while waiting for the tasks to complete.
+ /// A that is true if the token is valid, otherwise false.
public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
@@ -128,11 +149,13 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Returns false because tokens are two long to be used for two factor
+ /// Returns a indicating whether a token generated by this instance
+ /// can be used as a Two Factor Authentication token.
///
- ///
- ///
- ///
+ /// The to retrieve user properties from.
+ /// The the token was generated for.
+ /// True if a token generated by this instance can be used as a Two Factor Authentication token, otherwise false.
+ /// This method will always return false for instances of .
public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
@@ -140,12 +163,13 @@ namespace Microsoft.AspNet.Identity
}
///
- /// This provider no-ops by default when asked to notify a user
+ /// Creates a notification task for A based on the supplied .
///
- ///
- ///
- ///
- ///
+ /// The token to generate notifications for..
+ /// The to retrieve user properties from.
+ /// The the token was generated for.
+ /// A to observe while waiting for the tasks to complete.
+ /// A that represents the started task.
public Task NotifyAsync(string token, UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
@@ -153,7 +177,9 @@ namespace Microsoft.AspNet.Identity
}
}
- // Based on Levi's authentication sample
+ ///
+ /// Utility extensions to streams
+ ///
internal static class StreamExtensions
{
internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true);
diff --git a/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs
new file mode 100644
index 0000000000..57d589440a
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/DataProtectionTokenProviderOptions.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Contains options for the .
+ ///
+ public class DataProtectionTokenProviderOptions
+ {
+ ///
+ /// Gets or sets the name of the .
+ ///
+ ///
+ /// The name of the .
+ ///
+ public string Name { get; set; } = "DataProtectorTokenProvider";
+
+ ///
+ /// Gets or sets the amount of time a generated token remains valid.
+ ///
+ ///
+ /// The amount of time a generated token remains valid.
+ ///
+ public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
similarity index 61%
rename from src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs
rename to src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
index b8d1bfcbf2..159f222ffa 100644
--- a/src/Microsoft.AspNet.Identity/IUserNameNormalizer.cs
+++ b/src/Microsoft.AspNet.Identity/ILookupNormalizer.cs
@@ -4,15 +4,15 @@
namespace Microsoft.AspNet.Identity
{
///
- /// Used to normalize a user name
+ /// Used to normalize keys for consistent lookups
///
- public interface IUserNameNormalizer
+ public interface ILookupNormalizer
{
///
- /// Returns the normalized user name
+ /// Returns the normalized key
///
- ///
+ ///
///
- string Normalize(string userName);
+ string Normalize(string key);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IRoleStore.cs b/src/Microsoft.AspNet.Identity/IRoleStore.cs
index 4c4fc16f46..405a47ce31 100644
--- a/src/Microsoft.AspNet.Identity/IRoleStore.cs
+++ b/src/Microsoft.AspNet.Identity/IRoleStore.cs
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
+ Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
///
/// Update a role
@@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
+ Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
///
/// DeleteAsync a role
@@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
+ Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken));
///
/// Returns a role's id
@@ -63,6 +63,26 @@ namespace Microsoft.AspNet.Identity
Task SetRoleNameAsync(TRole role, string roleName,
CancellationToken cancellationToken = default(CancellationToken));
+ ///
+ /// Get a role's normalized name
+ ///
+ ///
+ ///
+ ///
+ Task GetNormalizedRoleNameAsync(TRole role,
+ CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Set a role's normalized name
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SetNormalizedRoleNameAsync(TRole role, string normalizedName,
+ CancellationToken cancellationToken = default(CancellationToken));
+
+
///
/// Finds a role by id
///
@@ -72,11 +92,11 @@ namespace Microsoft.AspNet.Identity
Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken));
///
- /// Find a role by name
+ /// Find a role by normalized name
///
- ///
+ ///
///
///
- Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken));
+ Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserClaimStore.cs b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs
index eb7d0d559f..825c289b0e 100644
--- a/src/Microsoft.AspNet.Identity/IUserClaimStore.cs
+++ b/src/Microsoft.AspNet.Identity/IUserClaimStore.cs
@@ -51,5 +51,13 @@ namespace Microsoft.AspNet.Identity
///
Task RemoveClaimsAsync(TUser user, IEnumerable claims,
CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Get users having a specific claim
+ ///
+ /// Claim to look up
+ ///
+ ///
+ Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
index 6569548ffe..816f9383eb 100644
--- a/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
+++ b/src/Microsoft.AspNet.Identity/IUserEmailStore.cs
@@ -51,9 +51,28 @@ namespace Microsoft.AspNet.Identity
///
/// Returns the user associated with this email
///
- ///
+ ///
///
///
- Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken));
+ Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Returns the normalized email
+ ///
+ ///
+ ///
+ ///
+ Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Set the normalized email
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SetNormalizedEmailAsync(TUser user, string normalizedEmail,
+ CancellationToken cancellationToken = default(CancellationToken));
+
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserRoleStore.cs b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs
index e74ca87fe6..690eeeb45a 100644
--- a/src/Microsoft.AspNet.Identity/IUserRoleStore.cs
+++ b/src/Microsoft.AspNet.Identity/IUserRoleStore.cs
@@ -51,5 +51,13 @@ namespace Microsoft.AspNet.Identity
///
Task IsInRoleAsync(TUser user, string roleName,
CancellationToken cancellationToken = default(CancellationToken));
+
+ ///
+ /// Returns all users in given role
+ ///
+ ///
+ ///
+ ///
+ Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IUserStore.cs b/src/Microsoft.AspNet.Identity/IUserStore.cs
index 93a8fcae36..d6cd313733 100644
--- a/src/Microsoft.AspNet.Identity/IUserStore.cs
+++ b/src/Microsoft.AspNet.Identity/IUserStore.cs
@@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
+ Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
///
/// UpdateAsync a user
@@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
+ Task UpdateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
///
/// DeleteAsync a user
@@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
+ Task DeleteAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
///
/// Finds a user
diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
index 8c9d51332a..4f78d0731d 100644
--- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
@@ -35,6 +35,12 @@ namespace Microsoft.AspNet.Identity
return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T));
}
+ public IdentityBuilder AddErrorDescriber() where TDescriber : IdentityErrorDescriber
+ {
+ Services.AddScoped();
+ return this;
+ }
+
public IdentityBuilder AddPasswordValidator() where T : class
{
return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T));
@@ -76,5 +82,15 @@ namespace Microsoft.AspNet.Identity
.AddTokenProvider(typeof(PhoneNumberTokenProvider<>).MakeGenericType(UserType))
.AddTokenProvider(typeof(EmailTokenProvider<>).MakeGenericType(UserType));
}
+
+ public IdentityBuilder AddUserManager() where TUserManager : class
+ {
+ return AddScoped(typeof(UserManager<>).MakeGenericType(UserType), typeof(TUserManager));
+ }
+
+ public IdentityBuilder AddRoleManager() where TRoleManager : class
+ {
+ return AddScoped(typeof(RoleManager<>).MakeGenericType(RoleType), typeof(TRoleManager));
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/SignInStatus.cs b/src/Microsoft.AspNet.Identity/IdentityError.cs
similarity index 64%
rename from src/Microsoft.AspNet.Identity/SignInStatus.cs
rename to src/Microsoft.AspNet.Identity/IdentityError.cs
index 3247a7dd39..0db1958a8f 100644
--- a/src/Microsoft.AspNet.Identity/SignInStatus.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityError.cs
@@ -3,12 +3,9 @@
namespace Microsoft.AspNet.Identity
{
- public enum SignInStatus
+ public class IdentityError
{
- Success,
- LockedOut,
- RequiresVerification,
- NotAllowed,
- Failure
+ public string Code { get; set; }
+ public string Description { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs
new file mode 100644
index 0000000000..39d1677713
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/IdentityErrorDescriber.cs
@@ -0,0 +1,189 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Identity
+{
+ public class IdentityErrorDescriber
+ {
+ public static IdentityErrorDescriber Default = new IdentityErrorDescriber();
+
+ public virtual IdentityError DefaultError()
+ {
+ return new IdentityError
+ {
+ Code = nameof(DefaultError),
+ Description = Resources.DefaultError
+ };
+ }
+ public virtual IdentityError ConcurrencyFailure()
+ {
+ return new IdentityError
+ {
+ Code = nameof(ConcurrencyFailure),
+ Description = Resources.ConcurrencyFailure
+ };
+ }
+
+ public virtual IdentityError PasswordMismatch()
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordMismatch),
+ Description = Resources.PasswordMismatch
+ };
+ }
+
+ public virtual IdentityError InvalidToken()
+ {
+ return new IdentityError
+ {
+ Code = nameof(InvalidToken),
+ Description = Resources.InvalidToken
+ };
+ }
+
+ public virtual IdentityError LoginAlreadyAssociated()
+ {
+ return new IdentityError
+ {
+ Code = nameof(LoginAlreadyAssociated),
+ Description = Resources.LoginAlreadyAssociated
+ };
+ }
+
+ public virtual IdentityError InvalidUserName(string name)
+ {
+ return new IdentityError
+ {
+ Code = nameof(InvalidUserName),
+ Description = Resources.FormatInvalidUserName(name)
+ };
+ }
+
+ public virtual IdentityError InvalidEmail(string email)
+ {
+ return new IdentityError
+ {
+ Code = nameof(InvalidEmail),
+ Description = Resources.FormatInvalidEmail(email)
+ };
+ }
+
+ public virtual IdentityError DuplicateUserName(string name)
+ {
+ return new IdentityError
+ {
+ Code = nameof(DuplicateUserName),
+ Description = Resources.FormatDuplicateUserName(name)
+ };
+ }
+
+ public virtual IdentityError DuplicateEmail(string email)
+ {
+ return new IdentityError
+ {
+ Code = nameof(DuplicateEmail),
+ Description = Resources.FormatDuplicateEmail(email)
+ };
+ }
+
+ public virtual IdentityError InvalidRoleName(string name)
+ {
+ return new IdentityError
+ {
+ Code = nameof(InvalidRoleName),
+ Description = Resources.FormatInvalidRoleName(name)
+ };
+ }
+
+ public virtual IdentityError DuplicateRoleName(string name)
+ {
+ return new IdentityError
+ {
+ Code = nameof(DuplicateRoleName),
+ Description = Resources.FormatDuplicateRoleName(name)
+ };
+ }
+
+ public virtual IdentityError UserAlreadyHasPassword()
+ {
+ return new IdentityError
+ {
+ Code = nameof(UserAlreadyHasPassword),
+ Description = Resources.UserAlreadyHasPassword
+ };
+ }
+
+ public virtual IdentityError UserLockoutNotEnabled()
+ {
+ return new IdentityError
+ {
+ Code = nameof(UserLockoutNotEnabled),
+ Description = Resources.UserLockoutNotEnabled
+ };
+ }
+
+ public virtual IdentityError UserAlreadyInRole(string role)
+ {
+ return new IdentityError
+ {
+ Code = nameof(UserAlreadyInRole),
+ Description = Resources.FormatUserAlreadyInRole(role)
+ };
+ }
+
+ public virtual IdentityError UserNotInRole(string role)
+ {
+ return new IdentityError
+ {
+ Code = nameof(UserNotInRole),
+ Description = Resources.FormatUserNotInRole(role)
+ };
+ }
+
+ public virtual IdentityError PasswordTooShort(int length)
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordTooShort),
+ Description = Resources.FormatPasswordTooShort(length)
+ };
+ }
+
+ public virtual IdentityError PasswordRequiresNonLetterAndDigit()
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordRequiresNonLetterAndDigit),
+ Description = Resources.PasswordRequiresNonLetterAndDigit
+ };
+ }
+
+ public virtual IdentityError PasswordRequiresDigit()
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordRequiresDigit),
+ Description = Resources.PasswordRequiresDigit
+ };
+ }
+
+ public virtual IdentityError PasswordRequiresLower()
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordRequiresLower),
+ Description = Resources.PasswordRequiresLower
+ };
+ }
+
+ public virtual IdentityError PasswordRequiresUpper()
+ {
+ return new IdentityError
+ {
+ Code = nameof(PasswordRequiresUpper),
+ Description = Resources.PasswordRequiresUpper
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs
index dbe60c4756..043c605fef 100644
--- a/src/Microsoft.AspNet.Identity/IdentityResult.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Identity
{
@@ -11,45 +12,19 @@ namespace Microsoft.AspNet.Identity
///
public class IdentityResult
{
- private static readonly IdentityResult _success = new IdentityResult(true);
+ private static readonly IdentityResult _success = new IdentityResult { Succeeded = true };
- ///
- /// Failure constructor that takes error messages
- ///
- ///
- public IdentityResult(params string[] errors) : this((IEnumerable)errors)
- {
- }
-
- ///
- /// Failure constructor that takes error messages
- ///
- ///
- public IdentityResult(IEnumerable errors)
- {
- if (errors == null || !errors.Any())
- {
- errors = new[] { Resources.DefaultError };
- }
- Succeeded = false;
- Errors = errors;
- }
-
- protected IdentityResult(bool success)
- {
- Succeeded = success;
- Errors = new string[0];
- }
+ private List _errors = new List();
///
/// True if the operation was successful
///
- public bool Succeeded { get; private set; }
+ public bool Succeeded { get; protected set; }
///
/// List of errors
///
- public IEnumerable Errors { get; private set; }
+ public IEnumerable Errors { get { return _errors; } }
///
/// Static success result
@@ -65,9 +40,32 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public static IdentityResult Failed(params string[] errors)
+ public static IdentityResult Failed(params IdentityError[] errors)
{
- return new IdentityResult(errors);
+ var result = new IdentityResult { Succeeded = false };
+ if (errors != null)
+ {
+ result._errors.AddRange(errors);
+ }
+ return result;
+ }
+
+ ///
+ /// Log Identity result
+ ///
+ ///
+ ///
+ public virtual void Log(ILogger logger, string message)
+ {
+ // TODO: Take logging level as a parameter
+ if (Succeeded)
+ {
+ logger.WriteInformation(Resources.FormatLogIdentityResultSuccess(message));
+ }
+ else
+ {
+ logger.WriteWarning(Resources.FormatLogIdentityResultFailure(message, string.Join(",", Errors.Select(x => x.Code).ToList())));
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityRole.cs b/src/Microsoft.AspNet.Identity/IdentityRole.cs
index e9206c92ad..0127acb84d 100644
--- a/src/Microsoft.AspNet.Identity/IdentityRole.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityRole.cs
@@ -65,5 +65,11 @@ namespace Microsoft.AspNet.Identity
/// Role name
///
public virtual string Name { get; set; }
+ public virtual string NormalizedName { get; set; }
+
+ ///
+ /// A random value that should change whenever a role is persisted to the store
+ ///
+ public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
index 86fd009b9e..6ccc4ce6c5 100644
--- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
@@ -57,8 +57,10 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAdd(describe.Transient, UserValidator>());
services.TryAdd(describe.Transient, PasswordValidator>());
services.TryAdd(describe.Transient, PasswordHasher>());
- services.TryAdd(describe.Transient());
+ services.TryAdd(describe.Transient());
services.TryAdd(describe.Transient, RoleValidator>());
+ // No interface for the error describer so we can add errors without rev'ing the interface
+ services.TryAdd(describe.Transient());
services.TryAdd(describe.Scoped>());
services.TryAdd(describe.Scoped, ClaimsIdentityFactory>());
services.TryAdd(describe.Scoped, UserManager>());
diff --git a/src/Microsoft.AspNet.Identity/IdentityUser.cs b/src/Microsoft.AspNet.Identity/IdentityUser.cs
index 210791c26f..4392bf7db4 100644
--- a/src/Microsoft.AspNet.Identity/IdentityUser.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityUser.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
namespace Microsoft.AspNet.Identity
{
@@ -37,6 +36,8 @@ namespace Microsoft.AspNet.Identity
///
public virtual string Email { get; set; }
+ public virtual string NormalizedEmail { get; set; }
+
///
/// True if the email is confirmed, default is false
///
@@ -52,6 +53,11 @@ namespace Microsoft.AspNet.Identity
///
public virtual string SecurityStamp { get; set; }
+ ///
+ /// A random value that should change whenever a user is persisted to the store
+ ///
+ public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
+
///
/// PhoneNumber for the user
///
@@ -81,20 +87,5 @@ namespace Microsoft.AspNet.Identity
/// Used to record failures for the purposes of lockout
///
public virtual int AccessFailedCount { get; set; }
-
- ///
- /// Roles for the user
- ///
- public virtual ICollection> Roles { get; } = new List>();
-
- ///
- /// Claims for the user
- ///
- public virtual ICollection> Claims { get; } = new List>();
-
- ///
- /// Associated logins for the user
- ///
- public virtual ICollection> Logins { get; } = new List>();
}
}
diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj
index 318f83366a..10681e7ef3 100644
--- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj
+++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj
@@ -1,4 +1,4 @@
-
+
14.0
@@ -13,5 +13,13 @@
2.0
+
+ False
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/PasswordHasher.cs b/src/Microsoft.AspNet.Identity/PasswordHasher.cs
index 09557ae983..611ce4a04f 100644
--- a/src/Microsoft.AspNet.Identity/PasswordHasher.cs
+++ b/src/Microsoft.AspNet.Identity/PasswordHasher.cs
@@ -37,14 +37,11 @@ namespace Microsoft.AspNet.Identity
/// Constructs a PasswordHasher using the specified options
///
///
- public PasswordHasher(IOptions options)
+ public PasswordHasher(IOptions optionsAccessor = null)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ var options = optionsAccessor?.Options ?? new PasswordHasherOptions();
- _compatibilityMode = options.Options.CompatibilityMode;
+ _compatibilityMode = options.CompatibilityMode;
switch (_compatibilityMode)
{
case PasswordHasherCompatibilityMode.IdentityV2:
@@ -52,7 +49,7 @@ namespace Microsoft.AspNet.Identity
break;
case PasswordHasherCompatibilityMode.IdentityV3:
- _iterCount = options.Options.IterationCount;
+ _iterCount = options.IterationCount;
if (_iterCount < 1)
{
throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount);
@@ -63,7 +60,7 @@ namespace Microsoft.AspNet.Identity
throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode);
}
- _rng = options.Options.Rng;
+ _rng = options.Rng;
}
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
diff --git a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
index e441a7bee0..a52e476f15 100644
--- a/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
+++ b/src/Microsoft.AspNet.Identity/PasswordHasherOptions.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Cryptography;
-using Microsoft.AspNet.Security.DataProtection;
namespace Microsoft.AspNet.Identity
{
diff --git a/src/Microsoft.AspNet.Identity/PasswordValidator.cs b/src/Microsoft.AspNet.Identity/PasswordValidator.cs
index 2a8bcf730f..1726e26694 100644
--- a/src/Microsoft.AspNet.Identity/PasswordValidator.cs
+++ b/src/Microsoft.AspNet.Identity/PasswordValidator.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity
///
public class PasswordValidator : IPasswordValidator where TUser : class
{
+ public PasswordValidator(IdentityErrorDescriber errors = null)
+ {
+ Describer = errors ?? new IdentityErrorDescriber();
+ }
+
+ public IdentityErrorDescriber Describer { get; private set; }
+
///
/// Ensures that the password is of the required length and meets the configured requirements
///
@@ -33,33 +39,32 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("manager");
}
- var errors = new List();
+ var errors = new List();
var options = manager.Options.Password;
if (string.IsNullOrWhiteSpace(password) || password.Length < options.RequiredLength)
{
- errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PasswordTooShort,
- options.RequiredLength));
+ errors.Add(Describer.PasswordTooShort(options.RequiredLength));
}
if (options.RequireNonLetterOrDigit && password.All(IsLetterOrDigit))
{
- errors.Add(Resources.PasswordRequireNonLetterOrDigit);
+ errors.Add(Describer.PasswordRequiresNonLetterAndDigit());
}
if (options.RequireDigit && !password.Any(IsDigit))
{
- errors.Add(Resources.PasswordRequireDigit);
+ errors.Add(Describer.PasswordRequiresDigit());
}
if (options.RequireLowercase && !password.Any(IsLower))
{
- errors.Add(Resources.PasswordRequireLower);
+ errors.Add(Describer.PasswordRequiresLower());
}
if (options.RequireUppercase && !password.Any(IsUpper))
{
- errors.Add(Resources.PasswordRequireUpper);
+ errors.Add(Describer.PasswordRequiresUpper());
}
return
Task.FromResult(errors.Count == 0
? IdentityResult.Success
- : IdentityResult.Failed(String.Join(" ", errors)));
+ : IdentityResult.Failed(errors.ToArray()));
}
///
diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
index 9c91fd4bd5..2aa5369096 100644
--- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
@@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Identity
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Identity.Resources", typeof(Resources).GetTypeInfo().Assembly);
+ ///
+ /// Optimistic concurrency failure, object has been modified.
+ ///
+ internal static string ConcurrencyFailure
+ {
+ get { return GetString("ConcurrencyFailure"); }
+ }
+
+ ///
+ /// Optimistic concurrency failure, object has been modified.
+ ///
+ internal static string FormatConcurrencyFailure()
+ {
+ return GetString("ConcurrencyFailure");
+ }
+
///
/// Your security code is: {0}
///
@@ -43,7 +59,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Security Code
+ /// Security code
///
internal static string DefaultEmailTokenProviderSubject
{
@@ -51,7 +67,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Security Code
+ /// Security code
///
internal static string FormatDefaultEmailTokenProviderSubject()
{
@@ -107,7 +123,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// DefaultTokenProvider
+ /// Default Token Provider
///
internal static string DefaultTokenProvider
{
@@ -115,7 +131,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// DefaultTokenProvider
+ /// Default Token Provider
///
internal static string FormatDefaultTokenProvider()
{
@@ -139,35 +155,35 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Name {0} is already taken.
+ /// Role name '{0}' is already taken.
///
- internal static string DuplicateName
+ internal static string DuplicateRoleName
{
- get { return GetString("DuplicateName"); }
+ get { return GetString("DuplicateRoleName"); }
}
///
- /// Name {0} is already taken.
+ /// Role name '{0}' is already taken.
///
- internal static string FormatDuplicateName(object p0)
+ internal static string FormatDuplicateRoleName(object p0)
{
- return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateName"), p0);
+ return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateRoleName"), p0);
}
///
- /// A user with that external login already exists.
+ /// User name '{0}' is already taken.
///
- internal static string ExternalLoginExists
+ internal static string DuplicateUserName
{
- get { return GetString("ExternalLoginExists"); }
+ get { return GetString("DuplicateUserName"); }
}
///
- /// A user with that external login already exists.
+ /// User name '{0}' is already taken.
///
- internal static string FormatExternalLoginExists()
+ internal static string FormatDuplicateUserName(object p0)
{
- return GetString("ExternalLoginExists");
+ return string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUserName"), p0);
}
///
@@ -218,6 +234,22 @@ namespace Microsoft.AspNet.Identity
return GetString("InvalidPasswordHasherIterationCount");
}
+ ///
+ /// Role name '{0}' is invalid.
+ ///
+ internal static string InvalidRoleName
+ {
+ get { return GetString("InvalidRoleName"); }
+ }
+
+ ///
+ /// Role name '{0}' is invalid.
+ ///
+ internal static string FormatInvalidRoleName(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("InvalidRoleName"), p0);
+ }
+
///
/// Invalid token.
///
@@ -235,7 +267,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User name {0} is invalid, can only contain letters or digits.
+ /// User name '{0}' is invalid, can only contain letters or digits.
///
internal static string InvalidUserName
{
@@ -243,7 +275,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User name {0} is invalid, can only contain letters or digits.
+ /// User name '{0}' is invalid, can only contain letters or digits.
///
internal static string FormatInvalidUserName(object p0)
{
@@ -251,19 +283,19 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Lockout is not enabled for this user.
+ /// A user with this login already exists.
///
- internal static string LockoutNotEnabled
+ internal static string LoginAlreadyAssociated
{
- get { return GetString("LockoutNotEnabled"); }
+ get { return GetString("LoginAlreadyAssociated"); }
}
///
- /// Lockout is not enabled for this user.
+ /// A user with this login already exists.
///
- internal static string FormatLockoutNotEnabled()
+ internal static string FormatLoginAlreadyAssociated()
{
- return GetString("LockoutNotEnabled");
+ return GetString("LoginAlreadyAssociated");
}
///
@@ -317,65 +349,65 @@ namespace Microsoft.AspNet.Identity
///
/// Passwords must have at least one digit ('0'-'9').
///
- internal static string PasswordRequireDigit
+ internal static string PasswordRequiresDigit
{
- get { return GetString("PasswordRequireDigit"); }
+ get { return GetString("PasswordRequiresDigit"); }
}
///
/// Passwords must have at least one digit ('0'-'9').
///
- internal static string FormatPasswordRequireDigit()
+ internal static string FormatPasswordRequiresDigit()
{
- return GetString("PasswordRequireDigit");
+ return GetString("PasswordRequiresDigit");
}
///
/// Passwords must have at least one lowercase ('a'-'z').
///
- internal static string PasswordRequireLower
+ internal static string PasswordRequiresLower
{
- get { return GetString("PasswordRequireLower"); }
+ get { return GetString("PasswordRequiresLower"); }
}
///
/// Passwords must have at least one lowercase ('a'-'z').
///
- internal static string FormatPasswordRequireLower()
+ internal static string FormatPasswordRequiresLower()
{
- return GetString("PasswordRequireLower");
+ return GetString("PasswordRequiresLower");
}
///
/// Passwords must have at least one non letter and non digit character.
///
- internal static string PasswordRequireNonLetterOrDigit
+ internal static string PasswordRequiresNonLetterAndDigit
{
- get { return GetString("PasswordRequireNonLetterOrDigit"); }
+ get { return GetString("PasswordRequiresNonLetterAndDigit"); }
}
///
/// Passwords must have at least one non letter and non digit character.
///
- internal static string FormatPasswordRequireNonLetterOrDigit()
+ internal static string FormatPasswordRequiresNonLetterAndDigit()
{
- return GetString("PasswordRequireNonLetterOrDigit");
+ return GetString("PasswordRequiresNonLetterAndDigit");
}
///
/// Passwords must have at least one uppercase ('A'-'Z').
///
- internal static string PasswordRequireUpper
+ internal static string PasswordRequiresUpper
{
- get { return GetString("PasswordRequireUpper"); }
+ get { return GetString("PasswordRequiresUpper"); }
}
///
/// Passwords must have at least one uppercase ('A'-'Z').
///
- internal static string FormatPasswordRequireUpper()
+ internal static string FormatPasswordRequiresUpper()
{
- return GetString("PasswordRequireUpper");
+ return GetString("PasswordRequiresUpper");
}
///
@@ -394,22 +426,6 @@ namespace Microsoft.AspNet.Identity
return string.Format(CultureInfo.CurrentCulture, GetString("PasswordTooShort"), p0);
}
- ///
- /// {0} cannot be null or empty.
- ///
- internal static string PropertyTooShort
- {
- get { return GetString("PropertyTooShort"); }
- }
-
- ///
- /// {0} cannot be null or empty.
- ///
- internal static string FormatPropertyTooShort(object p0)
- {
- return string.Format(CultureInfo.CurrentCulture, GetString("PropertyTooShort"), p0);
- }
-
///
/// Role {0} does not exist.
///
@@ -651,7 +667,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User already in role.
+ /// User already in role '{0}'.
///
internal static string UserAlreadyInRole
{
@@ -659,11 +675,43 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User already in role.
+ /// User already in role '{0}'.
///
- internal static string FormatUserAlreadyInRole()
+ internal static string FormatUserAlreadyInRole(object p0)
{
- return GetString("UserAlreadyInRole");
+ return string.Format(CultureInfo.CurrentCulture, GetString("UserAlreadyInRole"), p0);
+ }
+
+ ///
+ /// User is locked out.
+ ///
+ internal static string UserLockedOut
+ {
+ get { return GetString("UserLockedOut"); }
+ }
+
+ ///
+ /// User is locked out.
+ ///
+ internal static string FormatUserLockedOut()
+ {
+ return GetString("UserLockedOut");
+ }
+
+ ///
+ /// Lockout is not enabled for this user.
+ ///
+ internal static string UserLockoutNotEnabled
+ {
+ get { return GetString("UserLockoutNotEnabled"); }
+ }
+
+ ///
+ /// Lockout is not enabled for this user.
+ ///
+ internal static string FormatUserLockoutNotEnabled()
+ {
+ return GetString("UserLockoutNotEnabled");
}
///
@@ -683,7 +731,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User is not in role.
+ /// User is not in role '{0}'.
///
internal static string UserNotInRole
{
@@ -691,11 +739,91 @@ namespace Microsoft.AspNet.Identity
}
///
- /// User is not in role.
+ /// User is not in role '{0}'.
///
- internal static string FormatUserNotInRole()
+ internal static string FormatUserNotInRole(object p0)
{
- return GetString("UserNotInRole");
+ return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0);
+ }
+
+ ///
+ /// {0} : Failed : {1}
+ ///
+ internal static string LogIdentityResultFailure
+ {
+ get { return GetString("LogIdentityResultFailure"); }
+ }
+
+ ///
+ /// {0} : Failed : {1}
+ ///
+ internal static string FormatLogIdentityResultFailure(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultFailure"), p0, p1);
+ }
+
+ ///
+ /// {0} : Success
+ ///
+ internal static string LogIdentityResultSuccess
+ {
+ get { return GetString("LogIdentityResultSuccess"); }
+ }
+
+ ///
+ /// {0} : Success
+ ///
+ internal static string FormatLogIdentityResultSuccess(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultSuccess"), p0);
+ }
+
+ ///
+ /// {0} : Result : {1}
+ ///
+ internal static string LoggingSigninResult
+ {
+ get { return GetString("LoggingSigninResult"); }
+ }
+
+ ///
+ /// {0} : Result : {1}
+ ///
+ internal static string FormatLoggingSigninResult(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("LoggingSigninResult"), p0, p1);
+ }
+
+ ///
+ /// {0} for user: {1}
+ ///
+ internal static string LoggingResultMessage
+ {
+ get { return GetString("LoggingResultMessage"); }
+ }
+
+ ///
+ /// {0} for user: {1}
+ ///
+ internal static string FormatLoggingResultMessage(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessage"), p0, p1);
+ }
+
+ ///
+ /// {0} for role: {1}
+ ///
+ internal static string LoggingResultMessageForRole
+ {
+ get { return GetString("LoggingResultMessageForRole"); }
+ }
+
+ ///
+ /// {0} for role: {1}
+ ///
+ internal static string FormatLoggingResultMessageForRole(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessageForRole"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
diff --git a/src/Microsoft.AspNet.Identity/Properties/debugSettings.json b/src/Microsoft.AspNet.Identity/Properties/debugSettings.json
new file mode 100644
index 0000000000..a44fad34a3
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/Properties/debugSettings.json
@@ -0,0 +1,3 @@
+{
+ "Profiles": []
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx
index 674ae53538..2351b1e7aa 100644
--- a/src/Microsoft.AspNet.Identity/Resources.resx
+++ b/src/Microsoft.AspNet.Identity/Resources.resx
@@ -117,6 +117,10 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Optimistic concurrency failure, object has been modified.
+ Error when optimistic concurrency fails
+
Your security code is: {0}
Default body format for the email
@@ -126,7 +130,7 @@
Default name for the email token provider
- Security Code
+ Security code
Default subject for the email
@@ -142,24 +146,24 @@
Default name for the phone number token provider
- DefaultTokenProvider
+ Default Token Provider
Name of the default token provider
Email '{0}' is already taken.
- error for duplicate emails
+ Error for duplicate emails
-
- Name {0} is already taken.
- error for duplicate usernames
+
+ Role name '{0}' is already taken.
+ Error for duplicate user names
-
- A user with that external login already exists.
- Error when a login already linked
+
+ User name '{0}' is already taken.
+ Error for duplicate user names
Email '{0}' is invalid.
- invalid email
+ Invalid email
The provided PasswordHasherCompatibilityMode is invalid.
@@ -169,17 +173,21 @@
The iteration count must be a positive integer.
Error when the iteration count is < 1.
+
+ Role name '{0}' is invalid.
+ Error for invalid role names
+
Invalid token.
Error when a token is not recognized
- User name {0} is invalid, can only contain letters or digits.
- usernames can only contain letters or digits
+ User name '{0}' is invalid, can only contain letters or digits.
+ User names can only contain letters or digits
-
- Lockout is not enabled for this user.
- error when lockout is not enabled
+
+ A user with this login already exists.
+ Error when a login already linked
No IUserMessageProvider named '{0}' is registered.
@@ -193,19 +201,19 @@
Incorrect password.
Error when a password doesn't match
-
+
Passwords must have at least one digit ('0'-'9').
Error when passwords do not have a digit
-
+
Passwords must have at least one lowercase ('a'-'z').
Error when passwords do not have a lowercase letter
-
+
Passwords must have at least one non letter and non digit character.
Error when password does not have enough letter or digit characters
-
+
Passwords must have at least one uppercase ('A'-'Z').
Error when passwords do not have an uppercase letter
@@ -213,80 +221,104 @@
Passwords must be at least {0} characters.
Error message for passwords that are too short
-
- {0} cannot be null or empty.
- error for empty or null usernames
-
Role {0} does not exist.
- error when a role does not exist
+ Error when a role does not exist
Store does not implement IQueryableRoleStore<TRole>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IQueryableUserStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IRoleClaimStore<TRole>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserClaimStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserConfirmationStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserEmailStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserLockoutStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserLoginStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserPasswordStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserPhoneNumberStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserRoleStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserSecurityStampStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
Store does not implement IUserTwoFactorStore<TUser>.
- error when the store does not implement this interface
+ Error when the store does not implement this interface
User already has a password set.
- error when AddPasswordAsync called when a user already has a password
+ Error when AddPasswordAsync called when a user already has a password
- User already in role.
+ User already in role '{0}'.
Error when a user is already in a role
+
+ User is locked out.
+ Error when a user is locked out
+
+
+ Lockout is not enabled for this user.
+ Error when lockout is not enabled
+
User {0} does not exist.
- error when a user does not exist
+ Error when a user does not exist
- User is not in role.
+ User is not in role '{0}'.
Error when a user is not in the role
+
+ {0} : Failed : {1}
+ Logging method execution failure
+
+
+ {0} : Success
+ Logging method execution success
+
+
+ {0} : Result : {1}
+ Logging statement for SignInManager
+
+
+ {0} for user: {1}
+ Message prefix for Identity result
+
+
+ {0} for role: {1}
+ Message prefix for Identity result for role operation
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs
index 6b30e437fc..98b7db3990 100644
--- a/src/Microsoft.AspNet.Identity/RoleManager.cs
+++ b/src/Microsoft.AspNet.Identity/RoleManager.cs
@@ -7,7 +7,7 @@ using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Identity
{
@@ -24,13 +24,19 @@ namespace Microsoft.AspNet.Identity
///
/// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods
///
- public RoleManager(IRoleStore store, IEnumerable> roleValidators)
+ public RoleManager(IRoleStore store,
+ IEnumerable> roleValidators = null,
+ ILookupNormalizer keyNormalizer = null,
+ IdentityErrorDescriber errors = null,
+ ILoggerFactory loggerFactory = null)
{
if (store == null)
{
throw new ArgumentNullException("store");
}
Store = store;
+ KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer();
+ ErrorDescriber = errors ?? new IdentityErrorDescriber();
if (roleValidators != null)
{
@@ -39,6 +45,9 @@ namespace Microsoft.AspNet.Identity
RoleValidators.Add(v);
}
}
+
+ loggerFactory = loggerFactory ?? new LoggerFactory();
+ Logger = loggerFactory.Create(nameof(RoleManager));
}
///
@@ -51,6 +60,21 @@ namespace Microsoft.AspNet.Identity
///
public IList> RoleValidators { get; } = new List>();
+ ///
+ /// Used to generate public API error messages
+ ///
+ public IdentityErrorDescriber ErrorDescriber { get; set; }
+
+ ///
+ /// Used to log results
+ ///
+ public ILogger Logger { get; set; }
+
+ ///
+ /// Used to normalize user names, role names, emails for uniqueness
+ ///
+ public ILookupNormalizer KeyNormalizer { get; set; }
+
///
/// Returns an IQueryable of roles if the store is an IQueryableRoleStore
///
@@ -102,7 +126,7 @@ namespace Microsoft.AspNet.Identity
private async Task ValidateRoleInternal(TRole role, CancellationToken cancellationToken)
{
- var errors = new List();
+ var errors = new List();
foreach (var v in RoleValidators)
{
var result = await v.ValidateAsync(this, role, cancellationToken);
@@ -120,7 +144,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task CreateAsync(TRole role,
+ public virtual async Task CreateAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -134,17 +158,31 @@ namespace Microsoft.AspNet.Identity
{
return result;
}
- await Store.CreateAsync(role, cancellationToken);
- return IdentityResult.Success;
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
+ return await LogResultAsync(await Store.CreateAsync(role, cancellationToken), role);
}
+ ///
+ /// Update the user's normalized user name
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task UpdateNormalizedRoleNameAsync(TRole role,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var name = await GetRoleNameAsync(role, cancellationToken);
+ await Store.SetNormalizedRoleNameAsync(role, NormalizeKey(name), cancellationToken);
+ }
+
+
///
/// UpdateAsync an existing role
///
///
///
///
- public virtual async Task UpdateAsync(TRole role,
+ public virtual async Task UpdateAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -153,13 +191,19 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("role");
}
+ return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role);
+ }
+
+ private async Task UpdateRoleAsync(TRole role,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
var result = await ValidateRoleInternal(role, cancellationToken);
if (!result.Succeeded)
{
return result;
}
- await Store.UpdateAsync(role, cancellationToken);
- return IdentityResult.Success;
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
+ return await Store.UpdateAsync(role, cancellationToken);
}
///
@@ -168,7 +212,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task DeleteAsync(TRole role,
+ public virtual async Task DeleteAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -176,9 +220,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("role");
}
-
- await Store.DeleteAsync(role, cancellationToken);
- return IdentityResult.Success;
+ return await LogResultAsync(await Store.DeleteAsync(role, cancellationToken), role);
}
///
@@ -187,7 +229,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task RoleExistsAsync(string roleName,
+ public virtual async Task RoleExistsAsync(string roleName,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -196,16 +238,27 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("roleName");
}
- return await FindByNameAsync(roleName, cancellationToken) != null;
+ return await FindByNameAsync(NormalizeKey(roleName), cancellationToken) != null;
}
+ ///
+ /// Normalize a key (role name) for uniqueness comparisons
+ ///
+ ///
+ ///
+ public virtual string NormalizeKey(string key)
+ {
+ return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
+ }
+
+
///
/// FindByLoginAsync a role by id
///
///
///
///
- public virtual async Task FindByIdAsync(string roleId,
+ public virtual async Task FindByIdAsync(string roleId,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -218,7 +271,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task GetRoleNameAsync(TRole role,
+ public virtual async Task GetRoleNameAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -232,12 +285,13 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task SetRoleNameAsync(TRole role, string name,
+ public virtual async Task SetRoleNameAsync(TRole role, string name,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
await Store.SetRoleNameAsync(role, name, cancellationToken);
- return IdentityResult.Success;
+ await UpdateNormalizedRoleNameAsync(role, cancellationToken);
+ return await LogResultAsync(IdentityResult.Success, role);
}
///
@@ -246,7 +300,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task GetRoleIdAsync(TRole role,
+ public virtual async Task GetRoleIdAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -259,7 +313,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task FindByNameAsync(string roleName,
+ public virtual async Task FindByNameAsync(string roleName,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -268,7 +322,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("roleName");
}
- return await Store.FindByNameAsync(roleName, cancellationToken);
+ return await Store.FindByNameAsync(NormalizeKey(roleName), cancellationToken);
}
// IRoleClaimStore methods
@@ -289,7 +343,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task AddClaimAsync(TRole role, Claim claim,
+ public virtual async Task AddClaimAsync(TRole role, Claim claim,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -303,7 +357,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("role");
}
await claimStore.AddClaimAsync(role, claim, cancellationToken);
- return await UpdateAsync(role, cancellationToken);
+ return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role);
}
///
@@ -313,7 +367,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task RemoveClaimAsync(TRole role, Claim claim,
+ public virtual async Task RemoveClaimAsync(TRole role, Claim claim,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -323,7 +377,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("role");
}
await claimStore.RemoveClaimAsync(role, claim, cancellationToken);
- return await UpdateAsync(role, cancellationToken);
+ return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role);
}
///
@@ -332,7 +386,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task> GetClaimsAsync(TRole role,
+ public virtual async Task> GetClaimsAsync(TRole role,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -344,6 +398,21 @@ namespace Microsoft.AspNet.Identity
return await claimStore.GetClaimsAsync(role, cancellationToken);
}
+ ///
+ /// Logs the current Identity Result and returns result object
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task LogResultAsync(IdentityResult result,
+ TRole role, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ result.Log(Logger, Resources.FormatLoggingResultMessageForRole(methodName, await GetRoleIdAsync(role)));
+
+ return result;
+ }
+
private void ThrowIfDisposed()
{
if (_disposed)
diff --git a/src/Microsoft.AspNet.Identity/RoleValidator.cs b/src/Microsoft.AspNet.Identity/RoleValidator.cs
index 244b7136f0..fc20865ae9 100644
--- a/src/Microsoft.AspNet.Identity/RoleValidator.cs
+++ b/src/Microsoft.AspNet.Identity/RoleValidator.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +14,13 @@ namespace Microsoft.AspNet.Identity
///
public class RoleValidator : IRoleValidator where TRole : class
{
+ public RoleValidator(IdentityErrorDescriber errors = null)
+ {
+ Describer = errors ?? new IdentityErrorDescriber();
+ }
+
+ private IdentityErrorDescriber Describer { get; set; }
+
///
/// Validates a role before saving
///
@@ -33,7 +39,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("role");
}
- var errors = new List();
+ var errors = new List();
await ValidateRoleName(manager, role, errors);
if (errors.Count > 0)
{
@@ -42,13 +48,13 @@ namespace Microsoft.AspNet.Identity
return IdentityResult.Success;
}
- private static async Task ValidateRoleName(RoleManager manager, TRole role,
- ICollection errors)
+ private async Task ValidateRoleName(RoleManager manager, TRole role,
+ ICollection errors)
{
var roleName = await manager.GetRoleNameAsync(role);
if (string.IsNullOrWhiteSpace(roleName))
{
- errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.PropertyTooShort, "Name"));
+ errors.Add(Describer.InvalidRoleName(roleName));
}
else
{
@@ -56,7 +62,7 @@ namespace Microsoft.AspNet.Identity
if (owner != null &&
!string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role)))
{
- errors.Add(String.Format(CultureInfo.CurrentCulture, Resources.DuplicateName, roleName));
+ errors.Add(Describer.DuplicateRoleName(roleName));
}
}
}
diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs
index 846fd81540..d6fd276950 100644
--- a/src/Microsoft.AspNet.Identity/SignInManager.cs
+++ b/src/Microsoft.AspNet.Identity/SignInManager.cs
@@ -8,9 +8,10 @@ using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
-using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity
@@ -21,8 +22,11 @@ namespace Microsoft.AspNet.Identity
///
public class SignInManager where TUser : class
{
- public SignInManager(UserManager userManager, IContextAccessor contextAccessor,
- IClaimsIdentityFactory claimsFactory, IOptions optionsAccessor)
+ public SignInManager(UserManager userManager,
+ IHttpContextAccessor contextAccessor,
+ IClaimsIdentityFactory claimsFactory,
+ IOptions optionsAccessor = null,
+ ILoggerFactory loggerFactory = null)
{
if (userManager == null)
{
@@ -36,20 +40,21 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException(nameof(claimsFactory));
}
- if (optionsAccessor == null || optionsAccessor.Options == null)
- {
- throw new ArgumentNullException(nameof(optionsAccessor));
- }
+
UserManager = userManager;
Context = contextAccessor.Value;
ClaimsFactory = claimsFactory;
- Options = optionsAccessor.Options;
+ Options = optionsAccessor?.Options ?? new IdentityOptions();
+
+ loggerFactory = loggerFactory ?? new LoggerFactory();
+ Logger = loggerFactory.Create(nameof(SignInManager));
}
public UserManager UserManager { get; private set; }
public HttpContext Context { get; private set; }
public IClaimsIdentityFactory ClaimsFactory { get; private set; }
public IdentityOptions Options { get; private set; }
+ public ILogger Logger { get; set; }
// Should this be a func?
public virtual async Task CreateUserIdentityAsync(TUser user,
@@ -63,13 +68,13 @@ namespace Microsoft.AspNet.Identity
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user, cancellationToken)))
{
- return false;
+ return await LogResultAsync(false, user);
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user, cancellationToken)))
{
- return false;
+ return await LogResultAsync(false, user);
}
- return true;
+ return await LogResultAsync(true, user);
}
public virtual async Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null,
@@ -96,15 +101,15 @@ namespace Microsoft.AspNet.Identity
return UserManager.SupportsUserLockout && await UserManager.IsLockedOutAsync(user, token);
}
- private async Task PreSignInCheck(TUser user, CancellationToken token)
+ private async Task PreSignInCheck(TUser user, CancellationToken token)
{
if (!await CanSignInAsync(user, token))
{
- return SignInStatus.NotAllowed;
+ return SignInResult.NotAllowed;
}
if (await IsLockedOut(user, token))
{
- return SignInStatus.LockedOut;
+ return SignInResult.LockedOut;
}
return null;
}
@@ -141,18 +146,26 @@ namespace Microsoft.AspNet.Identity
return null;
}
- public virtual async Task PasswordSignInAsync(TUser user, string password,
+ public virtual async Task PasswordSignInAsync(TUser user, string password,
bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
{
+ if (user == null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
var error = await PreSignInCheck(user, cancellationToken);
if (error != null)
{
- return error.Value;
+ return await LogResultAsync(error, user);
+ }
+ if (await IsLockedOut(user, cancellationToken))
+ {
+ return await LogResultAsync(SignInResult.LockedOut, user);
}
if (await UserManager.CheckPasswordAsync(user, password, cancellationToken))
{
await ResetLockout(user, cancellationToken);
- return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken);
+ return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken), user);
}
if (UserManager.SupportsUserLockout && shouldLockout)
{
@@ -160,19 +173,20 @@ namespace Microsoft.AspNet.Identity
await UserManager.AccessFailedAsync(user, cancellationToken);
if (await UserManager.IsLockedOutAsync(user, cancellationToken))
{
- return SignInStatus.LockedOut;
+
+ return await LogResultAsync(SignInResult.LockedOut, user);
}
}
- return SignInStatus.Failure;
+ return await LogResultAsync(SignInResult.Failed, user);
}
- public virtual async Task PasswordSignInAsync(string userName, string password,
+ public virtual async Task PasswordSignInAsync(string userName, string password,
bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken))
{
var user = await UserManager.FindByNameAsync(userName, cancellationToken);
if (user == null)
{
- return SignInStatus.Failure;
+ return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, shouldLockout, cancellationToken);
}
@@ -207,9 +221,8 @@ namespace Microsoft.AspNet.Identity
return false;
}
var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken);
- // See IdentityConfig.cs to plug in Email/SMS services to actually send the code
await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken);
- return true;
+ return await LogResultAsync(true, user);
}
public async Task IsTwoFactorClientRememberedAsync(TUser user,
@@ -236,23 +249,23 @@ namespace Microsoft.AspNet.Identity
return Task.FromResult(0);
}
- public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent,
+ public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent,
bool rememberClient, CancellationToken cancellationToken = default(CancellationToken))
{
var twoFactorInfo = await RetrieveTwoFactorInfoAsync(cancellationToken);
if (twoFactorInfo == null || twoFactorInfo.UserId == null)
{
- return SignInStatus.Failure;
+ return SignInResult.Failed;
}
var user = await UserManager.FindByIdAsync(twoFactorInfo.UserId, cancellationToken);
if (user == null)
{
- return SignInStatus.Failure;
+ return SignInResult.Failed;
}
var error = await PreSignInCheck(user, cancellationToken);
if (error != null)
{
- return error.Value;
+ return await LogResultAsync(error, user);
}
if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken))
{
@@ -268,11 +281,13 @@ namespace Microsoft.AspNet.Identity
{
await RememberTwoFactorClientAsync(user, cancellationToken);
}
- return SignInStatus.Success;
+ await UserManager.ResetAccessFailedCountAsync(user, cancellationToken);
+ await SignInAsync(user, isPersistent);
+ return await LogResultAsync(SignInResult.Success, user);
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user, cancellationToken);
- return SignInStatus.Failure;
+ return await LogResultAsync(SignInResult.Failed, user);
}
///
@@ -292,20 +307,20 @@ namespace Microsoft.AspNet.Identity
return await UserManager.FindByIdAsync(info.UserId, cancellationToken);
}
- public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent,
+ public async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent,
CancellationToken cancellationToken = default(CancellationToken))
{
var user = await UserManager.FindByLoginAsync(loginProvider, providerKey, cancellationToken);
if (user == null)
{
- return SignInStatus.Failure;
+ return SignInResult.Failed;
}
var error = await PreSignInCheck(user, cancellationToken);
if (error != null)
{
- return error.Value;
+ return await LogResultAsync(error, user);
}
- return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider);
+ return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider), user);
}
private const string LoginProviderKey = "LoginProvider";
@@ -316,7 +331,7 @@ namespace Microsoft.AspNet.Identity
return Context.GetAuthenticationTypes().Where(d => !string.IsNullOrEmpty(d.Caption));
}
- public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null,
+ public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null,
CancellationToken cancellationToken = default(CancellationToken))
{
var auth = await Context.AuthenticateAsync(IdentityOptions.ExternalCookieAuthenticationType);
@@ -358,10 +373,10 @@ namespace Microsoft.AspNet.Identity
return properties;
}
- private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent,
+ private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent,
CancellationToken cancellationToken, string loginProvider = null)
{
- if (UserManager.SupportsUserTwoFactor &&
+ if (UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user, cancellationToken) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user, cancellationToken)).Count > 0)
{
@@ -370,7 +385,7 @@ namespace Microsoft.AspNet.Identity
// Store the userId for use after two factor check
var userId = await UserManager.GetUserIdAsync(user, cancellationToken);
Context.Response.SignIn(StoreTwoFactorInfo(userId, loginProvider));
- return SignInStatus.RequiresVerification;
+ return SignInResult.TwoFactorRequired;
}
}
// Cleanup external cookie
@@ -379,7 +394,7 @@ namespace Microsoft.AspNet.Identity
Context.Response.SignOut(IdentityOptions.ExternalCookieAuthenticationType);
}
await SignInAsync(user, isPersistent, loginProvider, cancellationToken);
- return SignInStatus.Success;
+ return SignInResult.Success;
}
private async Task RetrieveTwoFactorInfoAsync(CancellationToken cancellationToken)
@@ -396,6 +411,35 @@ namespace Microsoft.AspNet.Identity
return null;
}
+ ///
+ /// Log boolean result for user and return result
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async virtual Task LogResultAsync(bool result, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ Logger.WriteInformation(Resources.FormatLoggingSigninResult(Resources.FormatLoggingResultMessage(methodName,
+ await UserManager.GetUserIdAsync(user)), result));
+
+ return result;
+ }
+
+ ///
+ /// Log SignInStatus for user and return SignInStatus
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async virtual Task LogResultAsync(SignInResult status, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ status.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await UserManager.GetUserIdAsync(user)));
+
+ return status;
+ }
+
internal static ClaimsIdentity StoreTwoFactorInfo(string userId, string loginProvider)
{
var identity = new ClaimsIdentity(IdentityOptions.TwoFactorUserIdCookieAuthenticationType);
diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs
new file mode 100644
index 0000000000..7ef69a60bb
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/SignInResult.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Represents the result of an sign in operation
+ ///
+ public class SignInResult
+ {
+ private static readonly SignInResult _success = new SignInResult { Succeeded = true };
+ private static readonly SignInResult _failed = new SignInResult();
+ private static readonly SignInResult _lockedOut = new SignInResult { IsLockedOut = true };
+ private static readonly SignInResult _notAllowed = new SignInResult { IsNotAllowed = true };
+ private static readonly SignInResult _twoFactorRequired = new SignInResult { RequiresTwoFactor = true };
+
+ ///
+ /// True if the operation was successful
+ ///
+ public bool Succeeded { get; protected set; }
+
+ ///
+ /// True if the user is locked out
+ ///
+ public bool IsLockedOut { get; protected set; }
+
+ ///
+ /// True if the user is not allowed to sign in
+ ///
+ public bool IsNotAllowed { get; protected set; }
+
+ ///
+ /// True if the sign in requires two factor
+ ///
+ public bool RequiresTwoFactor { get; protected set; }
+
+ ///
+ /// Static success result
+ ///
+ ///
+ public static SignInResult Success
+ {
+ get { return _success; }
+ }
+
+ ///
+ /// Static failure result
+ ///
+ ///
+ public static SignInResult Failed
+ {
+ get { return _failed; }
+ }
+
+ ///
+ /// Static locked out result
+ ///
+ ///
+ public static SignInResult LockedOut
+ {
+ get { return _lockedOut; }
+ }
+
+ ///
+ /// Static not allowed result
+ ///
+ ///
+ public static SignInResult NotAllowed
+ {
+ get { return _notAllowed; }
+ }
+
+ ///
+ /// Static two factor required result
+ ///
+ ///
+ public static SignInResult TwoFactorRequired
+ {
+ get { return _twoFactorRequired; }
+ }
+
+ ///
+ /// Log result based on properties
+ ///
+ ///
+ ///
+ public virtual void Log(ILogger logger, string message)
+ {
+ if (IsLockedOut)
+ {
+ logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Lockedout"));
+ }
+ else if (IsNotAllowed)
+ {
+ logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "NotAllowed"));
+ }
+ else if (RequiresTwoFactor)
+ {
+ logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "RequiresTwoFactor"));
+ }
+ else if (Succeeded)
+ {
+ logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Succeeded"));
+ }
+ else
+ {
+ logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Failed"));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
similarity index 54%
rename from src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs
rename to src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
index 1564ecd3d4..9bfa9454a3 100644
--- a/src/Microsoft.AspNet.Identity/UpperInvariantUserNameNormalizer.cs
+++ b/src/Microsoft.AspNet.Identity/UpperInvariantLookupNormalizer.cs
@@ -6,22 +6,22 @@ using System;
namespace Microsoft.AspNet.Identity
{
///
- /// Normalizes user names via ToUpperInvariant()
+ /// Normalizes via ToUpperInvariant()
///
- public class UpperInvariantUserNameNormalizer : IUserNameNormalizer
+ public class UpperInvariantLookupNormalizer : ILookupNormalizer
{
///
- /// Normalizes user names via ToUpperInvariant()
+ /// Normalizes via ToUpperInvariant()
///
- ///
+ ///
///
- public string Normalize(string userName)
+ public string Normalize(string key)
{
- if (userName == null)
+ if (key == null)
{
return null;
}
- return userName.Normalize().ToUpperInvariant();
+ return key.Normalize().ToUpperInvariant();
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index 3b18d534fd..fa63e7d5ff 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -9,6 +9,7 @@ using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity
@@ -30,39 +31,39 @@ namespace Microsoft.AspNet.Identity
private IdentityOptions _options;
///
- /// Constructor which takes a service provider and user store
+ /// Constructor
///
///
///
///
- ///
- ///
- ///
- public UserManager(IUserStore store,
- IOptions optionsAccessor,
- IPasswordHasher passwordHasher,
- IEnumerable> userValidators,
- IEnumerable> passwordValidators,
- IUserNameNormalizer userNameNormalizer,
- IEnumerable> tokenProviders,
- IEnumerable msgProviders)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public UserManager(IUserStore store,
+ IOptions optionsAccessor = null,
+ IPasswordHasher passwordHasher = null,
+ IEnumerable> userValidators = null,
+ IEnumerable> passwordValidators = null,
+ ILookupNormalizer keyNormalizer = null,
+ IdentityErrorDescriber errors = null,
+ IEnumerable> tokenProviders = null,
+ IEnumerable msgProviders = null,
+ ILoggerFactory loggerFactory = null)
{
if (store == null)
{
throw new ArgumentNullException(nameof(store));
}
- if (optionsAccessor == null || optionsAccessor.Options == null)
- {
- throw new ArgumentNullException(nameof(optionsAccessor));
- }
- if (passwordHasher == null)
- {
- throw new ArgumentNullException(nameof(passwordHasher));
- }
Store = store;
- Options = optionsAccessor.Options;
- PasswordHasher = passwordHasher;
- UserNameNormalizer = userNameNormalizer;
+ Options = optionsAccessor?.Options ?? new IdentityOptions();
+ PasswordHasher = passwordHasher ?? new PasswordHasher();
+ KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer();
+ ErrorDescriber = errors ?? new IdentityErrorDescriber();
+
if (userValidators != null)
{
foreach (var v in userValidators)
@@ -77,6 +78,10 @@ namespace Microsoft.AspNet.Identity
PasswordValidators.Add(v);
}
}
+
+ loggerFactory = loggerFactory ?? new LoggerFactory();
+ Logger = loggerFactory.Create(nameof(UserManager));
+
if (tokenProviders != null)
{
foreach (var tokenProvider in tokenProviders)
@@ -132,9 +137,19 @@ namespace Microsoft.AspNet.Identity
public IList> PasswordValidators { get; } = new List>();
///
- /// Used to normalize user names for uniqueness
+ /// Used to normalize user names and emails for uniqueness
///
- public IUserNameNormalizer UserNameNormalizer { get; set; }
+ public ILookupNormalizer KeyNormalizer { get; set; }
+
+ ///
+ /// Used to generate public API error messages
+ ///
+ public IdentityErrorDescriber ErrorDescriber { get; set; }
+
+ ///
+ /// Used to log IdentityResult
+ ///
+ public ILogger Logger { get; set; }
public IdentityOptions Options
{
@@ -301,7 +316,7 @@ namespace Microsoft.AspNet.Identity
private async Task ValidateUserInternal(TUser user, CancellationToken cancellationToken)
{
- var errors = new List();
+ var errors = new List();
foreach (var v in UserValidators)
{
var result = await v.ValidateAsync(this, user, cancellationToken);
@@ -315,7 +330,7 @@ namespace Microsoft.AspNet.Identity
private async Task ValidatePasswordInternal(TUser user, string password, CancellationToken cancellationToken)
{
- var errors = new List();
+ var errors = new List();
foreach (var v in PasswordValidators)
{
var result = await v.ValidateAsync(this, user, password, cancellationToken);
@@ -327,6 +342,31 @@ namespace Microsoft.AspNet.Identity
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
}
+ public virtual Task GenerateConcurrencyStampAsync(TUser user,
+ CancellationToken token = default(CancellationToken))
+ {
+ return Task.FromResult(Guid.NewGuid().ToString());
+ }
+
+ ///
+ /// Validate user and update. Called by other UserManager methods
+ ///
+ ///
+ ///
+ ///
+ private async Task UpdateUserAsync(TUser user,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var result = await ValidateUserInternal(user, cancellationToken);
+ if (!result.Succeeded)
+ {
+ return result;
+ }
+ await UpdateNormalizedUserNameAsync(user, cancellationToken);
+ await UpdateNormalizedEmailAsync(user, cancellationToken);
+ return await Store.UpdateAsync(user, cancellationToken);
+ }
+
///
/// Create a user with no password
///
@@ -348,8 +388,8 @@ namespace Microsoft.AspNet.Identity
await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken);
}
await UpdateNormalizedUserNameAsync(user, cancellationToken);
- await Store.CreateAsync(user, cancellationToken);
- return IdentityResult.Success;
+ await UpdateNormalizedEmailAsync(user, cancellationToken);
+ return await LogResultAsync(await Store.CreateAsync(user, cancellationToken), user);
}
///
@@ -366,14 +406,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- var result = await ValidateUserInternal(user, cancellationToken);
- if (!result.Succeeded)
- {
- return result;
- }
- await UpdateNormalizedUserNameAsync(user, cancellationToken);
- await Store.UpdateAsync(user, cancellationToken);
- return IdentityResult.Success;
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -390,8 +423,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- await Store.DeleteAsync(user, cancellationToken);
- return IdentityResult.Success;
+ return await LogResultAsync(await Store.DeleteAsync(user, cancellationToken), user);
}
///
@@ -421,7 +453,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("userName");
}
- userName = NormalizeUserName(userName);
+ userName = NormalizeKey(userName);
return Store.FindByNameAsync(userName, cancellationToken);
}
@@ -456,7 +488,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("password");
}
- var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
+ var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
if (!result.Succeeded)
{
return result;
@@ -465,13 +497,13 @@ namespace Microsoft.AspNet.Identity
}
///
- /// Normalize a user name for uniqueness comparisons
+ /// Normalize a key (user name, email) for uniqueness comparisons
///
///
///
- public virtual string NormalizeUserName(string userName)
+ public virtual string NormalizeKey(string key)
{
- return (UserNameNormalizer == null) ? userName : UserNameNormalizer.Normalize(userName);
+ return (KeyNormalizer == null) ? key : KeyNormalizer.Normalize(key);
}
///
@@ -483,8 +515,8 @@ namespace Microsoft.AspNet.Identity
public virtual async Task UpdateNormalizedUserNameAsync(TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
- var userName = await GetUserNameAsync(user, cancellationToken);
- await Store.SetNormalizedUserNameAsync(user, NormalizeUserName(userName), cancellationToken);
+ var normalizedName = NormalizeKey(await GetUserNameAsync(user, cancellationToken));
+ await Store.SetNormalizedUserNameAsync(user, normalizedName, cancellationToken);
}
///
@@ -520,7 +552,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await UpdateUserName(user, userName, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
private async Task UpdateUserName(TUser user, string userName, CancellationToken cancellationToken)
@@ -558,7 +590,14 @@ namespace Microsoft.AspNet.Identity
{
return false;
}
- return await VerifyPasswordAsync(passwordStore, user, password, cancellationToken);
+ var result = await VerifyPasswordAsync(passwordStore, user, password, cancellationToken);
+ if (result == PasswordVerificationResult.SuccessRehashNeeded)
+ {
+ await UpdatePasswordHash(passwordStore, user, password, cancellationToken, validatePassword: false);
+ await UpdateUserAsync(user, cancellationToken);
+ }
+
+ return await LogResultAsync(result != PasswordVerificationResult.Failed, user);
}
///
@@ -576,7 +615,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- return await passwordStore.HasPasswordAsync(user, cancellationToken);
+ return await LogResultAsync(await passwordStore.HasPasswordAsync(user, cancellationToken), user);
}
///
@@ -598,14 +637,14 @@ namespace Microsoft.AspNet.Identity
var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken);
if (hash != null)
{
- return new IdentityResult(Resources.UserAlreadyHasPassword);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()), user);
}
- var result = await UpdatePasswordInternal(passwordStore, user, password, cancellationToken);
+ var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken);
if (!result.Succeeded)
{
- return result;
+ return await LogResultAsync(result, user);
}
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -625,16 +664,16 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken))
+ if (await VerifyPasswordAsync(passwordStore, user, currentPassword, cancellationToken) != PasswordVerificationResult.Failed)
{
- var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken);
+ var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken);
if (!result.Succeeded)
{
- return result;
+ return await LogResultAsync(result, user);
}
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
- return IdentityResult.Failed(Resources.PasswordMismatch);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.PasswordMismatch()), user);
}
///
@@ -652,21 +691,24 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- await passwordStore.SetPasswordHashAsync(user, null, cancellationToken);
- await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
- internal async Task UpdatePasswordInternal(IUserPasswordStore passwordStore,
- TUser user, string newPassword, CancellationToken cancellationToken)
+ internal async Task UpdatePasswordHash(IUserPasswordStore passwordStore,
+ TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true)
{
- var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
- if (!validate.Succeeded)
+ if (validatePassword)
{
- return validate;
+ var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken);
+ if (!validate.Succeeded)
+ {
+ return validate;
+ }
}
+ var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
await
- passwordStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword), cancellationToken);
+ passwordStore.SetPasswordHashAsync(user, hash, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
return IdentityResult.Success;
}
@@ -679,11 +721,11 @@ namespace Microsoft.AspNet.Identity
///
///
///
- protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user,
+ protected virtual async Task VerifyPasswordAsync(IUserPasswordStore store, TUser user,
string password, CancellationToken cancellationToken = default(CancellationToken))
{
var hash = await store.GetPasswordHashAsync(user, cancellationToken);
- return PasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed;
+ return PasswordHasher.VerifyHashedPassword(user, hash, password);
}
// IUserSecurityStampStore methods
@@ -731,7 +773,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -744,7 +786,10 @@ namespace Microsoft.AspNet.Identity
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
- return await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken);
+ var token = await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken);
+ await LogResultAsync(IdentityResult.Success, user);
+
+ return token;
}
///
@@ -766,15 +811,15 @@ namespace Microsoft.AspNet.Identity
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken))
{
- return IdentityResult.Failed(Resources.InvalidToken);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
}
var passwordStore = GetPasswordStore();
- var result = await UpdatePasswordInternal(passwordStore, user, newPassword, cancellationToken);
+ var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken);
if (!result.Succeeded)
{
- return result;
+ return await LogResultAsync(result, user);
}
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
// Update the security stamp if the store supports it
@@ -851,7 +896,7 @@ namespace Microsoft.AspNet.Identity
}
await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -877,10 +922,10 @@ namespace Microsoft.AspNet.Identity
var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken);
if (existingUser != null)
{
- return IdentityResult.Failed(Resources.ExternalLoginExists);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()), user);
}
await loginStore.AddLoginAsync(user, login, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -956,7 +1001,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await claimStore.AddClaimsAsync(user, claims, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -985,7 +1030,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await claimStore.ReplaceClaimAsync(user, claim, newClaim, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1032,7 +1077,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("claims");
}
await claimStore.RemoveClaimsAsync(user, claims, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1082,10 +1127,10 @@ namespace Microsoft.AspNet.Identity
var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken);
if (userRoles.Contains(role))
{
- return new IdentityResult(Resources.UserAlreadyInRole);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user);
}
await userRoleStore.AddToRoleAsync(user, role, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1113,11 +1158,11 @@ namespace Microsoft.AspNet.Identity
{
if (userRoles.Contains(role))
{
- return new IdentityResult(Resources.UserAlreadyInRole);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user);
}
await userRoleStore.AddToRoleAsync(user, role, cancellationToken);
}
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1138,10 +1183,10 @@ namespace Microsoft.AspNet.Identity
}
if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken))
{
- return new IdentityResult(Resources.UserNotInRole);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user);
}
await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1168,11 +1213,11 @@ namespace Microsoft.AspNet.Identity
{
if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken))
{
- return new IdentityResult(Resources.UserNotInRole);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user);
}
await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken);
}
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1213,10 +1258,10 @@ namespace Microsoft.AspNet.Identity
}
// IUserEmailStore methods
- internal IUserEmailStore GetEmailStore()
+ internal IUserEmailStore GetEmailStore(bool throwOnFail = true)
{
var cast = Store as IUserEmailStore;
- if (cast == null)
+ if (throwOnFail && cast == null)
{
throw new NotSupportedException(Resources.StoreNotIUserEmailStore);
}
@@ -1261,7 +1306,7 @@ namespace Microsoft.AspNet.Identity
await store.SetEmailAsync(user, email, cancellationToken);
await store.SetEmailConfirmedAsync(user, false, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1279,20 +1324,41 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("email");
}
- return store.FindByEmailAsync(email, cancellationToken);
+ return store.FindByEmailAsync(NormalizeKey(email), cancellationToken);
}
+ ///
+ /// Update the user's normalized email
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task UpdateNormalizedEmailAsync(TUser user,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var store = GetEmailStore(throwOnFail: false);
+ if (store != null)
+ {
+ var email = await GetEmailAsync(user, cancellationToken);
+ await store.SetNormalizedEmailAsync(user, NormalizeKey(email), cancellationToken);
+ }
+ }
+
+
///
/// Get the confirmation token for the user
///
///
///
///
- public virtual Task GenerateEmailConfirmationTokenAsync(TUser user,
+ public async virtual Task GenerateEmailConfirmationTokenAsync(TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
- return GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken);
+ var token = await GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken);
+ await LogResultAsync(IdentityResult.Success, user);
+
+ return token;
}
///
@@ -1313,10 +1379,10 @@ namespace Microsoft.AspNet.Identity
}
if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken))
{
- return IdentityResult.Failed(Resources.InvalidToken);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
}
await store.SetEmailConfirmedAsync(user, true, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1352,7 +1418,10 @@ namespace Microsoft.AspNet.Identity
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
- return await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken);
+ var token = await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken);
+ await LogResultAsync(IdentityResult.Success, user);
+
+ return token;
}
///
@@ -1374,13 +1443,13 @@ namespace Microsoft.AspNet.Identity
// Make sure the token is valid and the stamp matches
if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken))
{
- return IdentityResult.Failed(Resources.InvalidToken);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
}
var store = GetEmailStore();
await store.SetEmailAsync(user, newEmail, cancellationToken);
await store.SetEmailConfirmedAsync(user, true, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
// IUserPhoneNumberStore methods
@@ -1431,7 +1500,7 @@ namespace Microsoft.AspNet.Identity
await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken);
await store.SetPhoneNumberConfirmedAsync(user, false, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1453,12 +1522,12 @@ namespace Microsoft.AspNet.Identity
}
if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken))
{
- return IdentityResult.Failed(Resources.InvalidToken);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
}
await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken);
await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1497,9 +1566,12 @@ namespace Microsoft.AspNet.Identity
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
- return Rfc6238AuthenticationService.GenerateCode(
+ var token = Rfc6238AuthenticationService.GenerateCode(
await CreateSecurityTokenAsync(user, cancellationToken), phoneNumber)
.ToString(CultureInfo.InvariantCulture);
+
+ await LogResultAsync(IdentityResult.Success, user);
+ return token;
}
///
@@ -1517,8 +1589,13 @@ namespace Microsoft.AspNet.Identity
int code;
if (securityToken != null && Int32.TryParse(token, out code))
{
- return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber);
+ if (Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber))
+ {
+ await LogResultAsync(IdentityResult.Success, user);
+ return true;
+ }
}
+ await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
return false;
}
@@ -1547,7 +1624,18 @@ namespace Microsoft.AspNet.Identity
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider));
}
// Make sure the token is valid
- return await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken);
+ var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken);
+
+ if (result)
+ {
+ await LogResultAsync(IdentityResult.Success, user);
+ }
+ else
+ {
+ await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
+ }
+
+ return result;
}
///
@@ -1573,7 +1661,11 @@ namespace Microsoft.AspNet.Identity
{
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider));
}
- return await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken);
+
+ var token = await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken);
+ await LogResultAsync(IdentityResult.Success, user);
+
+ return token;
}
///
@@ -1651,7 +1743,18 @@ namespace Microsoft.AspNet.Identity
Resources.NoTokenProvider, tokenProvider));
}
// Make sure the token is valid
- return await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken);
+ var result = await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken);
+
+ if (result)
+ {
+ await LogResultAsync(IdentityResult.Success, user);
+ }
+ else
+ {
+ await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user);
+ }
+
+ return result;
}
///
@@ -1674,7 +1777,10 @@ namespace Microsoft.AspNet.Identity
throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture,
Resources.NoTokenProvider, tokenProvider));
}
- return await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken);
+ var token = await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken);
+ await LogResultAsync(IdentityResult.Success, user);
+
+ return token;
}
///
@@ -1703,7 +1809,7 @@ namespace Microsoft.AspNet.Identity
Resources.NoTokenProvider, tokenProvider));
}
await _tokenProviders[tokenProvider].NotifyAsync(token, this, user, cancellationToken);
- return IdentityResult.Success;
+ return await LogResultAsync(IdentityResult.Success, user);
}
// IUserFactorStore methods
@@ -1753,7 +1859,7 @@ namespace Microsoft.AspNet.Identity
}
await store.SetTwoFactorEnabledAsync(user, enabled, cancellationToken);
await UpdateSecurityStampInternal(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
// Messaging methods
@@ -1765,7 +1871,7 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message,
+ public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
@@ -1833,7 +1939,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await store.SetLockoutEnabledAsync(user, enabled, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1890,10 +1996,10 @@ namespace Microsoft.AspNet.Identity
}
if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false)))
{
- return IdentityResult.Failed(Resources.LockoutNotEnabled);
+ return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()), user);
}
await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1917,12 +2023,12 @@ namespace Microsoft.AspNet.Identity
var count = await store.IncrementAccessFailedCountAsync(user, cancellationToken);
if (count < Options.Lockout.MaxFailedAccessAttempts)
{
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
cancellationToken);
await store.ResetAccessFailedCountAsync(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1941,7 +2047,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user");
}
await store.ResetAccessFailedCountAsync(user, cancellationToken);
- return await UpdateAsync(user, cancellationToken);
+ return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user);
}
///
@@ -1962,6 +2068,75 @@ namespace Microsoft.AspNet.Identity
return await store.GetAccessFailedCountAsync(user, cancellationToken);
}
+ public virtual Task> GetUsersForClaimAsync(Claim claim,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ ThrowIfDisposed();
+ var store = GetClaimStore();
+ if (claim == null)
+ {
+ throw new ArgumentNullException("claim");
+ }
+ return store.GetUsersForClaimAsync(claim, cancellationToken);
+ }
+
+ ///
+ /// Get all the users in a role
+ ///
+ ///
+ ///
+ ///
+ public virtual Task> GetUsersInRoleAsync(string roleName,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ ThrowIfDisposed();
+ var store = GetUserRoleStore();
+ if (roleName == null)
+ {
+ throw new ArgumentNullException("role");
+ }
+
+ return store.GetUsersInRoleAsync(roleName, cancellationToken);
+ }
+
+ ///
+ /// Logs the current Identity Result and returns result object
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task LogResultAsync(IdentityResult result,
+ TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ result.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user)));
+
+ return result;
+ }
+
+ ///
+ /// Logs result of operation being true/false
+ ///
+ ///
+ ///
+ ///
+ /// result
+ protected async Task LogResultAsync(bool result,
+ TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "")
+ {
+ var baseMessage = Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user));
+ if (result)
+ {
+ Logger.WriteInformation(string.Format("{0} : {1}", baseMessage, result.ToString()));
+ }
+ else
+ {
+ Logger.WriteWarning(string.Format("{0} : {1}", baseMessage, result.ToString()));
+ }
+
+ return result;
+ }
+
private void ThrowIfDisposed()
{
if (_disposed)
diff --git a/src/Microsoft.AspNet.Identity/UserValidator.cs b/src/Microsoft.AspNet.Identity/UserValidator.cs
index 749f3a9f3f..06ab306683 100644
--- a/src/Microsoft.AspNet.Identity/UserValidator.cs
+++ b/src/Microsoft.AspNet.Identity/UserValidator.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
#if ASPNET50
using System.Net.Mail;
#endif
@@ -19,6 +18,13 @@ namespace Microsoft.AspNet.Identity
///
public class UserValidator : IUserValidator where TUser : class
{
+ public UserValidator(IdentityErrorDescriber errors = null)
+ {
+ Describer = errors ?? new IdentityErrorDescriber();
+ }
+
+ public IdentityErrorDescriber Describer { get; private set; }
+
///
/// Validates a user before saving
///
@@ -37,7 +43,7 @@ namespace Microsoft.AspNet.Identity
{
throw new ArgumentNullException("user");
}
- var errors = new List();
+ var errors = new List();
await ValidateUserName(manager, user, errors);
if (manager.Options.User.RequireUniqueEmail)
{
@@ -46,16 +52,16 @@ namespace Microsoft.AspNet.Identity
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
}
- private async Task ValidateUserName(UserManager manager, TUser user, ICollection errors)
+ private async Task ValidateUserName(UserManager