1
0

More than 3 years have passed since last update.

cancancan で API ベースの制御を行う方法

Posted at

概要

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 でログインユーザーを参照できる必要があります。
DeviseAuthlogic といった認証用の 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, @postauthorize! :read, @post では同じ結果が得られます。
cf. Action Aliases - cancancan

load_and_authorize_resourceload_resourceauthorize_resource の 2 つに分けることができます。
上記例だと、load_resource@post = Post.find(params[:post_id]) の部分の before_action が、authorize_resourceauthorize! :show, @post の部分の before_action が追加されます。
cf. Authorizing controller actions - cancancan

API ベースの制御

今回の本題です。
例として、Post コントローラーに showupdate の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_idPUT /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 を使う理由

例として PostsControllershow の場合で考えると、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 の実行権限を管理する方法をネットで調べてみましたが、以外にもなかなか見つからなかったので今回まとめてみました。
もし間違い等ありましたらお気軽にコメントください。

マイクロサービスの流行に伴い、こういった要件が増えていくかと思います。
もし同じような状況に陥った方がいたら、参考にしていただければ幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0