LoginSignup
1
0

More than 1 year has passed since last update.

【Rails】cancancanのソースコードを読んでみた(authorize!メソッド)

Posted at

ふと思いつきで、普段使っているcancancanのgemのソースコードを読んでみようと思いました。
今回はcontrollerにbefore_actionauthorize!メソッドが書かれたときの流れを追ってみました。

バージョン

cancancan 3.5.0

処理の流れ

まず、以下のauthorize!メソッドでcurrent_abilityの設定(Abilityファイルの読み込み)を行います。

gems/cancancan-3.5.0/lib/cancan/controller_additions.rb
def authorize!(*args)
  @_authorized = true
  current_ability.authorize!(*args)
end

current_abilityメソッドでAbility.newを行うようになっています。

gems/cancancan-3.5.0/lib/cancan/controller_additions.rb
def current_ability
  @current_ability ||= ::Ability.new(current_user)
end

can :xxx, XXXの記述があった場合に、それらをadd_ruleとして収集していきます。

gems/cancancan-3.5.0/lib/cancan/ability.rb
def can(action = nil, subject = nil, *attributes_and_conditions, &block)
  add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
end
gems/cancancan-3.5.0/lib/cancan/ability/rules.rb
def add_rule(rule)
  rules << rule
  add_rule_to_index(rule, rules.size - 1)
end

その後、abilityインスタンスのメソッドのauthorize!メソッドが呼ばれます。
actionには:indexなど操作したい内容が、subjectには権限チェックの対象が入っています。

そのsubjectに対して権限がない(cannnot?true)場合にはエラーをraiseするようになっています。

gems/cancancan-3.5.0/lib/cancan/ability.rb
def authorize!(action, subject, *args)
  message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
  if cannot?(action, subject, *args)
    message ||= unauthorized_message(action, subject)
    raise AccessDenied.new(message, action, subject, args)
  end
  subject
end

cannnot?メソッドはcan?メソッドの結果を反転させているだけでした。

gems/cancancan-3.5.0/lib/cancan/ability.rb
def cannot?(*args)
  !can?(*args)
end

extract_subjects(subject)では権限チェックの対象を配列に整えています。
relevant_rules_for_match(action, a_subject)で、事前にユーザーがability.rbで記載している権限とマッチするものを探して配列にしています。

gems/cancancan-3.5.0/lib/cancan/ability.rb
def can?(action, subject, attribute = nil, *extra_args)
  match = extract_subjects(subject).lazy.map do |a_subject|
    relevant_rules_for_match(action, a_subject).detect do |rule|
      rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute)
    end
  end.reject(&:nil?).first
  match ? match.base_behavior : false
end

権限チェック対象の渡し方に応じて配列の整え方を変えています。
参考: 【Rails/cancancan】authorize!で、複数のリソースのいずれかに対する権限があれば操作を許可したいときの方法

gems/cancancan-3.5.0/lib/cancan/ability.rb
# It translates to an array the subject or the hash with multiple subjects given to can?.
def extract_subjects(subject)
  if subject.is_a?(Hash) && subject.key?(:any)
    subject[:any]
  else
    [subject]
  end
end

relevant_rules_for_match(action, a_subject)で、事前にユーザーがability.rbで記載している権限とマッチするものを探して配列にしています。

gems/cancancan-3.5.0/lib/cancan/ability/rules.rb

def relevant_rules_for_match(action, subject)
  relevant_rules(action, subject).each do |rule|
    next unless rule.only_raw_sql?

    raise Error,
          "The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
          "The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
  end
end

relevant_rules(action, subject)にて、@rulesには、最初にadd_rulesメソッドが呼ばれたときの、ユーザーが記載した権限が格納されています。

gems/cancancan-3.5.0/lib/cancan/ability/rules.rb
def relevant_rules(action, subject)
  return [] unless @rules

  relevant = possible_relevant_rules(subject).select do |rule|
    rule.expanded_actions = expand_actions(rule.actions)
    rule.relevant? action, subject
  end
  relevant.reverse!.uniq!
  optimize_order! relevant
  relevant
end

possible_relevant_rulesメソッドでは、継承等含めて関連のリソースを拾ってきていました。
参考: 【Rails】cancancanでは継承元のclassを指定してまとめて権限設定することが可能

def possible_relevant_rules(subject)
  if subject.is_a?(Hash)
    rules
  else
    positions = @rules_index.values_at(subject, *alternative_subjects(subject))
    positions.compact!
    positions.flatten!
    positions.sort!
    positions.map { |i| @rules[i] }
  end
end

以上です。
このソースコードを読んだことをきっかけでcancancanの知らない機能を知れたりできてよかったです。

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