Ga Tech

1.01 everyday

Rails 4(4)

紀錄 Rails 3 to Rails 4 的一些改變。

STRONG PARAMETERS

在使用 Rails 3 的 form 上傳資料時,為了避免使用者上傳不必要的參數,會在 model 加上 whitelists 來過濾 mass assignment。比如:

models/user.rb
1
2
3
class User < ActiveRecord::Base
attr_accessible :name
end
controllers/users_controller.rb
1
2
3
4
5
6
7
def update
if @user.update_attributes(params[:user])
redirect_to @user, notice: 'Updated'
else
render action: 'edit'
end
end

這樣就能過濾使用者上傳的資料:
Parameters: {“user”=>{“name”=>”Cowzombie”}} # valid
Parameters: {“user”=>{“name”=>”Cowzombie”, “admin”=>”1”}} # invalid

在 Rails 4 中,管理 whitelists 的工作由 model 交給了 controller。
因此 model 裡頭不會再有 whitelists,而 controller 則多了一層過濾:

models/user.rb
1
2
class User < ActiveRecord::Base
end
controllers/users_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
def update
if @user.update(user_params)
redirect_to @user, notice: 'Updated'
else
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:name)
end

how STRONG PARAMETERES works

接下來看看 strong parameters 是如何運作的。假設我們的 application 收到的 parameter hash 如下:

1
2
3
4
5
6
{
id: "1",
user: {
name: "Cowzombie"
}
}

首先看到 params.require(:user)require(:user) 驗證 parameter hash 當中是否包含 user key,如果有就回傳 empty hash(因為此時還沒有同意傳入任何 parameters);如果沒有就丟出錯誤並回傳 404:
ActionController::ParameterMissing:
param not found: user

再來看到整段 params.require(:user).permit(:name)permit(:name) 表示只允許 name parameter 傳入,如果使用者多傳入其他的 parameter 比如 adminpermit 就會自動把不在清單裡的 parameter 給刪掉。

permit 同時也會檢查 parameter types(預設的 type 有 String, Symbol, NilClass…),並且設預會 log 住任何 unpermitted parameters。
如果想要在收到 unpermitted parameters 時候噴出錯誤訊息,可以到 config/application.rb 設定:

config/application.rb
1
config.action_controller.action_on_unpermitted_parameters = :raise

如果仍然要在 Rails 4 裡使用 attr_accessible 或是 attr_protected,可以使用這個 gem:

Gemfile
1
gem 'protected_attributes'

https://github.com/rails/strong_parameters 有更多關於 strong parameters 的資訊。

REMOTE FORMS

AUTHENTICITY TOKEN

Rails 的 form 裡頭有個 authenticity_token:

1
2
3
4
5
<form id="edit_user_1"...>
<input name="authenticity_token" type="hidden" value="a1RuTJ...=" />
<input id="user_name" type="text" value="Cowzombie" />
...
</form>

會在 submit form 的時候進行比對:
Parameters: {“authenticity_token”=>”a1RuTJ…=”,
“user”=>{“name”=>”Cowzombie”}, “id”=>”1”}

當我們使用 remote form 的時候,這個 authenticity_token 也有包含在裡頭:

views/users/_form.html.erb
1
2
3
4
<%= form_for(@user, remote: true) do |f| %>
<%= f.text_field :name %>
...
<% end %>
1
2
3
4
5
<form data-remote="true" id="edit_user_1"...>
<input name="authenticity_token" type="hidden" value="iS8pNE...=" />
<input id="user_name" type="text" value="Cowzombie" />
...
</form>

但是實際上,在我們使用 AJAX request 時,Rails 並不會用到 remote form 裡面的這個 authenticity_token,而是會使用 meta 裡頭相同的 token:

1
<meta content="iS8pNE...=" name="csrf-token" />

因此 Rails 4 就把 remote form 裡頭的 authenticity_token 給拿掉了:

1
2
3
4
<form data-remote="true" di="dit_user_1"...>
<input id="user_name" type="text" value="Cowzombie" />
...
</form>

what if JavaScript turned off in browsers

如果使用者的瀏覽器把 JavaScript 給關掉了,那這樣 Rails 就會因為讀不到 CSRF token 而噴錯誤:
Can’t verify CSRF token authenticity
ActionController::InvalidAuthenticityToken

要解決這個問題很簡單,只要再把 authenticity token 放回 remote form 就好。可以透過此設定:

config/application.rb
1
config.action_view.embed_authenticity_token_in_remote_forms = true

PROTECT FROM FORGERY

有些使用者會試圖假造 authenticity_token,在 Rails 3 當中遇到此情況時,預設會 reset session 並且把以下的 warning 給 log 起來:
WARNING: Can’t verify CSRF token authenticity

而針對此情形, Rails 4 則有更多的處理方式(通常會放在 controllers/applications.rb 裡面):
protect_from_forgery with: :exception # this is a good idea! (and also the default)

# 會產生錯誤:
ActionController::InvalidAuthenticityToken

或是
protect_from_forgery with: :null_session

# 會清空 session,但不會產生錯誤(直到接收到 valid request 為止)

或是

protect_from_forgery with: :reset_session
# 會針對這個使用者產生新的 session,並把舊的 session 給 destroy 掉

Comments