ふと思いつきで、普段使っているcancancanのgemのソースコードを読んでみようと思いました。
今回はcontrollerにbefore_action
でauthorize!
メソッドが書かれたときの流れを追ってみました。
バージョン
cancancan 3.5.0
処理の流れ
まず、以下のauthorize!
メソッドでcurrent_ability
の設定(Abilityファイルの読み込み)を行います。
def authorize!(*args)
@_authorized = true
current_ability.authorize!(*args)
end
current_ability
メソッドでAbility.new
を行うようになっています。
def current_ability
@current_ability ||= ::Ability.new(current_user)
end
can :xxx, XXX
の記述があった場合に、それらをadd_rule
として収集していきます。
def can(action = nil, subject = nil, *attributes_and_conditions, &block)
add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
end
def add_rule(rule)
rules << rule
add_rule_to_index(rule, rules.size - 1)
end
その後、ability
インスタンスのメソッドのauthorize!
メソッドが呼ばれます。
action
には:index
など操作したい内容が、subject
には権限チェックの対象が入っています。
そのsubject
に対して権限がない(cannnot?
がtrue
)場合にはエラーをraiseするようになっています。
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?
メソッドの結果を反転させているだけでした。
def cannot?(*args)
!can?(*args)
end
extract_subjects(subject)
では権限チェックの対象を配列に整えています。
relevant_rules_for_match(action, a_subject)
で、事前にユーザーが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!で、複数のリソースのいずれかに対する権限があれば操作を許可したいときの方法
# 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
で記載している権限とマッチするものを探して配列にしています。
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
メソッドが呼ばれたときの、ユーザーが記載した権限が格納されています。
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の知らない機能を知れたりできてよかったです。