Rails4系からデフォルトで組み込まれたStrongParametersについていろいろ調べてみたのでまとめてみました。
StrongParametersについて概要
StrongParameters とは Mass Assignment 脆弱性対策のための機能です。おおまかに言うと、Rails3系ではModelでattr_accessible
を使って制御していたものとは違い、Controller側で受け取ったパラメータを制御するものです。
Mass Assignemt対策やStrong Parametersについての詳細は以下がとても参考になります。ありがとうございます。
- Rails4のMass Assignment脆弱性対策のStrong Parametersについて
- strong_parametersについて
- 7. StrongParameters -TECHSCORE
StrongParametersで例外時の挙動
- controller側に渡された
params
にpermitの設定がないところで params["user"]とかでモデルにnew
,update
するとActiveModel::ForbiddenAttributesError
の例外が発生する。 -
params
にpermit の 設定を一つでも入れた上で、permitしていない paramsをnew
,update
「Unpermitted parameters: name」の例外が発生する。
今回StrongParametersについて調べるきっかけは
Rails4のアプリケーションを作ろうとしている時に、管理機能ツールとして ActiveAdmin の初期化したあとに 「admin_user」というModelを更新するテストしていた時にStrongParameterで以下のエラーが出ていたことです。
Unpermitted parameters: utf8, authenticity_token, commit
ActiveAdminを初期化したときはapp/admi/admin_user.rb
が生成され、その中に以下のStrongParameterに対応したコードができます。
ActiveAdmin.register AdminUser do
index do
column :email
column :current_sign_in_at
column :last_sign_in_at
column :sign_in_count
default_actions
end
filter :email
form do |f|
f.inputs "Admin Details" do
f.input :email
f.input :password
f.input :password_confirmation
end
f.actions
end
controller do
def permitted_params
params.permit admin_user: [:email, :password, :password_confirmation]
end
end
end
permitted_params
メソッドがStrongParametersに対応したコードです。
ActiveAdminは Inherited_resources というCRUDの処理に特化したコントローラを簡単に実装できるGemに依存しているので、 permitted_params というメソッドの中身をコントローラで定義することでStrongParametersに対応させることができるようです。active_adminでは 「 controller do 」 ブロックに定義することで対応させます。
【参考】 active_admin本家 permitted_paramsメソッドを生成するGeneratorのコード
【参考】inherited_resources の permitted_params を定義しているコードinherited_resources/base_helpers.rb
しかし、ActiveAdmin初期化時に生成された permitted_params メソッドの中身では、上述したように Unpermitted parameters が発生してしまいます。
ここからはその部分を確認してみます。
Unpermitted parameters がなぜ発生するのか、ActionController::Parameters
クラスの挙動を色々見て行きます。
ActionController::Parameters をREPL(irb や pry)を使って確認
まずはparamsとして ActionController::Parameters
のクラスを作る
:form
以下のハッシュデータ(:email, :admin) が実際のformからPostされたデータと仮定して、Mass Assignment
したい(permitする対象にしたい)という状況を作る。
:name
と :admin
は 付加情報としてparamsに入っているだけ。
hash_params = {
:name=>"John",
:admin=>true,
:form=>{:email=>"hoge@hoge.com", :pass=>"hogehoge"}
}
# ActionController::Parametersクラスを作る
params = ActionController::Parameters.new(hash_params)
permit1 = params.permit(form: [:email, :pass]) #「Unpermitted parameters: name, admin」が発生
permit1.permitted? #=> true
puts permit1 #=> {"form"=>{"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}}
# これだとparamsの「:form=>{:email, :pass}」がpermit1に入り、permitted?もtrueになるが、、「permit()」メソッドを実行したタイミングでparamsで「:form」と同階層にある「:name」「:admin」がUnpermitted parametersとなる。
# ①でpermitした:formに加えて:name, :adminもpermitさせると
permit2 = params.permit(:name, :admin, form: [:email, :pass]) # 「Unpermitted parameters」は発生しない
permit2.permitted? #=> true
puts permit2 #=> {"name"=>"John", "admin"=>true, "form"=>{"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}}
# 当然ながらpermit2にはparams全てのデータが入り、permitted?もtrueになる。「Unpermitted parameter」は発生しない。:form以下のみpermitしたいのにこれだと、なんか違う気もする。
# requireメソッドでparams内のハッシュキーを特定して、それ以下のみをpemitさせる方法
permit3 = params.require(:form).permit(:email,:pass) # 「Unpermitted parameters」は発生しない
permit3.permitted? #=> true
puts permit3 #=> {"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}
# Unpermitted parametersは発生せず目的の「:form」のみがpermit3に入り、permitted?もtrueになる。
# また、以下のようにすると「Unpermitted parameters」は発生する
permit4 = params.require(:form).permit(:email) #=> Unpermitted parameters: pass
# :form=> :emailと同階層の:passを指定していないパターン。:passについてUnpermitted parametersが発生
以上から、strong_parametersでpermitする場合は、requireしてpermitするという方法が一番使い回しがきくなーという感じですねー。ま~適宜用途にあった使いかたをしていくのがいいとは思いますが、、
実際の実装例
実際の実装例というか自分の設定している定義方法を示します。
1.ActiveAdmin Controllerの場合
冒頭にactive_admin:installで自動生成されたadmin_user.rbの controller do
ブロックの中を書き換えます。
ActiveAdmin.register AdminUser do
…省略
controller do
def permitted_params
{admin_user: params.require(:admin_user).permit(:email, :password, :password_confirmation)}
end
end
…省略
end
2.通常のRailsのコントローラの場合
ModelがUser
で email
と password
を持っている場合を想定して書きます。
2-1. InheritedResources::Base
を継承している時
rails generate scaffold
コマンドで生成した時はinherited_resources
を継承したコントローラクラスになるので、以下のようにpermitted_params
メソッドを定義します。
class UsersController < InheritedResources::Base
private
def permitted_params
{:user => params.require(:user).permit(:email, :password)}
# params.permit! #<=すべてのparamを許可するとき
end
end
inherited_resources
を使っているときは特に CRUDに関するメソッド(create, updateメソッドなど)は内部で実装されているため特に定義しなくても動作する。
2−2. ApplicationController
を継承している時
rails generate controller
や rails generate scaffold_controller
コマンドで生成した時はApplicationController
を継承したコントローラクラスになるので以下のように指定します。
privateなuser_params
メソッドで独自にpermitするカラムを指定する
class UsersController < ApplicationController
…省略
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.json { render action: 'show', status: :created, location: @user }
else
format.html { render action: 'new' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
…省略
private
def user_params
params.require(:user).permit(:email, :password)
end
…省略
end
2.通常のRailsのコントローラの場合
の時は個人的には ApplicationController
を継承した 2−2 の方法が使いやすいかな〜と思っています。InheritedResources::Base
を継承した 2−1 の方法はコード量は少ないけどコントローラの処理が隠蔽されすぎていてわかりにくくなるので。
参考
InheritedResources
を使ったStrongParametersの定義方法は正規のドキュメントに書かれていました。
3つの方法があるようです。私は2番めのrequireを使ったHashで返す方法を利用しています。
https://github.com/josevalim/inherited_resources#strong-parameters
おまけ
おまけ1 Unpermitted parametersの時の振る舞いを設定する
StrongParametersの Unpermitted Parameters
が発生した時はデフォルトではLogに書きだされる設定になっているが例外を吐くようにすることもできる。application.rb
に設定する。
…省略
# StrongParameters unpermitted parameters behavior
config.action_controller.action_on_unpermitted_parameters = :raise # :raise か :log で設定する
…省略
おまけ2 Unpermitted parametersのログを色付けする
colorize_unpermitted_parameters
というGemをインストールするだけ。Gemfileに以下を定義してbundle install
gem 'colorize_unpermitted_parameters'
以上、長くなったけど、自分の備忘録として。