概要
Web システムを構築するにあたり、「ユーザーごとに権限管理を行いたい」という要望は比較的多いと思います。
Ruby on Rails では cancancan という Gem を使って権限管理を行うことができますが、この Gem は Model への実行権限(read/write)を管理するのが基本的な使い方になっています。
今回、「API の実行権限を管理したい」という要件があり、その方法を調べたのでまとめました。
cancancan の一般的な使い方(モデルベースの制御)
本題に入る前に、cancancan の一般的な使い方(モデルベースの制御)を簡単に紹介します。
(ほぼ Gem の Readme の日本語訳です)
Gem のインストール
Gemfile に下記を追加します。
gem 'cancancan'
Ability の定義
ユーザーに付与する権限は Ability
クラスで定義します。
まず、下記コマンドで Ability
クラスを作成します。
rails g cancan:ability
例として、モデル Post の実行権限を制御します。
class Ability
include CanCan::Ability
def initialize(user)
can :read, Post, public: true # public=true のレコードは誰でも参照可
if user.present? # ログインユーザーの権限を追加で定義
can :read, Post, user_id: user.id # user_id が自分自身のレコードも参照可
if user.admin? # admin ユーザーの権限を追加で定義
can :read, Post # すべてのレコードを参照可
end
end
end
end
cancancan は wiki が充実しており、詳細な定義方法もそちらにまとまっています。
Defining Abilities - cancancan
権限の確認
ビューで権限を確認する場合
<% if can? :read, @post %>
<%= link_to "View", @post %>
<% end %>
can? :read, @post
で、ユーザーに変数 @post
の参照権限があるかを確認できます。
上記例の場合は、@post
の参照権限がある場合のみリンクを表示するようにしています。
ビューで使用できる helper の詳細については、wiki の下記ページを参照してください。
Checking Abilities - cancancan
コントローラーで権限を確認する場合
前提として、メソッド current_user
でログインユーザーを参照できる必要があります。
Devise や Authlogic といった認証用の Gem を事前に導入してください。
def show
@post = Post.find(params[:id])
authorize! :read, @post # current_user が @post を参照不可の場合はエラー
end
load_and_authorize_resource
を記述すると、コントローラー名に応じたリソースの読み込みと権限の確認を行う before_action
が追加されます。
load_and_authorize_resource
を使えば、「後から追加したアクションに authorize!
を書き忘れて権限チェックから漏れていた」という事態を防ぐことが出来ます。
class PostsController < ApplicationController
load_and_authorize_resource
def show # GET /posts/:post_id にアクセスした際に呼び出される
# before_action で以下を実行
# @post = Post.find(params[:post_id])
# authorize! :show, @post
end
end
補足事項
cancancan では内部的に以下のエイリアスが切られています。
alias_action :index, :show, :to => :read
alias_action :new, :to => :create
alias_action :edit, :to => :update
このため、たとえば authorize! :show, @post
と authorize! :read, @post
では同じ結果が得られます。
cf. Action Aliases - cancancan
load_and_authorize_resource
は load_resource
と authorize_resource
の 2 つに分けることができます。
上記例だと、load_resource
で @post = Post.find(params[:post_id])
の部分の before_action
が、authorize_resource
で authorize! :show, @post
の部分の before_action
が追加されます。
cf. Authorizing controller actions - cancancan
API ベースの制御
今回の本題です。
例として、Post
コントローラーに show
と update
の2つのアクションを定義している場合を考えます。
class PostsController < ApplicationController
def show # GET /posts/:post_id で呼び出す API
end
def update # PUT /posts/:post_id で呼び出す API
end
end
admin ユーザーは GET /posts/:post_id
と PUT /posts/:post_id
の両方を、それ以外のユーザーは GET /posts/:post_id
だけを実行できるようにします。
Ability の定義
class Ability
include CanCan::Ability
def initialize(user)
can :show, :post # GET /posts/:post_id は誰でも実行可
if user.admin? # admin ユーザーの権限を追加で定義
can :update, :post # PUT /posts/:post_id は admin ユーザーだけが実行可
end
end
end
権限の確認
API の実行を制御するのが目的なので、ビューではなくコントローラーで確認します。
class PostsController < ApplicationController
authorize_resource class: false
def show # GET /posts/:post_id で実行する API
# before_action で以下を実行
# authorize! :show, :post
end
def update # PUT /posts/:post_id で実行する API
# before_action で以下を実行
# authorize! :update, :post
end
end
load_and_authorize_resource
ではなく authorize_resource class: false
を使うのが鍵になります。
authorize_resource class: false
を使う理由
例として PostsController
の show
の場合で考えると、authorize_resource
は以下のようなロジックで動作する before_action
を追加するようです。
(ソースをきちんと追ったわけではないので、厳密には違うかもしれません)
if @post にリソースが代入されている?
authorize! :show, @post
elsif 'class: false' が指定されていない?
authorize! :show, Post
else
authorize! :show, :post
end
load_and_authorize_resource
を使用すると @post
にリソースが入ってしまうため、@post
の権限チェックが走ってしまいます。
また、class: false
を指定しないとモデル Post の権限チェックが走ってしまいます。
以上が authorize_resource class: false
を使う理由です。
おわりに
API の実行権限を管理する方法をネットで調べてみましたが、以外にもなかなか見つからなかったので今回まとめてみました。
もし間違い等ありましたらお気軽にコメントください。
マイクロサービスの流行に伴い、こういった要件が増えていくかと思います。
もし同じような状況に陥った方がいたら、参考にしていただければ幸いです。