10.2 protect pages.

6111 단어
자세히 보기
 
again, we will start from TDD!!!
 
 
1. since both edit and update need the same authentication, we can put their test together:
  describe "authentication of edit/update pages" do
    before(:each) do
      @user = Factory(:user)
    end
    describe "for not signed-in users" do
      it "should redirect to sign in page" do
        get :edit, :id => @user
        response.should redirect_to signin_path
      end
      it "should deny access to update" do
        put :update, :id => @user, :user => {}
        response.should redirect_to signin_path
      end
    end
  end

 
2. we will add before_filter to user controller to make this test pass:
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  .
  private

    def authenticate
      deny_access unless signed_in?
    end
end

 
we still need to define the deny_access method, since it is kind of authentication, I'll put it into session helper:
def deny_access
  redirect_to signin_path, :notice => "please sign in first."
end

 note, this line of code is equivalent with two
flash[:notice] = ""
redirect_to signin_path

 you can also use:
redirect_to signin_path, :alert => "fdsfdsfsdf"

 but you can't use :success or :error in this contruction.
3. except need of user to sign in, we still need to make sure current user can't edit other user info.
start from TDD again!!!
describe UsersController do
  render_views
  .
  .
  .
  describe "authentication of edit/update pages" do
    .
    .
    .
    describe "for signed-in users" do

      before(:each) do
        wrong_user = Factory(:user, :email => "[email protected]")
        test_sign_in(wrong_user)
      end

      it "should require matching users for 'edit'" do
        get :edit, :id => @user
        response.should redirect_to(root_path)
      end

      it "should require matching users for 'update'" do
        put :update, :id => @user, :user => {}
        response.should redirect_to(root_path)
      end
    end
  end
end

 
4. now to make the test pass, we need to add a new before filter to user controller.
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  before_filter :correct_user, :only => [:edit, :update]
  .
  .
  .
  def edit
    @title = "Edit user"
  end
  .
  .
  .
  private

    def authenticate
      deny_access unless signed_in?
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_path) unless current_user?(@user)
    end
end
 
module SessionsHelper
  .
  .
  .
  def current_user?(user)
    user == current_user
  end

  def deny_access
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  private
    .
    .
    .
end

 
now we have make our site very safe.
5. now we are doing some useful thing:
if a unsigned in user try to visit a protected page, he is redirected to the sign in page, then after he sign in, he is always redirected to the profile page, what we want is to redirect the user to the page he was trying to visit.
this is a very good work flow to be tested by the integration test!
so let's write a integration test for this flow first.
require 'spec_helper'

describe "FriendlyForwardings" do

  it "should forward to the requested page after signin" do
    user = Factory(:user)
    visit edit_user_path(user)
    # The test automatically follows the redirect to the signin page.
    fill_in :email,    :with => user.email
    fill_in :password, :with => user.password
    click_button
    # The test follows the redirect again, this time to users/edit.
    response.should render_template('users/edit')
  end
end
 
you may wondering, why I use 
should render_template()
instead of 
should redirect_to()
because, in integration test, it will follow the redirect, so response.should redirect_to will not work.
6. next, we will do the implementation to make the test pass.
how do we do this?
a. since http is stateless, we have to use session to store the requested url in last request, then get it from session in the new request.(the things in session will expire when browser close.)
b. we will use the request object to get the url.
module SessionsHelper
  .
  .
  .
  def deny_access
    store_location
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  def redirect_back_or(default)
    redirect_to(session[:return_to] || default)
    clear_return_to
  end

  private
    .
    .
    .
    def store_location
      session[:return_to] = request.fullpath
    end

    def clear_return_to
      session[:return_to] = nil
    end
end

좋은 웹페이지 즐겨찾기