Skip to content

Instantly share code, notes, and snippets.

@RoLYroLLs
Created February 12, 2018 22:53
Show Gist options
  • Save RoLYroLLs/af072e3ae9b875f1c3b6a0efd731950e to your computer and use it in GitHub Desktop.
Save RoLYroLLs/af072e3ae9b875f1c3b6a0efd731950e to your computer and use it in GitHub Desktop.
Custom MVC AuthorizationAttribute for Role Claims
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