現在、rails のアカウント管理系プラグインデファクトスタンダードといえば acts_as_authenticated である。
このプラグインに「ロール」の概念を追加するプラグインとして role_requirement(RoleRequirement) プラグインがある。

このプラグインは非常に直感的にコントローラ、アクションごとのロールの適用を可能とする。
下記の例ではシステム管理用のコントローラ SystemController が admin ロールを持ったユーザのみがアクセスできると記述していることになる。

class SystemController < ApplicationController
  require_role "admin"
  # ...
end

ただし、ロールを満たせなかった場合の挙動が以下のように実装されておりいただけない。

  • 401 Unauthorized レスポンスを返す
  • なにも描画しない

以下、自分が行なった対応をまとめる。RoleRequirement プラグインのバージョンは 1.3.2 である。
実装を確認すると、lib/role_requirement_system.rb の RoleSecurityInstanceMethods#access_denied メソッドに問題があるようである。

def access_denied
  if logged_in?
    render :nothing => true, :status => 401
    return false
  else
    super
  end
end

L.113 の render メソッドにすべてが集約されている。これを、以下のように修正する。

  • 401 Unauthorizedレスポンスを返さない
  • 任意のコントローラ・アクションを描画する
  • 権限のないアクセスはサイト内で一律同じURLへリダイレクトする

まずは lib/role_requirement_system.rb に対する patch から。

Index: C:/ruby/rails/trunk/lib/role_requirement_system.rb
===================================================================
--- C:/ruby/rails/trunk/lib/role_requirement_system.rb	(revision 24)
+++ C:/ruby/rails/trunk/lib/role_requirement_system.rb	(working copy)
@@ -110,7 +110,7 @@
     
     def access_denied
       if logged_in?
-        render :nothing => true, :status => 401
+        self.url_for_access_denied ? redirect_to(self.url_for_access_denied) : redirect_to("/")
         return false
       else
         super

self はリクエストされたコントローラのインスタンスであり、url_for_access_denied は ApplicationController へ新たに定義したアクセサで以下のように定義している。

class ApplicationController < ActionController::Base
  attr_accessor :url_for_access_denied

  def initialize
    @url_for_access_denied = {:controller => :home}
  end
end

@url_for_access_denied が設定されず nil であれば “/” へリダイレクトされるようにしてあるのでこれでも十分かもしれない。

RoleRequirement プラグイン自体はよくできているのでこの対応をいれればより使いやすくなるだろう。