LoginSignup
6
5

More than 1 year has passed since last update.

devise ストロングパラメータの仕組みを調べてみた。

Last updated at Posted at 2021-10-13

はじめに

サインアップページにdeviseのデフォルトで許可されている要素ではない、"username"を追加することになり、deviseでのストロングパラメータの設定方法を調べた時のことを備忘録として投稿します。

ストロングパラメータとは

特定のパラメーターのみを許可することで、モデルへの不正なパラメータの流入を防ぐ仕組みのこと。MassAssingment脆弱性というセキュリティ上の問題に対処するための仕組みとしてRails4系から導入されましたものです。

deviseにおいては、モデルにパラメータを送ることのできるアクションは、3つに限定されており、パラーメータも以下のように限定されています。

action allowed params
sign_in authentication keys
sign_up authentication keys, password,password_confirmation
account_update authentication keys,password, password_confirmation,current_password

参考:Strong Parameters

サインアップ時のストロングパラメータの設定

サインアップ時にデフォルトで許可されているパラメータは、以下の3つです。

  1. authentication keys(認証キー)
  2. password(パスワード)
  3. password_confirmation(パスワード確認用)

※authentication keys(認証キー)のデフォルト値は、emailです。

config/initializers/divise.rb
# ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. 
  ....中略.....
  # config.authentication_keys = [:email]

これら以外を許可したい場合は、ApplicationControllerに設定する必要があります。以下は、サインアップアクションでusernameの値をパラメータとして許可する設定です。

application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end
end

これで一見落着!

と思ったのですが、一つ気になることがありました。

ログイン時のストロングパラメータの設定

何が気になったのかというと、ログイン(sign_in
)アクションの許可されているパラメータについてです。もう一度見てみます。

action allowed params
sign_in authentication keys

参考:Strong Parameters
あれ??authentication keysだけなの???
先述した通り、authentication keysのデフォルト値は、:emailです。つまり、許可されたパラメータは、:emailのみということでしょうか。
しかし、ログイン認証時には、サインアップの際に登録したパスワードと、ログインフォームから送られてきたパスワードを照合して、ログイン認証を行います。とすると、passwordも許可されているはずです。
このモヤモヤを解消するべく、deviseのストロングパラメータもう少し詳しく確認しました。

1.configure_permitted_parametersメソッドの確認

application_controller.rb
def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end

1. devise_parameter_sanitizer

devise/lib/devise/controllers/helpers.rb
# Set up a param sanitizer to filter parameters using strong_parameters. See
      # lib/devise/parameter_sanitizer.rb for more info. Override this
      # method in your application controller to use your own parameter sanitizer.
      def devise_parameter_sanitizer
        @devise_parameter_sanitizer ||= Devise::ParameterSanitizer.new(resource_class, resource_name, params)
      end

Set up a param sanitizer to filter parameters using strong_parameters. See lib/devise/parameter_sanitizer.rb for more info. Override this method in your application controller to use your own parameter sanitizer.
(ストロングパラメータを使用してパラメータをフィルタリングするための、パラメータのサニタイザーを設定します。詳細については、lib/devise/parameter_sanitizer.rbをご覧ください。ご自身で設定したパラメータのサニタイザーを使いたい場合は、このメソッドをapplication controllerでオーバーライドしてください。)

メソッドの処理は、
Devise::ParameterSanitizerクラスをインスタンス化しています。
詳細については、lib/devise/parameter_sanitizer.rbをご覧ください。とのことでしたので、確認してみます。

2. lib/devise/parameter_sanitizer.rb

すると、デフォルトの設定が見つかりました!!

devise/lib/devise/parameter_sanitizer.rb
  DEFAULT_PERMITTED_ATTRIBUTES = {
      sign_in: [:password, :remember_me],
      sign_up: [:password, :password_confirmation],
      account_update: [:password, :password_confirmation, :current_password]
    }

ちゃんとpasswordが許可リストに入っています。それとremember_meもデフォルトで許可されているようです。
ちなみに、認証キーの情報については、以下の @auth_keys にありました。

devise/lib/devise/parameter_sanitizer.rb
 def initialize(resource_class, resource_name, params)
      @auth_keys      = extract_auth_keys(resource_class)
      @params         = params
      @resource_name  = resource_name
      @permitted      = {}

      DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
        permit(action, keys: keys)
      end
    end

ここまでで、deviseのデフォルトのストロングパラメータがわかりました。

ストロングパラメータがどのように追加されるのかを確認してみましたので、以下に記載します。

devise ストロングパラメータの仕組み

1.Devise::ParameterSanitizerクラスからインスタンス作成

devise/lib/devise/controllers/helpers.rb
      def devise_parameter_sanitizer
        @devise_parameter_sanitizer ||= Devise::ParameterSanitizer.new(resource_class, resource_name, params)
      end

引数について

引数を理解するために、まずdevise_mappingについて確認します。

devise/app/controllers/devise_controller.rb
# Attempt to find the mapped route for devise based on request path
  def devise_mapping
    @devise_mapping ||= request.env["devise.mapping"]
  end

リクエストパスに基づいたマッピングルートを探してくれるメソッドです。
request.env["devise.mapping"]には、deviseのマッピング情報が格納されています。具体的には、routes.rbの"devise_for"で作成された、マッピングオブジェクトの情報が格納されています。

今回は、devise_for :usersとしています。

map.devise_for :users mapping = Devise.mappings[:user] mapping.name #=> :user # is the scope used in controllers and warden, given in the route as :singular. mapping.as #=> "users" # how the mapping should be search in the path, given in the route as :as. mapping.to #=> User # is the class to be loaded from routes, given in the route as :class_name. mapping.modules #=> [:authenticatable] # is the modules included in the class

参考:Class: Devise::Mapping

それでは、引数を確認していきます。

1.resource_class

devise/app/controllers/devise_controller.rb
  # Proxy to devise map class
  def resource_class
    devise_mapping.to
  end

このメソッドでは、クラスネームが返されます。
Userが返されます。

2.resource_name

devise/app/controllers/devise_controller.rb
# Proxy to devise map name
  def resource_name
    devise_mapping.name
  end
  alias :scope_name :resource_name

このメソッドでは、コントローラーで使用されているスコープ名が「:単数形」で返されます。
:userが返されます。

3.params

リクエストからのパラメータ情報

2.ストロングパラメータの初期化処理

クラスからインスタンスが作成(new)されたことがトリガとなって、initializeメソッドが呼び出されます。newメソッドの引数をそのまま受け取ります。
まず、インスタンス変数の値をそれぞれ確認していきましょう。

devise/lib/devise/parameter_sanitizer.rb
 def initialize(resource_class, resource_name, params)
      @auth_keys      = extract_auth_keys(resource_class)
      @params         = params
      @resource_name  = resource_name
      @permitted      = {}

      DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
        permit(action, keys: keys)
      end
    end

1. @auth_keys

extract_auth_keys(resource_class)の値が入っています。

devise/lib/devise/parameter_sanitizer.rb
   def extract_auth_keys(klass)
      auth_keys = klass.authentication_keys

      auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys
    end

auth_keysに引数のauthentication_keysの値を代入しています。
引数(klass)の値は、resource_classの値(User)です。

devise/lib/devise/strategies/authenticatable.rb
      def authentication_keys
        @authentication_keys ||= mapping.to.authentication_keys
      end

@authentication_keysにマッピングオブジェクトの認証キー情報を代入しています。

つまり、Userクラス(モデル)の認証キー情報を取得しているということですね。
※認証キーのデフォルト値は、:emailです。

The default is
# just :email.

2.@params

引数のparamsの情報(リクエストパラメータ)が入っています。

3.@resource_name

引数のresource_nameの値(:user)が入っています。

4.@permitted

空の配列が入ります。この配列に、この後アクション名をキーにした、ストロングパラメータの値がこの配列の中に格納されます。

3.パラメータのホワイトリスト(ストロングパラメータ)の作成

initializeメソッドの以下の部分がストロングパラメータの設定の部分にあたります。

devise/lib/devise/parameter_sanitizer.rb
 DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
        permit(action, keys: keys)
      end

1.each_pairメソッドについて

each_pairメソッド は eachメソッドの別名(エイリアス)です。
ハッシュのキーと値を引数としてブロックを評価します。
DEFAULT_PERMITTED_ATTRIBUTESはハッシュ形式でデータが格納されていますので、ハッシュ用のeachメソッドを用います。

参考:instance method Hash#each

2.permitメソッドについて

devise/lib/devise/parameter_sanitizer.rb
    # Add or remove new parameters to the permitted list of an +action+.
    #
    # === Arguments
    #
    # * +action+ - A +Symbol+ with the action that the controller is
    #   performing, like +sign_up+, +sign_in+, etc.
    # * +keys:+     - An +Array+ of keys that also should be permitted.
    # * +except:+   - An +Array+ of keys that shouldn't be permitted.
    # * +block+     - A block that should be used to permit the action
    #   parameters instead of the +Array+ based approach. The block will be
    #   called with an +ActionController::Parameters+ instance.
    #
    # === Examples
    #
    #   # Adding new parameters to be permitted in the `sign_up` action.
    #   devise_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
    #
    #   # Removing the `password` parameter from the `account_update` action.
    #   devise_parameter_sanitizer.permit(:account_update, except: [:password])
    #
    #   # Using the block form to completely override how we permit the
    #   # parameters for the `sign_up` action.
    #   devise_parameter_sanitizer.permit(:sign_up) do |user|
    #     user.permit(:email, :password, :password_confirmation)
    #   end
    #
    #
    # Returns nothing.
    def permit(action, keys: nil, except: nil, &block)
      if block_given?
        @permitted[action] = block
      end

      if keys.present?
        @permitted[action] ||= @auth_keys.dup
        @permitted[action].concat(keys)
      end

      if except.present?
        @permitted[action] ||= @auth_keys.dup
        @permitted[action] = @permitted[action] - except
      end
    end

説明書きによると、
アクションごとに、パラメータの許可リストに新たなパラメータを追加または、削除するメソッドです。
引数については、以下の4つをとります。

  1. action(シンボル形式のアクション名)
  2. keys( 許可するパラメータの配列)
  3. except(許可しないパラメータの配列)
  4. block(アクション自体の許可パラメータをオーバーライドする用)

戻り値は、何もありません。内部的に許可パラメータを設定する処理を行うものということですね。

処理としては、
引数の条件によって、initializeメソッドで定義した、@permittedの空配列にアクション名をキーに、許可するパラメータを値としてデータをハッシュ形式で格納します。

1.blockの場合

ブロックの値をそのまま配列に格納

2.keysが存在する場合(許可パラメータを追加)

@permitted[action]の"action"にDEFAULT_PERMITTED_ATTRIBUTESのアクション名を代入。
@permitted[action]に、dupメソッドで@auth_keys(認証キー)から複製したオブジェクトを代入。
@permitted[action]に、concatメソッドで配列を追加

例(sign_inアクション)
@permitted[:sign_in] = [:email,:password,:remember_me]
hashにすると以下のようになる。
{:sign_in=>[:email,:password,:remember_me]}

3.exceptが存在する場合(許可パラメータを削除)

@permitted[action]の"action"にDEFAULT_PERMITTED_ATTRIBUTESのアクション名を代入。
@permitted[action]に、dupメソッドで@auth_keys(認証キー)から複製したオブジェクトを代入。
※認証キーは必須のため。
@permitted[action]から引数exceptの値を控除する。

まとめ

devise/lib/devise/parameter_sanitizer.rb
  DEFAULT_PERMITTED_ATTRIBUTES = {
      sign_in: [:password, :remember_me],
      sign_up: [:password, :password_confirmation],
      account_update: [:password, :password_confirmation, :current_password]
    }
devise/lib/devise/parameter_sanitizer.rb
 def initialize(resource_class, resource_name, params)
      @auth_keys      = extract_auth_keys(resource_class)
      @params         = params
      @resource_name  = resource_name
      @permitted      = {}

      DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
        permit(action, keys: keys)
      end
    end

deviseのデフォルトのストロングパラメータの値は、ハッシュ形式でDEFAULT_PERMITTED_ATTRIBUTESに格納されている。
deviseインストールをすると、Devise::ParameterSanitizerクラスからインスタンスが作成され、それがトリガとなり、initializeメソッドが実行される。
initializeメソッドでは、スコープの認証キーの情報を取得し、permitメソッドで、取得した認証キーの情報と、DEFAULT_PERMITTED_ATTRIBUTESのデータからパラメータの許可リストを作成する。ここで作成される許可リストは、アクション名をキーに持ったハッシュ形式のリストである。

例(sign_inアクション)
@permitted[:sign_in] = [:email,:password,:remember_me]
hashにすると以下のようになる。
{:sign_in=>[:email,:password,:remember_me]}

参考

heartcombo/devise
Strong Parameters
Class: Devise::Mapping
Class: Devise::ParameterSanitizer
instance method Hash#each
dup
concat

6
5
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
6
5