From 6fc83d4ea307c98eae0b65ff00ed5eaf998d15fd Mon Sep 17 00:00:00 2001 From: JudahGabriel Date: Wed, 6 Sep 2017 11:49:33 -0500 Subject: [PATCH] Authorization works with roles properly. Added authorization examples to Sample project. --- RavenDB.Identity/IdentityUser.cs | 11 +++- RavenDB.Identity/RoleStore.cs | 4 +- .../ServiceCollectionExtensions.cs | 2 +- RavenDB.Identity/UserStore.cs | 59 +++++++++++++++---- Sample/Controllers/HomeController.cs | 21 ++++++- Sample/Sample.csproj | 5 +- Sample/Startup.cs | 2 + Sample/Views/Home/Auth.cshtml | 1 + Sample/Views/Home/AuthAdmin.cshtml | 1 + 9 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 Sample/Views/Home/Auth.cshtml create mode 100644 Sample/Views/Home/AuthAdmin.cshtml diff --git a/RavenDB.Identity/IdentityUser.cs b/RavenDB.Identity/IdentityUser.cs index 6a96c97..12e460e 100644 --- a/RavenDB.Identity/IdentityUser.cs +++ b/RavenDB.Identity/IdentityUser.cs @@ -21,7 +21,7 @@ namespace RavenDB.Identity public virtual bool LockoutEnabled { get; set; } public virtual DateTimeOffset? LockoutEndDate { get; set; } public virtual bool TwoFactorAuthEnabled { get; set; } - public virtual List Roles { get; private set; } + public virtual IReadOnlyList Roles { get; private set; } public virtual List Claims { get; private set; } public virtual List Logins { get; private set; } @@ -38,6 +38,15 @@ namespace RavenDB.Identity this.Id = userId; this.UserName = userName; } + + /// + /// Gets the mutable roles list. This shouldn't be modified by user code; roles should be changed via UserManager instead. + /// + /// + internal List GetRolesList() + { + return (List)this.Roles; + } } public sealed class IdentityUserLogin diff --git a/RavenDB.Identity/RoleStore.cs b/RavenDB.Identity/RoleStore.cs index 68b4db1..73b4a5c 100644 --- a/RavenDB.Identity/RoleStore.cs +++ b/RavenDB.Identity/RoleStore.cs @@ -242,7 +242,7 @@ namespace RavenDB.Identity throw new ArgumentNullException(nameof(role)); } role.Name = roleName; - return Task.FromResult(0); + return Task.CompletedTask; } /// @@ -268,7 +268,7 @@ namespace RavenDB.Identity { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - return AsyncSession.LoadAsync($"IdentityRoles/{normalizedName}"); + return AsyncSession.LoadAsync($"IdentityRoles/{normalizedName.ToLower()}"); } /// diff --git a/RavenDB.Identity/ServiceCollectionExtensions.cs b/RavenDB.Identity/ServiceCollectionExtensions.cs index 3b411f1..88938e3 100644 --- a/RavenDB.Identity/ServiceCollectionExtensions.cs +++ b/RavenDB.Identity/ServiceCollectionExtensions.cs @@ -97,7 +97,7 @@ namespace RavenDB.Identity services.AddIdentity() .AddDefaultTokenProviders(); } - + services.AddScoped, UserStore>(); services.AddScoped, RoleStore>(); diff --git a/RavenDB.Identity/UserStore.cs b/RavenDB.Identity/UserStore.cs index 9240331..108912a 100644 --- a/RavenDB.Identity/UserStore.cs +++ b/RavenDB.Identity/UserStore.cs @@ -250,18 +250,16 @@ namespace RavenDB.Identity return Task.CompletedTask; } - public Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) + public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) { ThrowIfNullDisposedCancelled(user, cancellationToken); - + var indexOfClaim = user.Claims.FindIndex(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value); if (indexOfClaim != -1) { user.Claims.RemoveAt(indexOfClaim); - this.AddClaimsAsync(user, new[] { newClaim }, cancellationToken); + await this.AddClaimsAsync(user, new[] { newClaim }, cancellationToken); } - - return Task.CompletedTask; } public Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken) @@ -289,31 +287,65 @@ namespace RavenDB.Identity #region IUserRoleStore implementation - public Task AddToRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) + /// + /// Adds the user to the specified role. + /// + /// The user. + /// The name of the role. + /// + /// + public async Task AddToRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) { ThrowIfNullDisposedCancelled(user, cancellationToken); - if (!user.Roles.Contains(roleName, StringComparer.OrdinalIgnoreCase)) + var roleNameLowered = roleName.ToLower(); + if (!user.Roles.Contains(roleNameLowered, StringComparer.OrdinalIgnoreCase)) { - user.Roles.Add(roleName); + user.GetRolesList().Add(roleNameLowered); } - return Task.CompletedTask; + // See if we have an IdentityRole with that. + var roleId = "IdentityRoles/" + roleNameLowered; + var existingRoleOrNull = await this.DbSession.LoadAsync(roleId, cancellationToken); + if (existingRoleOrNull == null) + { + ThrowIfDisposedOrCancelled(cancellationToken); + existingRoleOrNull = new IdentityRole(roleNameLowered); + await this.DbSession.StoreAsync(existingRoleOrNull, roleId, cancellationToken); + } + + if (!existingRoleOrNull.Users.Contains(user.Id, StringComparer.OrdinalIgnoreCase)) + { + existingRoleOrNull.Users.Add(user.Id); + } } - public Task RemoveFromRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) + /// + /// Removes the user from the specified role. + /// + /// + /// + /// + /// + public async Task RemoveFromRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) { ThrowIfNullDisposedCancelled(user, cancellationToken); - user.Roles.RemoveAll(r => string.Equals(r, roleName, StringComparison.OrdinalIgnoreCase)); - return Task.CompletedTask; + user.GetRolesList().RemoveAll(r => string.Equals(r, roleName, StringComparison.OrdinalIgnoreCase)); + + var roleId = "IdentityRoles/" + roleName.ToLower(); + var roleOrNull = await DbSession.LoadAsync(roleId, cancellationToken); + if (roleOrNull != null) + { + roleOrNull.Users.Remove(user.Id); + } } public Task> GetRolesAsync(TUser user, CancellationToken cancellationToken) { ThrowIfNullDisposedCancelled(user, cancellationToken); - return Task.FromResult>(user.Roles); + return Task.FromResult>(new List(user.Roles)); } public Task IsInRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) @@ -336,6 +368,7 @@ namespace RavenDB.Identity return DbSession.Query() .Where(u => u.Roles.Contains(roleName)) + .Take(1024) .ToListAsync(); } diff --git a/Sample/Controllers/HomeController.cs b/Sample/Controllers/HomeController.cs index c71697e..120cdd6 100644 --- a/Sample/Controllers/HomeController.cs +++ b/Sample/Controllers/HomeController.cs @@ -6,14 +6,19 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Sample.Models; using Raven.Client; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; namespace Sample.Controllers { public class HomeController : RavenController { - public HomeController(IAsyncDocumentSession dbSession) + private UserManager userManager; + + public HomeController(IAsyncDocumentSession dbSession, UserManager userManager) : base(dbSession) { + this.userManager = userManager; } public async Task Index() @@ -27,6 +32,20 @@ namespace Sample.Controllers return View(); } + // Require that the user be logged in. + [Authorize] + public IActionResult Auth() + { + return View(); + } + + // Require that the user be in the Admin role. + [Authorize(Roles = "Admin")] + public IActionResult AuthAdmin() + { + return View(); + } + public IActionResult About() { ViewData["Message"] = "Your application description page."; diff --git a/Sample/Sample.csproj b/Sample/Sample.csproj index 1c7ffbb..de9d5ae 100644 --- a/Sample/Sample.csproj +++ b/Sample/Sample.csproj @@ -9,7 +9,6 @@ - @@ -18,4 +17,8 @@ + + + + diff --git a/Sample/Startup.cs b/Sample/Startup.cs index e621465..f5dc2ee 100644 --- a/Sample/Startup.cs +++ b/Sample/Startup.cs @@ -32,6 +32,8 @@ namespace Sample .AddRavenDbAsyncSession() // Create a RavenDB IAsyncDocumentSession for each request. .AddRavenDbIdentity(); // Use Raven for users and roles. + // You can change the login path if need be. + // services.ConfigureApplicationCookie(options => options.LoginPath = "/my/login/path"); // Add application services. services.AddTransient(); diff --git a/Sample/Views/Home/Auth.cshtml b/Sample/Views/Home/Auth.cshtml new file mode 100644 index 0000000..0ae620e --- /dev/null +++ b/Sample/Views/Home/Auth.cshtml @@ -0,0 +1 @@ +

Hi, this is a page you can access only if you're signed in.

\ No newline at end of file diff --git a/Sample/Views/Home/AuthAdmin.cshtml b/Sample/Views/Home/AuthAdmin.cshtml new file mode 100644 index 0000000..23270e1 --- /dev/null +++ b/Sample/Views/Home/AuthAdmin.cshtml @@ -0,0 +1 @@ +

Hi, this is a page you can access only if you're an admin.

\ No newline at end of file