Rails
banken

権限管理のgemをbankenに乗り換えてみた

More than 3 years have passed since last update.

この記事をみて、さっそくbankenの乗り換えてみた。


準備

bankenのREADME.mdにある通りGemfileに追記して、


Gemfile

gem 'banken'


gemとApplicationLoyaltyをインストールをする。

$ bundle install

$ bundle exec rails g banken:install


乗り換えるにあたっての手抜き

乗り換えるにあたり、なるべくコード量を減らしたかったので、手抜きのためのコードを書いた。


ApplicationLoyalty#allow_all?

「この権限ならこのコントローラー全部アクセスできるでしょ!」っていうのがあると思うので、ApplicationLoyaltyallow_all?というメソッドを追加して、各メソッドはその値を参照するようにした。


app/loyalties/application_loyalty.rb

class ApplicationLoyalty

attr_reader :user, :record

def initialize(user, record)
@user = user
@record = record
end

def index?
allow_all?
end

def show?
allow_all?
end

def create?
allow_all?
end

def new?
create?
end

def update?
allow_all?
end

def edit?
update?
end

def destroy?
allow_all?
end

private

def allow_all?
false
end
end


使うときはこんな感じ。「この権限なら」の権限毎にallow_all?を定義したモジュールを用意してインクルードしてる。

# app/loyalties/concerns/allow_no_signed_in_loyal.rb

module AllowNotSignedInLoyal
def allow_all?
true
end
end

# app/loyalties/concerns/allow_singed_in_loyal.rb
module AllowSignedInLoyal
def allow_all?
user.present?
end
end

# app/loyalties/welcome_loyalty.rb
class WelcomeLoyalty < ApplicationLoyalty
include AllowNotSignedInLoyal
end

# app/loyalties/posts_loyalty.rb
class PostsLoyalty < ApplicationLoyalty
include AllowSignedInLoyal

# 条件個別に定義することもできる。
def index?
true
end
end

「ログインしていればOK」「管理者権限があればOK」とかそういう大味な権限管理の場合に使う。


アクションのメソッドを定義するのをやめる


  • いちいちアクションを定義するのがだるい1

  • 水際で食い止めるのはallow_all?でいいじゃん

という気持ちがあったので、method_missingBanken#banken_query_name(実際は?で終わるメソッド)に自動で応答するようにした。


app/loyalties/concerns/dynamic_loyal.rb

module DynamicLoyal

def method_missing(method_name, *argments, &block)
if method_name.to_s.end_with?('?')
allow_all?
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
method_name.to_s.end_with?('?') || super
end
end


やんちゃなのでApplicationLoyaltyでincludeしちゃう。


app/loyalties/application_loyalty.rb

class ApplicationLoyalty

include DynamicLoyal

# snip ...
end



権限は専用のオブジェクトにまとめて移譲する。

bankenは「権限とコントローラー、ビューの紐付けをするgem」で「権限自体を定義するgemではない」って認識なので、HogeLoyaltyに具体的な権限の定義を書くのはやめてAbilityのようなクラスにまとめて定義した。2


app/models/ability.rb

class Ability

attr_reader :user

def initialize(user)
@user = user
end

def not_singed_in?
true
end

def signed_in?
user.present?
end

def admin?
user.try(:admin?)
end

def update_post?(post)
admin? || post.unpublished?
end
end


確認はAbilityに委譲する。

class ApplicationLoyalty

def ability
@ability ||= Ability(user)
end
end

module AllowSignedInLoyal
def allow_all?
ability.signed_in?
end
end

class PostsLoyalty < ApplicationLoyalty
include AllowSignedInLoyal

def index?
ability.not_singed_in?
end

def update?
ability.update_post?(record)
end
end


テストをかく

愚直に書くとcontroller_nameaction_nameが分離してしまってわかりにくいので

describe 'loyalty' do

context 'user' do
let(:user) { create(:user) }
it { expect(Banken.loyalty!(:posts, user, create(:post))).to be_update }
end
end

ヘルパークラス書いた。

# spec/support/banken_permission.rb

class BankenPermission
attr_reader :user

def initialize(user)
@user = user
end

def allow?(controller_name, action_name, record = nil)
Banken.loyalty!(controller_name, user, record).public_send("#{action_name}?")
end
end

describe 'loyalty' do
context 'user' do
subject { BankenPermission.new(create(:user)) }
it { should be_allow(:posts, :update, create(:post)) }
end
end


乗り換えてみて

乗り換え前は#386 Authorization from Scratch Part 2コードを使っていた。これと比べると、

Pros.


  • 権限周りの修正の影響範囲が狭くなった。


    • コントローラーごとに別クラス、別ファイルなので影響しにくい

    • コンフリクトに強そう。diffが見やすそう。



  • 定義順に依存とかそういうのを気にする必要が無くなった。

Cons.


  • コントローラーの数だけLoyaltyを用意するし記述が冗長。


    • 冗長だけど、3行で済むLoyaltyも多いので何とかなりそう。







  1. アクセス制御にあるまじき発想だけど、、、許して、、 



  2. Abilityの実装にはcancancansix(gitlabの権限管理gem)のような権限を定義するgemが使える。