【注:Rails初心者記事】
記載に間違いなどがありましたら、指摘いただけると幸いです。
はじめに.この記事の目的
ストロングパラメータって何ぞや?
いろいろ調べた結果、頭の中が崩壊したので、一度記事としてまとめます。
これで、少しは理解が深まる・・・はず!!
1.ストロングパラメータって何?
ストロングパラメータは、Web上から受けつけたパラメータが、本当に安全なデータかどうかを検証した上で、取得するための仕組みです。Rails4から実装されています。
2.ストロングパラメータがなぜ必要なのか?
この仕組みを使うことで、意図しない(安全では無い)データの登録・更新を防いでくれます。
具体的にどうやって防ぐかと言うと、メソッドにあらかじめ登録・更新を許可するカラム名を指定(ホワイトリスト形式)しておきます。そうすると、万が一、未許可のカラムデータが送られてきても、データの登録前に未許可であることを検出し、登録対象として無視することができます。
不特定多数に公開するWebアプリケーションだからこそ、ストロングパラメータの仕組みは必要不可欠な訳ですね。
2−1.「マスアサインメント(Mass Assignment)機能と脆弱性」という問題
ストロングパラメータ機能が実装される以前(rails3)は、実際に意図しないデータの登録・更新が発生していたようです。実例では無いですが、どう言った問題なのか具体例をあげて整理してみました。
・ 問題発生の土壌
以下のとおり、マスアサインメント機能の脆弱性を孕んだWebアプリを作成したことを前提とします。
モデル
ユーザの管理を行うUserテーブル(モデル)
最後の「admin」カラムは管理者ユーザかどうかを識別するために作っています。
(admin=1(管理者))
user_id | name | admin | |
---|---|---|---|
1 | はむ太郎 | hamu@hamu.co.jp | 1 |
ビュー
ユーザ情報の登録を求めるビュー
下図のとおり、Userモデルのname、emailのみの入力を意図して作成しています。
「admin」カラムは管理者ページからのみ操作したいので対象としていません。
・・・中略・・・
<%= form_for @user do |f| %>
<div class="field">
<%= f.text_field :name, placeholder: "ユーザ名を入力してね!" %>
</div>
<div class="field">
<%= f.text_field :email, placeholder: "e-mailアドレスを入力してね!" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
・・・中略・・・
コントローラー
ユーザ情報の登録を行うコントローラ
マスアサインメント機能を使用し、Userモデル丸ごとデータ登録をさせるよう作成されています。
def create
user = User.new(params[:user(モデル名)])
user.save
def
・ 問題の発生契機と内容
このアプリのWeb公開後、該当のViewから下図のようなパラメータを受け取りました。最後のパラメータ"admin"は、開発者側が意図していない(安全で無い)データでした。
このパラメータは悪意あるユーザによるパラメータの改ざんによって発生したようです。
{"user"=>{"name"=>"はむ太郎","email"=>"hamu@hamu.co.jp","admin"=>1}}
^^^^^^^^^^^^^^
この結果どうなるでしょうか?
「admin」と言うカラムは管理者ユーザかどうかを識別をしています。
そのため、この悪意あるユーザが管理者権限付きのユーザIDを獲得してしまったのです!
こんな事しちゃうユーザです。その後の更なるセキュリティ攻撃に発展しそうな予感大ですね。。。
こうした問題が「マスアサインメント機能と脆弱性の問題」でした。多分。
ただ、Rails4以降はストロングパラメータの使用が必須になっていて、上であげたような記述は出来ないようです。以下に実際の事象に関する記事がありましたので、参考までに。。。
【マスアサインメント機能の脆弱性問題に関する記事】
https://www.infoq.com/jp/news/2012/03/GitHub-Compromised/
3.ストロングパラメータの適用対象
3-1.適用対象について
ユーザがフォームから入力する情報がストロングパラメータの適用対象となります。
観点としては、以下2つを覚えておくと迷わないと思います。
・対象のデータ
ユーザフォーム(View)から送られてきたデータ。
コントローラ上の表記で言う”params”
※ただし、1カラムずつparams内のデータを指定する場合は不要
・対象の機能:対象のデータに対する登録・更新機能
コントローラ上に定義された”create/update"などのメソッド
3-2.適用対象外について
対象となる機能・データ以外であれば、ストロングパラメータを介する必要はないようです。
・不要なデータ
ユーザフォームから入力された情報ではない(=params以外)データ。
例えば、current_user(devise利用時に使える変数)を登録する場合などです。ただし、敢えて、ストロングパラメータの対象とすることも可能です。
・不要な機能
データの登録・更新が発生しない機能はもちろんですが、View/controllerを介さない機能も不要です。例えば、テストデータの一括登録、データ移行、バッチ処理などは不要。と言うより、機能自体使えないですね。きっと。
4.ストロングパラメータの書き方
前置きが長くなりましたが、ストロングパラメータの書き方について整理してみましょう。
4-1. 基本構文
ストロングパラメータは以下のように記述します。
メソッド名に命名規則は無いようですが[ モデル名_params ]とするのが一般的なようです。
また、実行結果として、許可されたカラムの値だけを抽出し、ハッシュ形式で呼び出し元に値を返してくれます。
private
def user_params
params.require(:キー(モデル名)).permit(:カラム名1,:カラム名2,・・・).merge(カラム名: 入力データ)
end
4-2.requireメソッド
requireメソッドを使用する事で、params内の特定のキーに紐付く値だけを抽出する事ができます。そのため、引数には取り出したい値のキーを指定する必要があります。
例)キー値userに対するデータを抽出したい場合は、以下のように設定します
params.require(:user).permit(・・・略・・・)
・ キーの設定元
View上でform_forメソッドを使用した場合のキー設定箇所を見てみましょう。下図の例ではform_forに続く”@user"がrequireメソッドで指定すべきキーです。
なお、form_forメソッドは、モデルに基づくformを作成する際に使うヘルパーメソッドで"@user"が対象のモデル名をさしています。
・・・中略・・・
<%= form_for @user do |f| %>
<%= f.text_field :name, placeholder: "ユーザ名を入力してね!" %>
<%= f.submit %>
<% end %>
・・・中略・・・
4-3.permitメソッド
permitメソッドを使用する事で、許可された値のみを取得することができます。
そのため、permitメソッドの引数には登録を許可する全てのカラム名を指定しておく必要があります。もし、許可されいないカラムがparams内に存在した場合、そのデータは取得されず無視されます。
例)Userモデルに存在するnameおよび、emailカラムのみ入力を受け付けたい場合
(他のカラム(admin)は受け付けないたくない)
params.require(:user).permit(:name,:email)
※ユーザの入力項目を増やした場合は、ストロングパラメータへの項目追加も忘れずに!
もし、忘れたら・・・何と明示的にエラーとなりません!(エラーキャッチとかしてるとなるのかな?)
必須項目だった場合、DB自体に保存されませんし、必須項目でない場合も、該当データのみDBに反映されない歯抜けの状態になっちゃいます。
4-4.mergeメソッド
mergeメソッドを使用することでハッシュ同士を結合することができます。
例えば、paramsに含まれない値をストロングメソッドに加えたい場合などに、ストロングパラメータの後に記述することができます。
params.require(:user).permit(:name,:email).merge(user_id: current_user.id)
4-5.privateメソッド
privateメソッド配下に記述したメソッドは、クラス外からのアクセスができません。
基本的にストロングパラメータは、クラス外からのアクセスをさせないようにprivateメソッド配下に書くようです。
##5.ストロングパラメータの呼び出し方
折角定義したストロングパラメータのメソッドですが、呼び出して使わないと意味がありません。どのように呼び出すか、記述例を見てみましょう。
なお、呼び出し方には大きく2パターンあります。(もっとあるかもしれませんが・・・)
####5-1. 丸投げパターン
このパターンが多いと思います。ストロングパラメータに全てお任せパターンです。
def create
User.create(user_params)
end
5-2. 部分投げパターン
params以外のデータを含む場合などに使うようです。
ただ、このパターンでは、そもそもマスアサインメント機能を使っていません。そのため、ストロングパラメータ自体不要ですね。実際にストロングパラメータを使わなくても登録が可能です。
なお、params以外のデータもストロングパラメータとして指定できるので、丸投げパターンで呼び出すことも可能です。(4-4.margeメソッド参照)
def create
Post.create(image: post_params[:image], text: post_params[:text],user_id: current_id)
end
まとめ
ストロングパラメータは、マスアサインメント機能の脆弱性問題を回避するために作られた機能。
そのため、対象はparamsを使用したデータの登録、更新のみ。
さらに言うと、paramsを使っていたとしても、1カラムずつ定義する場合は、ストロングパラメータの利用が必須ではない!
これでストロングパラメータと少しは仲良くなれたかな。。。
##参考
・Ruby on Rails5 アプリケーションプログラミング 山田祥寛 (参考書)
・https://kirohi.com/strong_parameters_rails
・https://diveintocode.jp/tips/strong_parameter