Railsアプリケーションのユーザー権限を管理する完全ガイド


この記事はもともとは書かれていたRenata MarquesHoneybadger Developer Blog .
Webアプリケーションの一般的な要件は、特定のロールと権限を割り当てる能力です.
多くの種類のウェブアプリケーションは、制限されたアクセスを提供する際に管理者と通常のユーザーを区別します.これは、ユーザーが管理者であるかどうかを判断する単純なBooleanを使用して行われます.しかし、ロールとパーミッションは、はるかに複雑になることができます.
アプリケーションの値は、特定のデータとアクションへのアクセスを制限します.それは間違いなく何かを台無しにしたくないです.このポストでは、基本的なRuby on Railsアプリケーションでロールとパーミッションを実装する方法について説明します.

許可を管理するために宝石が必要ですか?


いいえ、特にあなたのアプリケーションが小さいならば、あなたは宝石を必要としません、そして、あなたはコードベースにより多くの依存関係を加えるのを避けたいです.
しかし、選択肢を探している場合は、ロールとパーミッションを扱う最も人気のあるGemです.
  • Devise
    deviseは認証とロール管理のための宝石です、そして、それは本当に複雑で堅牢な解決です.
    Githubで21.7 Kの星で、それはこのポストで最も人気のあるレポです、しかし、それは役割管理より多くをします.これは、認証ソリューションとして知られているので、非常に堅牢なライブラリが必要な場合は、コードベースに適用されます.
  • Pundit :
    PunditはシンプルなRubyオブジェクトを使用する宝石です、そして、おそらく、我々はカバーする最も簡単な方針宝石です.使用するのが簡単で、最小限の承認をして、純粋なルビーを使用することに似ています.Githubの上で7.3 Kの星で、それは現在最も人気のある政策宝石です.
  • CanCan :
    Cancanは、指定されたユーザーがアクセスできるようにするリソースを制限する認証ライブラリです.しかし、カンカンは何年も放棄されており、唯一のレール3と以前のリリースで動作します.
  • CanCanCan :
    CancancanはRubyおよびRuby on Rails用の別の認証ライブラリです.それはCancanに代わるもので、現在維持されています.Githubの上で4.9 Kの星で、それは最も人気があります、しかし、それはかなりよく働き、よく維持されます.
  • これらのすべての宝石は素晴らしいですが、プレーンRubyで自分自身を許可するのは難しくありません.方針オブジェクトパターンと呼ばれる戦略を使用して、宝石なしで許可を管理する方法をあなたに教えます.

    ポリシーオブジェクトパターン


    Policyオブジェクトは、パーミッションとロールを処理するためのデザインパターンです.あなたは何か誰かがアクションを実行するために許可されているかどうかを確認する必要があるたびにそれを使用することができます.それは複雑なビジネスルールをカプセル化し、簡単に別のルールを持つ他のポリシーオブジェクトに置き換えることができます.すべての外部依存関係は、ポリシーオブジェクトに注入され、許可チェックロジックをカプセル化します.
    これは、きれいなコントローラとモデルに終わります.Pundit、Cancan、およびCancananのような宝石はこのパターンを実装することができます.

    純粋なポリシーオブジェクト規則

  • 返り値はboolean値でなければなりません
  • 論理は単純でなければならない
  • メソッドの内部では、渡されたオブジェクトのメソッドのみを呼び出す必要があります
  • 実装


    命名規則から始めましょうファイル名が_policy 最後に適用される接尾辞とクラスと方針.
    このメソッドでは、? 文字(例えば、UsersPolicy#allowed? ).
    以下にコード例を示します:
    class UsersPolicy
      def initialize(user)
        @user = user
      end
    
      def allowed?
        admin? || editor?
      end
    
      def editor?
        @user.where(editor: true)
      end
    
      def admin?
        @user.where(admin: true)
      end
    end
    

    どのシナリオで私はそれらを使用する必要がありますか?


    あなたのアプリケーションが制限されたアクセスと制限された行動の1つ以上のタイプを持つとき.例えば、以下のように投稿を作成できます.
  • 少なくとも一つのタグ
  • 管理者と編集者だけがそれらをつくることができる制限
  • エディタが検証される必要がある要件.
  • 以下に例を示します.
    class PostsController < ApplicationController
      def create
        if @post.tag_ids.size > 0
        && (current_user.role == admin
        || (current_user.role == editor && current_user.verified_email))
          # create
        end
      end
    end
    
    上記の条件チェックは長く、醜い、読みにくいので、ポリシーオブジェクトパターンを適用する必要があります.
    まず始めましょうPostsCreationPolicy .
    class PostsCreationPolicy
      attr_reader :user, :post
    
      def initialize(user, post)
        @user = user
        @post = post
      end
    
      def self.create?(user, post)
        new(user, post).create?
      end
    
      def create?
        with_tags? && author_is_allowed?
      end
    
      private
    
      def with_tags?
        post.tag_ids.size > 0
      end
    
      def author_is_allowed?
        is_admin? || editor_is_verified?
      end
    
      def is_admin?
        user.role == admin
      end
    
      def editor_is_verified?
        user.role == editor` && user.verified_email
      end
    end
    
    ポリシーオブジェクトを持つコントローラは以下のようになります.
    class PostsController < ApplicationController
      def create
        if PostsCreationPolicy.create?(current_user, @post)
          # create
        end
      end
    end
    

    レールで方針オブジェクトを使う方法


    アプリケーション内のポリシーディレクトリを作成する/policies すべてのポリシークラスを配置します.ときに、コントローラを呼び出す必要がある場合は、直接行動するか、またはbefore_action :
    class PostsController < ApplicationController
      before_action :authorized?, only: [:edit, :create, :update, :destroy]
    
      def authorized?
        unless ::PostsCreationPolicy.create?(current_user, @post)
          render :file => "public/404.html", :status => :unauthorized
        end
      end
    end
    

    ポリシーオブジェクトをテストする方法


    コントローラの動作をテストするのは簡単です.
    require 'rails_helper'
    
    RSpec.describe "/posts", type: :request do
      describe "when user is not allowed" do
        let(:user_not_allowed) { create(:user, admin: false, editor: false) }
        let(:tag) { create(:tag) }
        let(:valid_attributes) { attributes_for(:post, tag_id: tag.id) }
    
        before do
          sign_in user_not_allowed
        end
    
        describe "GET /index" do
          it "return code 401" do
            diet = Post.create! valid_attributes
            get edit_post_url(post)
            expect(response).to have_http_status(401)
          end
        end
      end
    end
    
    ポリシーのテストも簡単です我々には、1つの責任だけの小さい方法がたくさんあります.
    require 'rails_helper'
    
    RSpec.describe PostsCreationPolicy do
      describe "when user is not allowed" do
        let(:user) { create(:user, editor: false, admin: false) }
        let(:user_editor) { create(:user, editor: true, email: verified) }
        let(:tag) { create(:tag) }
        let(:post) { create(:post, tag_id: tag.id) }
    
        describe ".create?" do
          context "when user is allowed" do
            it "creates a new post" do
              expect(described_class.create?(user_editor, post)).to eq(true)
            end
          end
    
          context "when user is not allowed" do
            it "does not create a new post" do
              expected(described_class.create?(user, post)).to eq(false)
            end
          end
        end
    
        # ...more test cases
      end
    end
    
    我々は、オブジェクトがすべてのシナリオで作成されるかどうかテストします.

    結論


    ポリシーパターンの概念は小さいが、大きな結果を生む.
    単純または複雑なアクセス許可に対処するたびにポリシーオブジェクトを適用することを検討してください.rspecでテストするとき、データベースレコードを使う必要はありませんポリシー
    純粋にRubyオブジェクトで、テストが簡単で高速になります.