Azureはじめました

Windows Azureで業務システムを組んでみる日記

ユーザーのアクティビティをAuthorizeアノテーションと同様にチェックする

MVC4のAuthorizedAttributeは

    [Authorize]
    public class HomeController : Controller {

        [Authorize(Role="Administrators")
        public ActionResult Index() {

みたいな感じのアノテーションを追加するだけで、チェックとリダイレクトができちゃうので非常にありがたい。
ありがたいんだけど、ログインしているだけではなくて他のステートまでチェックしなきゃいけない場合があると、

    [Authorize]
    public class HogeController : Controller {

        [Authorize(Role="Administrators")
        public ActionResult Index() {
           if (Session["Printers"]!=null){
                 return RedirectToAction("Printer","Select");
           }
           :

みたいな感じにメソッド内で個別にチェックする必要があったりしてアレ。

そんな時も安心のCodeprex

Extending the behavior of MVC AuthorizeAttribute for activity-based authorization - CodeProject

If you’re familiar with NetSqlAzMan or CanCan, you know that checking permissions based on a user’s activities is easier to manage and more flexible that working with the roles a user is in. Whatever method you take to add activity based authorization, if you are working in MVC you will run into the issue that AuthorizeAttribute only cares about Users and Roles. The good news is that you can inherit from AuthorizeAttribute and easily adapt it to account for activity-based authorization.

he method that manages whether or not a user is authorized is the AuthorizeCore method:

protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
    if (httpContext == null) {
        throw new ArgumentNullException("httpContext");
    }

    IPrincipal user = httpContext.User;
    if (!user.Identity.IsAuthenticated) {
        return false;
    }

    if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name,
          StringComparer.OrdinalIgnoreCase)) {
        return false;
    }

    if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
        return false;
    }

    return true;
}

Going through the code:

  1. If the httpContext is null, error out.
  2. If the user isn’t authenticated, return false.
  3. If the required users list isn’t empty and the user isn’t in that list, return false.
  4. If the required roles list isn’t empty and the user isn’t in a role in that list, return false.
  5. If we made it this far, the user is good to go.

AuthorizeCoreをオーバーライドして判定基準を変更し、HandleUnauthorizedRequestで対処を書いてやればいいのか。

サクッと実装

    public class PrinterSelected : AuthorizeAttribute {
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
            bool result = base.AuthorizeCore(httpContext);
            if (result) {
                result = (httpContext.Session["Printers"]!=null);
            }
            return result;
        }
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
            if (!filterContext.HttpContext.User.Identity.IsAuthenticated) {
                base.HandleUnauthorizedRequest(filterContext);
            } else if (httpContext.Session["Printers"]!=null){
                filterContext.Result = new RedirectToRouteResult(new
                RouteValueDictionary(new { controller = "Printers", action = "Index", returnUrl = filterContext.HttpContext.Request.Path }));
            } else {
                filterContext.Result = new RedirectToRouteResult(new
                RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
            }
        }
    }

AuthorizeCore()でSession["Printers"]をチェックしてnullならFalseを返しておく。*1
ここでFalseを返すとAuthorizeAttribute.HandleUnauthorizedRequest()がコールされるので、オーバーライドしたメソッド内で同様にチェックし、リダイレクトコードを記述する。

んで、

    [PrinterSelected]
    public class HogeController : Controller {
        public ActionResult Index() {
           if (Session["Printers"]!=null){
                 return RedirectToAction("Printer","Select");
           }
           :

こんな感じのアノテーション定義をしてやればOK。

ユーザーのアクティビティのチェックなんかがこういう遣り方で出来るのは楽でいいね。

*1:先にAuthorizeAttribute.AuthorizeCore()を実行しFalseなら判断しない