1

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?

Dener
  • 121
  • 2
  • 8

1 Answers1

3

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.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Thanks. I'd corrected this removing the `[SystemAuthorize]` Attribute from class declaration which had no value specified for Form attribute. But looking your code it seems the right way to do it. I'll try it! – Dener Oct 05 '17 at 15:12
  • That's a hole new level of over-complicating from Microsoft side. – ojonasplima Dec 19 '22 at 17:51