From 5e5bf19863fdad26758974e87cead45a259bba1b Mon Sep 17 00:00:00 2001 From: ElenaRepository <50620495+ElenaRepository@users.noreply.github.com> Date: Fri, 29 Oct 2021 08:41:11 +0200 Subject: [PATCH] Different cache key by role (#8510) * feature: adds roles and permissions of the user in to cache key --- .../Filters/CacheByRoleFilter.cs | 151 ++++++++++++++++++ .../Modules/Orchard.OutputCache/Module.txt | 5 + .../Orchard.OutputCache.csproj | 7 +- 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CacheByRoleFilter.cs diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CacheByRoleFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CacheByRoleFilter.cs new file mode 100644 index 000000000..6aac3ac23 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/CacheByRoleFilter.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Orchard.Data; +using Orchard.Environment.Extensions; +using Orchard.Roles.Models; +using Orchard.Security; + +namespace Orchard.OutputCache.Filters { + [OrchardFeature("Orchard.OutputCache.CacheByRole")] + public class CacheByRoleFilter : ICachingEventHandler { + private readonly IAuthenticationService _authenticationService; + private readonly IAuthorizer _authorizer; + private readonly IRepository _userRolesRepo; + private readonly IRepository _roleRepo; + private readonly IRepository _rolesPermissionsRepo; + private readonly IRepository _permissionRepo; + + public CacheByRoleFilter( + IAuthenticationService authenticationService, + IAuthorizer authorizer, + IRepository userRolesRepo, + IRepository roleRepo, + IRepository rolesPermissionsRepo, + IRepository permissionRepo) { + _authenticationService = authenticationService; + _authorizer = authorizer; + _userRolesRepo = userRolesRepo; + _roleRepo = roleRepo; + _rolesPermissionsRepo = rolesPermissionsRepo; + _permissionRepo = permissionRepo; + } + + public void KeyGenerated(StringBuilder key) { + List userRolesPermissions = new List(); + IQueryable userRolesPermissionsQuery = Enumerable.Empty().AsQueryable(); + IQueryable permissionsQuery = Enumerable.Empty().AsQueryable(); + + var currentUser = _authenticationService.GetAuthenticatedUser(); + if (currentUser != null) { + // add the Authenticated role and its permissions + // the Authenticated role is not assigned to the current user + permissionsQuery = GetPermissioFromRole("Authenticated"); + + if (_authorizer.Authorize(StandardPermissions.SiteOwner)) { + // the site owner has no permissions + // get the roles of the site owner + userRolesPermissionsQuery = _userRolesRepo + .Table.Where(usr => usr.UserId == currentUser.Id) + .Join( + _roleRepo.Table, + ur => ur.Role.Id, + r => r.Id, + (ur, r) => r + ) + .Select(urp => new UserPermission { RoleName = urp.Name }); + } else { + userRolesPermissionsQuery = _userRolesRepo + // get user roles and permissions + .Table.Where(usr => usr.UserId == currentUser.Id) + // given the ids of the roles related to the user + // join with the RoleRecord table + // get the role name + .Join( + _roleRepo.Table, + ur => ur.Role.Id, + r => r.Id, + (ur, r) => new { ContentItemRecord = r } + ) + // join table RolePermissionRecord + // for each role, get id of role permissions + .Join( + _rolesPermissionsRepo.Table, + obj => obj.ContentItemRecord, + rp => rp.Role, + (obj, rp) => rp + ) + // join PermissionRecord + // for each id permission get feature and permission name + .Join( + _permissionRepo.Table, + rp => rp.Permission.Id, + p => p.Id, + (rp, p) => new UserPermission { RoleName = rp.Role.Name, PermissionName = p.FeatureName + "." + p.Name } + ); + } + } else { + // the anonymous user has no roles, get its permissions + permissionsQuery = GetPermissioFromRole("Anonymous"); + } + + if (userRolesPermissionsQuery.Any()) { + userRolesPermissions.AddRange(userRolesPermissionsQuery + .OrderBy(urp => urp.RoleName) + .ThenBy(urp => urp.PermissionName) + .ToList()); + } + if (permissionsQuery.Any()) { + userRolesPermissions.AddRange(permissionsQuery + .OrderBy(urp => urp.RoleName) + .ThenBy(urp => urp.PermissionName) + .ToList()); + } + + if (userRolesPermissions.Any()) { + var userRoles = String.Join(";", userRolesPermissions + .Select(r => r.RoleName) + .Distinct() + .OrderBy(s => s)); + + var userPermissions = String.Join(";", userRolesPermissions + .Select(p => p.PermissionName) + .Distinct() // permissions may be duplicate: two different roles may give the same permission + .OrderBy(s => s)); + + + key.Append(string.Format("UserRoles={0};UserPermissions={1};", + userRoles.GetHashCode(), + userPermissions.GetHashCode())); + } else { + key.Append("UserRoles=;UserPermissions=;"); + } + } + + private IQueryable GetPermissioFromRole(string role) { + return _roleRepo + .Table.Where(r => r.Name == role) + .Join( + _rolesPermissionsRepo.Table, + r => r.Id, + rp => rp.Role.Id, + (obj, rp) => rp + ) + .Join( + _permissionRepo.Table, + rp => rp.Permission.Id, + p => p.Id, + (rp, p) => new UserPermission { RoleName = rp.Role.Name, PermissionName = p.FeatureName + "." + p.Name } + ); + } + } + public class UserPermission { + public UserPermission() { + RoleName = string.Empty; + PermissionName = string.Empty; + } + public string RoleName { get; set; } + public string PermissionName { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Module.txt b/src/Orchard.Web/Modules/Orchard.OutputCache/Module.txt index c934cd9de..90ac720b8 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Module.txt +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Module.txt @@ -20,3 +20,8 @@ Features: Description: Activates a provider that stores output cache data in the App_Data folder. Category: Performance Dependencies: Orchard.OutputCache + Orchard.OutputCache.CacheByRole: + Name: Cache By Role + Description: Adds the user role in the cache key + Category: Performance + Dependencies: Orchard.OutputCache, Orchard.Roles diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj b/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj index 81c91f0c2..a9882d1ef 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Orchard.OutputCache.csproj @@ -104,6 +104,7 @@ + @@ -120,6 +121,10 @@ Orchard.Core $(MvcBuildViews) + + {D10AD48F-407D-4DB5-A328-173EC7CB010F} + Orchard.Roles + @@ -213,4 +218,4 @@ - + \ No newline at end of file