Rails 2.3RC1で”Restful Authentication with all the bells and whistles”をやってみる(2)

本文

app/model/user.rbを編集する。

% diff user.rb.org user.rb
12c12
<   validates_length_of       :email,    :within => 3..100
---
>   validates_length_of       :email,    :within => 6..100
13a14,17
> 
>   has_many :permissions
>   has_many :roles, :through => :permissions
> 
15c19
<   before_create :make_activation_code 
---
>   before_create :make_activation_code
20,25c24,45
<   # Activates the user in the database.
<   def activate
<     @activated = true
<     self.activated_at = Time.now.utc
<     self.activation_code = nil
<     save(false)
---
>   class ActivationCodeNotFound < StandardError; end
>   class AlreadyActivated < StandardError
>     attr_reader :user, :message;
>     def initialize(user, message = nil)
>       @message, @user = message, user
>     end
>   end
> 
>   # Finds the user with the corresponding activation code, activates
>   # their account and returns the user.
>   # Raises:
>   #  +User::ActivationCodeNotFound+ if there is no user with the
>   #  corresponding activation code
>   #  +User::AlreadyActivated+ if the user with the corresponding
>   #  activation code has already activated their account
>   def self.find_and_activate!(activation_code)
>     raise ArgumentError if activation_code.nil?
>     user = find_by_activation_code(activation_code)
>     raise ActivationCodeNotFound if !user
>     raise AlreadyActivated.new(user) if user.active?
>     user.send(:activate!)
>     user
32a53,57
>   # Returns true if the user has just been activated.
>   def pendings?
>     @activated
>   end
> 
35c60
<     u = find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login] # need to get the salt
---
>     u = find :first, :conditions => ['login = ?', login] # need to get the salt
54c79
<     remember_token_expires_at && Time.now.utc < remember_token_expires_at 
---
>     remember_token_expires_at && Time.now.utc < remember_token_expires_at
78,80c103,129
<   # Returns true if the user has just been activated.
<   def recently_activated?
<     @activated
---
>   def forgot_password
>     @forgotten_password = true
>     self.make_password_reset_code
>   end
> 
>   def reset_password
>     # First update the password_reset_code before setting the
>     # reset_password flag to avoicd duplicate email notifications.
>     update_attribute(:password_reset_code, nil)
>     @reset_password = true
>   end
> 
>   # used in user_observer
>   def recently_forgot_password?
>     @forgotten_password
>   end
> 
>   def recently_reset_password?
>     @reset_password
>   end
> 
>   def self.find_for_forget(email)
>     find :first, :conditions => ['email = ? and activated_at IS NOT NULL', email]
>   end
> 
>   def has_role?(rolename)
>     self.roles.find_by_rolename(rolename) ? true : false
84c133
<     # before filter 
---
>     # before filter
90c139
<       
---
> 
94c143
<     
---
> 
99c148,158
<     
---
> 
>     def make_password_reset_code
>       self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_split(//).sort_by{ rand}.join)
>     end
> 
>     private
> 
>     def activate!
>       @activated = true
>       self.update_attribute(:activated_at, Time.now.utc)
>     end

lib/authenticated_system.rbにメソッド not_logged_in_required, check_role, check_administrator_role, and permission_deniedを付け加える。

% diff authenticated_system.rb.org authenticated_system.rb
9c9
<     # Accesses the current user from the session. 
---
>     # Accesses the current user from the session.
54a55,73
>     def not_logged_in_required
>       !logged_in? || permission_denied
>     end
> 
>     def check_role(role)
>       unless logged_in? && @current_user.has_role?(role)
>         if logged_in?
>           permission_denied
>         else
>           store_referer
>           access_denied
>         end
>       end
>     end
> 
>     def check_administrator_role
>       check_role('administrator')
>     end
> 
74a94,121
>     def permission_denied
>       respond_to do |format|
>         format.html do
>           # Put your domain name here ex. http://www.example.com
>           domain_name = "http://localhost:3000"
>           http_referer = session[:refer_to]
>           if http_referer.nil?
>             store_referer
>             http_referer = { session[:refer_to] || domain_name}
>           end
>           flash[:error] = "You don't have permisson to complete that action."
>           # The [0..20] represents the 21 characters in http://localhost:3000/
>           # You have to set that to the number of characters in your domain name
>           if http_referer[0..20] != domain_name
>             session[:refer_to] = nil
>             redirect_to root_path
>           else
>             redirect_to_referer_or_default(root_path)
>           end
>         end
>         format.xml do
>           headers["Status"]= "Unauthorized"
>           headers["WWW-Authenticate"] = %(Basic realm="Web Password")
>           reander :text => "You don't have permission to complete this action.", :status => '401 Unauthorized'
>         end
>       end
>     end
> 
81a129,131
>     def store_referer
>       session[:refer_to] = request.env["HTTP_REFERER"]
>     end

チュートリアルの著者は permission_deniedメソッドは改善が必要だと思っているとのこと。

次はコントローラーを変更する。方針はよりRESTfulにするのが目的らしい。まずはapp/controller/user_controller.rb

% diff users_controller.rb.org  users_controller.rb
2,4c2,14
<   # Be sure to include AuthenticationSystem in Application Controller instead
<   include AuthenticatedSystem
<   
---
>   layout 'application'
>   before_filter :not_logged_in_required, :only=>[:new, :create]
>   before_filter :login_required, :only=>[:show, :edit, :update]
>   before_filter :check_administrator_role, :only => [:index, :destroy, :enable]
> 
>   def index
>     @users = User.find(:all)
>   end
> 
>   # This show action only allows users to view their own provfile
>   def show
>     @user = current_user
>   end
7a18
>     @user = User.new
12c23
<     # protects against session fixation attacks, wreaks havoc with 
---
>     # protects against session fixation attacks, wreaks havoc with
17c28,32
<     @user.save
---
>     @user.save!
>     # Uncomment to have the user logged in after creating an account - Not Recommended
>     # self.current_user = @user
>     flash[:notice] = "Thanks for signing up! Please check your email to activate your account before logging in."
> 

次にsession_controller.rb

% diff sessions_controller.rb.org sessions_controller.rb
1c1
< # This controller handles the login/logout function of the site.  
---
> # This controller handles the login/logout function of the site.
3,4c3,5
<   # Be sure to include AuthenticationSystem in Application Controller instead
<   include AuthenticatedSystem
---
>   layout 'application'
>   before_filter :login_required, :only => :destroy
>   before_filter :not_logged_in_required, :only => [:new, :create]
11,21c12
<     self.current_user = User.authenticate(params[:login], params[:password])
<     if logged_in?
<       if params[:remember_me] == "1"
<         current_user.remember_me unless current_user.remember_token?
<         cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
<       end
<       redirect_back_or_default('/')
<       flash[:notice] = "Logged in successfully"
<     else
<       render :action => 'new'
<     end
---
>     password_authentication(params[:login], params[:password])
29c20,36
<     redirect_back_or_default('/')
---
>     redirect_to_ login_path
>   end
> 
>   protected
>   #Updated 2/20/08
>   def password_authentication(login, password)
>     user = User.authenticate(login, password)
>     if user == nil
>       faild_login("Your username or password is incorrect.")
>     elsif user.activated_at.blank?
>     faild_login("Your account is not active, please check your email for the activation code")
>     elsif user.enabled == false
>      failed_login("Your account has been disabled.")
>     else
>       self.current_user = user
>       successful_login
>     end
30a38,59
> 
>   private
> 
>   def failed_login(message)
>     flash.now[:error] = message
>     render :action =>'new'
>   end
> 
>   def successful_login
>     if params[:remember_me] == "l"
>       self.current_user.remenber_me
>       collkies[:auth_token] = { :value => self.current_user.remember_token, :expires => self.current_user.remember_token_expires_at }
>     end
>     flash[:notice] = "Logged in successfully"
>     return_to = session[:return_to]
>     if return_to.nil?
>       redirect_to user_path(self.current_user)
>     else
>       redirect_to return_to
>     end
>   end

次に二つのコントローラーを作成する。

% ruby script/generate controller Passwords
% ruby script/generate controller Accounts

つづく。