Instantly share code, notes, and snippets.
Created
February 12, 2018 22:53
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save RoLYroLLs/af072e3ae9b875f1c3b6a0efd731950e to your computer and use it in GitHub Desktop.
Custom MVC AuthorizationAttribute for Role Claims
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.Caching; | |
using System.Security.Principal; | |
using System.Web; | |
using System.Web.Mvc; | |
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] | |
public class ClaimsAuthorizationAttribute : AuthorizeAttribute { | |
private readonly ApplicationDb _db = new ApplicationDb(); | |
private static readonly string[] EmptyArray = new string[0]; | |
private string _claims; | |
private string[] _claimsSplit = EmptyArray; | |
public string Claims { | |
get => _claims ?? string.Empty; | |
set { | |
_claims = value; | |
_claimsSplit = SplitString(value); | |
} | |
} | |
public bool MatchAll { get; set; } | |
public string ViewName { get; set; } | |
public ClaimsAuthorizationAttribute() { | |
ViewName = "AuthorizeFailed"; | |
} | |
public ClaimsAuthorizationAttribute(string[] claims) : this() { | |
_claimsSplit = claims; | |
_claims = string.Join(",", _claims); | |
} | |
public ClaimsAuthorizationAttribute(string claims) : this() { | |
Claims = claims; | |
} | |
// This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method. | |
protected override bool AuthorizeCore(HttpContextBase httpContext) { | |
base.AuthorizeCore(httpContext); | |
if (_claimsSplit.Length <= 0) { | |
return false; | |
} | |
var user = httpContext.User; | |
return HasClaim(user, _claimsSplit); | |
} | |
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { | |
filterContext.Result = new ViewResult { ViewName = ViewName, ViewData = new ViewDataDictionary { { "AuthorizeFailedMessage", "You do not have sufficient permission for this operation." } } }; | |
} | |
internal bool HasClaim(IPrincipal user, string[] requiredClaims) { | |
// Check if we have any required Claims | |
if (requiredClaims == null || requiredClaims.Length <= 0) { | |
return false; | |
} | |
// Get user's RoleIds from cache | |
var userRoleIds = (ICollection<string>) CacheHelper.Get(user.Identity.Name + "_RoleIds"); | |
// Check if this user's RoleIds are already in the cache | |
if (userRoleIds == null) { | |
// Get RoleIds from database | |
userRoleIds = _db.Users.FirstOrDefault(u => u.UserName == user.Identity.Name)?.Roles.Select(r => r.RoleId).ToList() ?? new List<string>(); | |
CacheHelper.Set(user.Identity.Name + "_RoleIds", userRoleIds, 240); | |
} | |
// Check the cache for each Claim required | |
foreach (var requiredClaim in requiredClaims) { | |
// Check if this Claim is already in the cache | |
var roleIds = (ICollection<string>) CacheHelper.Get(requiredClaim); | |
// Skip if we found a claim | |
if (roleIds == null) { | |
// Get Claims from database | |
roleIds = _db.RoleClaims.Where(rc => rc.Claim.Name == requiredClaim).Select(rc => rc.RoleId).ToList(); | |
CacheHelper.Set(requiredClaim, roleIds, 240); | |
} | |
if (roleIds.Intersect(userRoleIds).Any()) { | |
if (!MatchAll) { | |
return true; | |
} | |
} else { | |
if (MatchAll) { | |
return false; | |
} | |
} | |
} | |
return MatchAll; | |
} | |
/// <summary> | |
/// Splits the string on commas and removes any leading/trailing whitespace from each result item. | |
/// </summary> | |
/// <param name="original">The input string.</param> | |
/// <returns>An array of strings parsed from the input <paramref name="original"/> string.</returns> | |
internal static string[] SplitString(string original) { | |
if (string.IsNullOrEmpty(original)) { | |
return EmptyArray; | |
} | |
var split = from piece in original.Split(',') | |
let trimmed = piece.Trim() | |
where !string.IsNullOrEmpty(trimmed) | |
select trimmed; | |
return split.ToArray(); | |
} | |
} | |
public static class CacheHelper { | |
public static object Get(string key) { | |
return MemoryCache.Default[key]; | |
} | |
public static void Set(string key, object data, int duratin = 30) { | |
var policy = new CacheItemPolicy { | |
AbsoluteExpiration = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(duratin) | |
}; | |
MemoryCache.Default.Add(new CacheItem(key, data), policy); | |
} | |
public static void Invalidate(string key) { | |
MemoryCache.Default.Remove(key); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment