I have the follow Custom AuthorizeAttribute:
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Request is an Authenticated Method?
if (filterContext.HttpContext.Request.IsAuthenticated)
{
Debug.WriteLine("Test 1 " + PermissionForm);
if (!CurrentUserHasPermissionForm(PermissionForm))
{
//Deny access code
}
}
}
//...
}
After Login method it redirects to Index page from HomeController. The problem is when use SystemAuthorize Attribute in my HomeController the Form value always come as 0 when it should be 4 (Content).
HomeController method:
[SystemAuthorize(PermissionForm = Form.CONTENT, Permissions = PermissionValue.VIEW)]
public ActionResult Index()
{
return this.View();
}
Login method:
[AllowAnonymous]
[Route("Account/Login")]
public ActionResult Login(LoginViewModel model, string url = "")
{
var user= GetUserAccount(model);
if (user == null)
{
ModelState.AddModelError("", "User not found!");
return View(model);
}
else
{
FormsAuthentication.SetAuthCookie(user.Sign, false);
var authTicket = new FormsAuthenticationTicket(1, user.Sign, DateTime.Now, DateTime.Now.AddMinutes(20), false, JsonConvert.SerializeObject(user));
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
HttpContext.Response.Cookies.Add(authCookie);
return RedirectToAction("Index", "Home");
}
}
Form enum:
public enum Form : short
{
PATIENT = 1,
USERS = 2,
MEDICE = 3,
CONTENT = 4,
}
What I'm doing wrong or missing?
Unfortunately Microsoft made this a bit confusing by combining IAuthorizationFilter with Attribute in the same class. The fact of the matter is that attributes cannot do anything except store meta-data.
The part of MVC that reads the attribute is the IAuthorizationFilter which through some MVC magic is registered with MVC automatically when you place AuthorizeAttribute (or a subclass) on a controller or action.
But the only way to actually read the meta-data from the attribute is to use Reflection. The meta-data is in the same class, but not the same instance of the class. The meta-data is in the Attribute, but the code that executes when the filter runs is in the IAuthorizationFilter, which is a separate instance of the same class.
public class SystemAuthorizeAttribute : AuthorizeAttribute
{
public Form PermissionForm { get; set; } //Enum
public PermissionValue Permissions { get; set; }//Enum
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetSystemAuthorizeAttribute(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Run the authorization based on the attribute
var form = authorizeAttribute.PermissionForm;
var permissions = authorizeAttribute.Permissions;
// Return true if access is allowed, false if not...
if (!CurrentUserHasPermissionForm(form))
{
//Deny access code
}
}
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
private SystemAuthorizeAttribute GetSystemAuthorizeAttribute(ActionDescriptor actionDescriptor)
{
SystemAuthorizeAttribute result = null;
// Check if the attribute exists on the action method
result = (SystemAuthorizeAttribute)actionDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (SystemAuthorizeAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(SystemAuthorizeAttribute), inherit: true)
.SingleOrDefault();
return result;
}
}
Note that OnAuthorization has some logic in it that you will need to support output caching and the part of the code that checks for [AllowAnonymous], so you should not put your authorization check there, but in AuthorizeCore. But unfortunately, AuthorizeCore isn't passed the ActionDescriptor you need to check whether the attribute exists, so you need the above httpContext.Items hack to ensure it is passed into that method on the same thread.
The Reflection part becomes much more clear if you separate your Attribute into a different class from the IAuthorizationFilter, as in this example.
Related
Here is my code. I have written login to validate the token, for valid token return user object. but unable to find way to make it available across controllers.
I don't want to use Identity.
public class CustomAuthorize : AuthorizeAttribute
{
private const string AUTH_TOKEN = "AuthToken";
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
AllowAnonymousAttribute allowAnonymousAttribute = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().FirstOrDefault();
if (allowAnonymousAttribute != null)
{
return Task.FromResult<object>(null);
}
if (actionContext.Request.Headers.Contains(AUTH_TOKEN))
{
var authToken = actionContext.Request.Headers.GetValues(AUTH_TOKEN).First();
var user = Utility.GetUserByToken(authToken);
if (user != null)
{
//
// how to make this `user` object available across the controllers
//
return Task.FromResult<object>(null);
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new CustomError() { Code = 100, Message = "Invalid Access Token" });
return Task.FromResult<object>(null);
}
}
else
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new CustomError() { Code = 100, Message = "Invalid Access Token" });
return Task.FromResult<object>(null);
}
}
}
Please help...
Your Question is a bit unclear - I assume you are referring to this line:
var user = Utility.GetUserByToken(authToken);
If so, then I might have a solution. So the fundamental problem is that you cannot simply save this variable where you currently are in the current controller, you need to understand the context you are working in - Every time a different user makes a request a different user models get created in the current controller. To have access to the user model across my app whenever a user makes a request, I do the following:
First you need to hook into the request receiving process of ASP.NET. This can be done inside the Global.asax.cs file, but I prefer to keep it clean and create a PartialGlobal class and mark the Global.asax.cs as partial.
From
public class MvcApplication : System.Web.HttpApplication
To
public partial class MvcApplication : System.Web.HttpApplication
Then create the PartialGlobal Class:
public partial class MvcApplication
{
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
//For API users
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
if (authHeaderVal.Scheme.Equals("Basic", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrEmpty(authHeaderVal.Parameter))
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
//For Regular Website Users
else
{
HttpCookie authCookie = request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// If caching userData field then extract
var userModel = UsersBLL.DeserializeObject(authTicket.UserData);
var principal = new UserPrincipal(userModel);
SetPrincipal(principal);
}
}
}
private static bool AuthenticateUser(string credentials)
{
var model = UsersBLL.DecryptToken(credentials);
if (!model.RefUser.HasValue)
{
return false;
}
SetPrincipal(new UserPrincipal(model));
return true;
}
private static void SetPrincipal(UserPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
}
The UserPrincipal Class:
public class UserPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
//Just a class with details like name,age etc.
public UserModel Model { get; set; }
public UserPrincipal(UserModel model)
{
this.Model = model;
this.Identity = new GenericIdentity(model.Email);
}
}
Note the line in the PartialGLobal class: var model = UsersBLL.DecryptToken(credentials);. Here I simply use a method I created to de-crypt my token string so it can be deserialized, you probably won't have/need this.
The essential part is this last step of the PartialGlobal class:
private static void SetPrincipal(UserPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
If you have the context know about your user, you can access it anywhere by simply calling:
var principal = (UserPrincipal)HttpContext.Current.User;
One way is to Extend the ApiController which is what your controllers are using as the base class.
Define a CustomController
public class CustomController : ApiController
{
projected User _user;
}
For all the other controller use this as a base class and the _user object should be accessible from all the controllers.
Context: C# ASP.NET MVC Web Application.
I added functionality which enables me to impersonate another user.
I should only see this functionality when the application is configured as such. So I added to the appSettings in web.config.
<add key="AllowImpersonation" value="true" />
(Not) Showing the controls is easy. But I also want to disable the corresponding controller method.
First thing on my mind is a custom AuthorizeAttribute in which I can just check for the configuration setting.
Even better would be a more generic attribute in which you can provide the key of the (boolean) Appsetting to check. Something like this:
namespace MILF.Security
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ConfigSetting_Authorize : AuthorizeAttribute
{
private string _configKey;
public ConfigSetting_Authorize(string configKey)
{
if (string.IsNullOrEmpty(configKey))
throw new ArgumentException("configKey");
this._configKey = configKey;
}
//Core authentication, called before each action
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
return Convert.ToBoolean(AppSettingsHelper.Get<bool>(this._configKey))
}
catch (Exception)
{
return false;
}
}
//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
SecurityLogManager securityLogManager = new SecurityLogManager(filterContext);
var message = string.format("{0} triez to access shit that waz disabled.", filterContext.HttpContext.User.Identity.Name);
securityLogManager.Write(message);
securityLogManager = null;
}
}
}
namespace MILF.Utils
{
public static class AppSettingsHelper
{
public static T Get<T>(string key)
{
var appSetting = ConfigurationManager.AppSettings[key];
if (string.IsNullOrWhiteSpace(appSetting)) throw new Exception(key);
var converter = TypeDescriptor.GetConverter(typeof(T));
return (T)(converter.ConvertFromInvariantString(appSetting));
}
}
}
And then use it like this:
public class ImpersonateController : BaseController
{
// GET: Impersonate
[ConfigSetting_Authorize("AllowImpersonation")]
public ActionResult Index()
{
if (!LoggedInUser.IsAdministrator)
{
return RedirectToAction("Index", "Home");
}
return View(new List<KBEAccounts.Account>());
}
[ConfigSetting_Authorize("AllowImpersonation")]
public ActionResult Impersonate(string Id)
{
if (!LoggedInUser.IsAdministrator)
{
return RedirectToAction("Index", "Home");
}
// Set the account to impersonate
var service = new ActiveDirectoryUsersService();
this.LoggedInUser =
service.GetUserByLoginNameToImpersonate(
Id,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorPersonnelId)) ?
LoggedInUser.EmployeeNumber :
LoggedInUser.ImpersonatorPersonnelId,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorCommonName)) ?
LoggedInUser.CommonName :
LoggedInUser.ImpersonatorCommonName,
(string.IsNullOrWhiteSpace(LoggedInUser.ImpersonatorFullName)) ?
LoggedInUser.FullName :
LoggedInUser.ImpersonatorFullName);
// redirect to home
return RedirectToAction("", "");
}
}
But it feels like I am re-inventing the wheel.
Does the .NET framework not already supply this functionality?
I have two entity sets like below:
public class Serial
{
[HiddenInput(DisplayValue=false)]
public int SerialID { get; set; }
[HiddenInput(DisplayValue=false)]
public string Id { get; set; }
[Required(ErrorMessage="Please provide your membership serial")]
[StringLength(16,ErrorMessage="This field can't be longer as of 16 characters.")]
public string UserSerial { get; set; }
}
AND
public class Subscription
{
[HiddenInput(DisplayValue=false)]
public int SubscriptionID { get; set; }
[Required(ErrorMessage="Please provide a subscription code.")]
public string AdminSerial { get; set; }
}
I would like to create a custom authorization attribute to design my action methods within my controllers with following scenario:
I would like to check if the any value of UserSerial in Serial
Entity not equal to any value ofAdminSerial in Subscription Entity.
If the above condition become true so the ActionResult method itself should be executed else the Custom AuthorizeAttribute should redirect it to another action method, here is what i tried but it isn't working am i missing something?
public class RequireSerial : AuthorizeAttribute
{
EFDbContext db = new EFDbContext();
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!db.Subscriptions.Any(s => s.AdminSerial.Equals(db.Serials.Any())))
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Serials", action = "Create" }));
}
else
{
// Execute the Action method itself
}
}
}
I tried to put this RequireSerial custom authorize attribute on the top of action methods but nothing really happens.
[RequireSerial]
public ViewResult Checkout()
{
return View();
}
Any help would be appreciated.
You need to override OnAuthorization a HandleUnauthorizedRequest method. HandleUnauthorizedRequest is called by default implementation of OnAuthorization method if user is not authorized.
Default implementation of HandleUnauthorizedRequestredirects the user to login page.
EFDbContext db = new EFDbContext();
public override void OnAuthorization(AuthorizationContext filterContext)
{
//handle base authorization logic
base.OnAuthorization(filterContext);
//if user is not authorized (by base rules) simply return because redirect was set in 'base.OnAuthorization' call.
if (this.AuthorizeCore(filterContext.HttpContext) == false)
{
return;
}
//Here comes your custom redirect logic:
if (!db.Subscriptions.Any(s => s.AdminSerial.Equals(db.Serials.Any())))
{
filterContext.Result = your redirect url goes here;
}
}
Authorization is basically a "boolean" value (not exactly a true boolean but it returns either an authorization or a failure of it)
to full get it this, MSDN's article about is very clear.
Custom Authorization
Is there a way to create a custom filter with an API controller to redirect to a MVC controller?
After looking around a bit his is what i have.
public class APIHasOneOfThesePermissions : ActionFilterAttribute
{
protected UserManager<ApplicationUser> UserManager { get; set; }
private SAMPortal.DAL.SAMPortalContext db = new DAL.SAMPortalContext();
public string[] Permissions { get; set; }
public APIHasOneOfThesePermissions(string[] Permissions)
{
this.UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(this.db));
this.Permissions = Permissions;
}
public override void OnActionExecuting(HttpActionContext filterContext)
{
string userID = HttpContext.Current.User.Identity.GetUserId();
var CurrUser = db.Users.Include(u => u.Role.Permissions).Where(user => user.Id.Equals(userID)).FirstOrDefault();
bool hasPermission = false;
foreach (string x in Permissions)
{
if (hasPermission == false)
{
hasPermission = CurrUser.HasPermission(x);
}
}
if (hasPermission == false)
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}
However when i execute the code it doesn't redirect them to the error page. Ideally i would like to redirect to a specify non-API controller is that possible?
I've created AuthorizeRedirectAttribute in one of my projects like this:
using System;
using System.Net;
using System.Web.Mvc;
namespace MyNamespace.Attributes
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirectAttribute : AuthorizeAttribute
{
public string RedirectUrl = "~/Error/Forbidden403";
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
var httpContext = filterContext.RequestContext.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
// If AJAX request, just return appropriate code
if (request.IsAjaxRequest())
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
response.StatusCode = (int)HttpStatusCode.Forbidden;
else
response.StatusCode = (int)HttpStatusCode.Unauthorized;
response.SuppressFormsAuthenticationRedirect = true;
response.End();
}
// Otherwise check if authenticated, and if not redirect to specified url
if (httpContext.User.Identity.IsAuthenticated)
{
httpContext.Response.Redirect(RedirectUrl);
}
}
}
}
Then I've used it like this
[AuthorizeRedirect(Roles = "Administrator")]
public class MyController : Controller
{
}
In this case I've decorated whole controller with this attribute. It can also be applied to single controller function, if necessary. Basically what it does is, it checks whether logged on user is in role Administrator. If it is not, user is redirected to "~/Error/Forbidden403" action (returning simple view displaying user has not enough permissions). Hope it helps.
You could also implement checking your own permissions, as you did in your code.
I have created the filter below to restrict access to sections of my application to only users that are logged in. However, my app still complains that the user objects have not been instantiated before the filter fires the redirect. How can I make the redirect kick the user out before the action method has a chance to notice the objects are null?
Context
For completeness, it is worth mentioning:
UserSession.CurrentOrDefault();
Returns an object if it finds values stored in the current session, or null if the session doesn't exist.
The filter
public class RestrictAccess : ActionFilterAttribute
{
public UserRole RequiredRole { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
base.OnActionExecuting(filterContext);
return;
}
else
{
HttpContext.Current.Response.Redirect("/");
return;
}
}
HttpContext.Current.Response.Redirect("/Session/Create");
}
}
An example action method that complains:
[RestrictAccess]
public ActionResult Index()
{
var userSession = UserSession.CurrentOrDefault();
// This is the part that throws the exception. userSession.User is null here.
// My expectation was for this to be unreachable if user is null because of the filter.
var model = new IndexViewModel { User = userSession.User };
return View(model);
}
You should implement your own AuthorizeAttribute for that
public class Authorization : AuthorizeAttribute
{
public UserRole RequiredRole { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
return true;
}
else
{
return false;
}
}
return false;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
filterContext.Result = new RedirectResult("/Session/Create");
}
}
catch (Exception)
{
filterContext.Result = new RedirectResult("/Session/Create");
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
When you call HttpContext.Current.Response.Redirect, you are going outside of the MVC architecture, so it does not know not to run the action method and so continues and errors.
Instead you should set the Result of your ActionExecutingContext filterContext to a RedirectResult like so:
public class RestrictAccess : ActionFilterAttribute
{
public UserRole RequiredRole { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userSession = UserSession.CurrentOrDefault();
if(userSession != null)
{
int userRole = Convert.ToInt32(userSession.User.Role);
int requiredRole = Convert.ToInt32(this.RequiredRole);
if(userRole >= requiredRole)
{
base.OnActionExecuting(filterContext);
return;
}
else
{
filterContext.Result = new RedirectResult("/");
return;
}
}
filterContext.Result = new RedirectResult("/Session/Create");
}
}