Requiring the right user
要求使用者先登入還不夠,還要限制只有使用者自己才能編輯自己的資料。從上一節可以知道,test suit 很容易漏掉基本的安全漏洞,所以我們要使用 TDD 開發技術確保寫出的程式碼能正確實現安全機制。所以我們要繼續在 Users controller test 增加一些測試。
為了要確保使用者不能編輯其他使用者的資料,我們需要再登入第二個使用者。所以要在 fixtures 增加第二名使用者資料:
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
透過使用之前定義的 log_in_as 方法,可以用來測試 edit 和 update action。注意最後是直接將使用者導回 root path 而不是登入頁面,因為這個使用者已經登入:
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
@other_user = users(:archer)
end
test "should get new" do
get :new
assert_response :success
end
test "should redirect edit when not logged in" do
get :edit, id: @user
assert_not flash.empty?
assert_redirected_to login_url
end
test "should redirect update when not logged in" do
patch :update, id: @user, user: { name: @user.name, email: @user.email }
assert_not flash.empty?
assert_redirected_to login_url
end
test "should redirect edit when logged in as wrong user" do
log_in_as(@other_user)
get :edit, id: @user
assert flash.empty?
assert_redirected_to root_url
end
test "should redirect update when logged in as wrong user" do
log_in_as(@other_user)
patch :update, id: @user, user: { name: @user.name, email: @user.email }
assert flash.empty?
assert_redirected_to root_url
end
end
為了要重新把試圖編輯其他人資料的使用者導回 root path,我們要定義一個 correct_user 方法,然後再設定一個 before filter 調用這個方法。注意,correct_user 中定義了一個 @user 變數,所以可以把 edit 和 update 中的 @user 刪掉:
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
.
.
.
def edit
end
def update
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
.
.
.
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless @user == current_user
end
end
現在執行測試會通過(Green):
$ bundle exec rake test
最後還要重構一下,根據慣例,我們要在 Sessions helper 中 定義一個 current_user? 布林值方法,然後可以在 correct_user 中調用,也就是說,我們要把這句:
unless @user == current_user
改成更明確一點:
unless current_user?(@user)
完整程式碼如下:
app/helpers/sessions_helper.rb
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# Returns true if the given user is the current user.
def current_user?(user)
user == current_user
end
.
.
.
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
.
.
.
def edit
end
def update
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
.
.
.
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
end