NetBeansで作成中のRailsプロジェクトにRestful Authenticationを追加する(その2)
- はじめに
- 今回の前提
- 認証モデル
- Restful Authenticationのインストール
- UserモデルとSessionモデルの生成
- room、rolesとpermissionsの設定
- コントローラーの編集
- ビューの編集
- routes.rbの編集。
- 既存のプロジェクトの修正
- 公開するときのメモ
- 戻る
はじめに_
Restful Authentication with all the bells and whistles (new 9/05/08)に従い、NetBeansで開発中のRailsプロジェクトにRestful Authenticationを付け加えるときのメモ。
このページでは、上記のチュートリアルと以下の点が違う。
- Roomというモデルがあり、ユーザーの役割はRoomごとに異なる
今回の前提_
実施環境は以下のとおり。
- Windows XP SP2
- NetBeans 6.7.1
- Ruby 1.8.6 p287
- Rails 2.3.5
公開環境は以下のとおり
- Apache 2.2.14 (起動コマンド:/etc/init.d/apache)
- Postfix 2.6.5 (/usr/sbin/sendmail)
- passenger 2.2.7
- Ruby 1.8.7
- gem 1.3.5
- Rails 2.3.5
- SQLite3 3.6.21
認証モデル_
Roomごとに役割が異なる。つまり、ユーザーはシステム上で複数の役割を持つ。
- Room:作業の一単位
- Role:役割として administrator, user
- User:ユーザー
- Session:セッション管理用
- Permission:Room, Role, Userの対応テーブル
Restful Authenticationのインストール_
- NetBeansでプロジェクトが読み込まれているとする。プロジェクト名の上にカーソルを合わせて右クリック→「Railsプラグイン」を選ぶ。
- 開いたウィンドウで「リポジトリ」タブをクリック、その後「URLを追加」をクリックし、「http://svn.techno-weenie.net/projects/plugins」を加える。
- 「新しいプラグイン」タブをクリックし、検索ウィンドウで「restful_authentication」を検索する。
- restful_authenticationが見つかったら、選択し、インストールする(Subversionは使わなくても良い)
UserモデルとSessionモデルの生成_
restful_authenticationプラグインで使うファイルを生成する。
- NetBeansでプロジェクトが読み込まれているとする。プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは「authenticated」を選ぶ
- 引数は「user sessions --include-activation」を選ぶ
上記コマンドでセッション用のコントローラーとビューができているはず。その後、application_controller.rbに"include AuthenticatedSystem"が付け加えられているかをチェックする。チェックすると付け加えられていないので付け加える。
次にconfig/initalizersにmail.rbを作成する。localhostのsendmailを使う場合は、以下の内容にする。
- 「構成」をクリック
- 「initalizers」にカーソルを合わせて「新規作成」→「Rubyファイル」
- ファイル名は「mail.rb」にする。
mail.rbの中身(Sendmailバージョン)
# Email settings
ActionMailer::Base.delivery_method = :sendmail
ActionMailer::Base.sendmail_settings = {
:location => "/usr/sbin/sendmail -t"
}
次に、config/environment.rbの"Rails::Initializer.run do |config|"の後ろに以下の行を追加する。そうすると、このアプリケーションは、レジストレーションやアクティベーションなどの後にユーザーにメールを送るようになる(user_observer.rbとuser_mailer.rbはrestfulauthenticationによって、app/models以下に作られている)。
config.active_record.observers = :user_observer
次に、config/environment.rbに定数を定義するか、config/environments/development.rb(開発用設定ファイル)かconfig/environments/production.rb(公開用設定ファイル)にアプリケーション用の定数を定義する。
どのファイルに記載するかを以下の視点から決定する。
- 開発環境でも実行環境でも同じ定数を使う→config/environment.rb
- 開発環境のみでその定数を使う→config/environments/production.rb
- 実行環境のみでその定数を使う→config/environments/production.rb
具体的には以下を追加する。
## For my application RootURL = 'http://localhost:3000/' MyMailAddress = 'mail@yourapplication.com'
そして、user_mailer.rbを以下のように設定する。
user_mailer.rb:
class UserMailer < ActionMailer::Base
def signup_notification(user)
setup_email(user)
@subject += 'Please activate your new account'
@body[:url] = "#{RootURL}activate/#{user.activation_code}"
end
def activation(user)
setup_email(user)
@subject += 'Your account has been activated!'
@body[:url] = "#{RootURL}"
end
def forgot_password(user)
setup_email(user)
@subject += 'You have requested to change your password'
@body[:url] = "#{RootURL}reset_password/#{user.password_reset_code}"
end
def reset_password(user)
setup_email(user)
@subject += 'Your password has been reset.'
end
protected
def setup_email(user)
@recipients = "#{user.email}"
@from = "#{MyMailAddress}"
@subject = "YourApplication - "
@sent_on = Time.now
@body[:user] = user
end
end
config以下のファイルを書き換えた場合は、WebRickを再起動すること。
そして、user_observer.rbにパスワードのリセットとパスワード忘れ機能を組み込む。
user_observer.rb:
class UserObserver < ActiveRecord::Observer
def after_create(user)
UserMailer.deliver_signup_notification(user)
end
def after_save(user)
UserMailer.deliver_activation(user) if user.pending?
UserMailer.deliver_forgot_password(user) if user.recently_forgot_password?
UserMailer.deliver_reset_password(user) if user.recently_reset_password?
end
end
room、rolesとpermissionsの設定_
モデルroom、roleとpermissionを設定する。
- roomの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは scaffold
- モデル名は Role
- 属性ペアは roomname:string
- roleの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは scaffold
- モデル名は Role
- 属性ペアは rolename:string
- permissonの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは model
- 引数は Permission
db/migrate以下にあるXXX_create_roles.rbを編集する。
- 「データベースマイグレーション」をクリック
- 「migrate」をクリック。
- XXX_create_roles.rbを以下のように編集。
class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.string :rolename
t.timestamps
end
end
def self.down
drop_table :roles
end
end
db/migrate以下にあるXXX_create_permissions.rbを編集する。
- 「データベースマイグレーション」をクリック
- 「migrate」をクリック。
- XXX_create_permissions.rbを以下のように編集。
class CreatePermissions < ActiveRecord::Migration
def self.up
create_table :permissions do |t|
t.integer :room_id, :role_id, :user_id, :null => false
t.timestamps
end
end
def self.down
drop_table :permissions
end
end
db/migrate以下にあるXXX_create_users.rbを編集する。
- 「データベースマイグレーション」をクリック
- 「migrate」をクリック。
- XXX_create_users.rbを以下のように編集。
class CreateUsers < ActiveRecord::Migration
def self.up
create_table "users", :force => true do |t|
t.column :login, :string
t.column :email, :string
t.column :crypted_password, :string, :limit => 40
t.column :salt, :string, :limit => 40
t.column :created_at, :datetime
t.column :updated_at, :datetime
t.column :remember_token, :string
t.column :remember_token_expires_at, :datetime
t.column :activation_code, :string, :limit => 40
t.column :activated_at, :datetime
t.column :password_reset_code, :string, :limit => 40
t.column :enabled, :boolean, :default => true
end
end
def self.down
drop_table "users"
end
end
元のチュートリアルでは、migrationファイルの中で初期データをセットしていたが、Rails 2.3.4からそれをやめるようになったらしい(情報元:ひげろぐ:Rails 2.3.4で追加されたseeds.rbについて)
また、元のチュートリアルでは、システムの管理者をpermissionsで管理していたが、今回のシステムでは、その方法ではシステム管理者を登録できない。そこで、管理者専用ユーザーを用意し、そのユーザーをシステム管理者とすることとする。公開前にシステム管理者のパスワードを変更しておくこと。
db/seeds.rbを以下のように書く。
Role.create(:rolename => 'administrator') Role.create(:rolename => 'user') user = User.new user.login = "ServerAdmin" user.email = "yourmail@hogehoge" user.password = "admin" user.password_confirmation = "admin" user.save(false) user.send(:activate!)
次に、app/models以下のファイルをいくつか変更しなければならない。 はじめに、rolesとusersの間の関係を定義する。rolesとusersは多対多の関係なので、has_many :through宣言で関係を示す。
- 「モデル」をクリック
- room.rbを以下のように編集。
class Room < ActiveRecord::Base has_many :permissions has_many :rooms, :through => :permissions has_many :roles, :through => :permissions end
- 「モデル」をクリック
- role.rbを以下のように編集。
class Role < ActiveRecord::Base has_many :permissions has_many :users, :through => :permissions has_many :rooms, :through => :permissions end
- 「モデル」をクリック
- permission.rbを以下のように編集。
class Permission < ActiveRecord::Base belongs_to :user belongs_to :role belongs_to :room end
続いて、以下の変更をuser.rbに行なう。
- メールアドレスの長さチェックに関する変更
- userとrolesの間の関係
- あるユーザーがある役割をもっているかどうかを調べるチェック
- パスワード忘れに関する記述
- ユーザーアクティベートに関するいくつかの変更。
- 「モデル」をクリック
- user.rbを以下のように編集。
require 'digest/sha1'
class User < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password
validates_presence_of :login, :email
validates_presence_of :password, :if => :password_required?
validates_presence_of :password_confirmation, :if => :password_required?
validates_length_of :password, :within => 4..40, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
validates_length_of :login, :within => 3..40
validates_length_of :email, :within => 6..100
validates_uniqueness_of :login, :email, :case_sensitive => false
validates_format_of :email, :with => /(^([^@\s]+)@((?:[-_a-z0-9]+\.)+[a-z]{2,})$)|(^$)/i
has_many :permissions
has_many :roles, :through => :permissions
has_many :rooms, :through => :permissions
before_save :encrypt_password
before_create :make_activation_code
# prevents a user from submitting a crafted form that bypasses activation
# anything else you want your user to change should be added here.
attr_accessible :login, :email, :password, :password_confirmation
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
end
def active?
# the presence of an activation date means they have activated
!activated_at.nil?
end
# Returns true if the user has just been activated.
def pending?
@activated
end
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
# Updated 2/20/08
def self.authenticate(login, password)
u = find :first, :conditions => ['login = ?', login] # need to get the salt
u && u.authenticated?(password) ? u : nil
end
# Encrypts some data with the salt.
def self.encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
# Encrypts the password with the user salt
def encrypt(password)
self.class.encrypt(password, salt)
end
def authenticated?(password)
crypted_password == encrypt(password)
end
def remember_token?
remember_token_expires_at && Time.now.utc < remember_token_expires_at
end
# These create and unset the fields required for remembering users between browser closes
def remember_me
remember_me_for 2.weeks
end
def remember_me_for(time)
remember_me_until time.from_now.utc
end
def remember_me_until(time)
self.remember_token_expires_at = time
self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
save(false)
end
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
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 avoid 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
end
def is_server_admin?
self.login == 'ServerAdmin'
end
protected
# before filter
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
self.crypted_password = encrypt(password)
end
def password_required?
crypted_password.blank? || !password.blank?
end
def make_activation_code
self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
def make_password_reset_code
self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
private
def activate!
@activated = true
self.update_attribute(:activated_at, Time.now.utc)
end
end
lib以下にあるauthenticated_system.rbの変更をする。
- 「ライブラリー」をクリック
- authenticated_system.rbにnot_logged_in_required, check_role, check_administrator_roleと permission_denied を以下のように付け加える。
module AuthenticatedSystem
protected
# Returns true or false if the user is logged in.
# Preloads @current_user with the user model if they're logged in.
def logged_in?
current_user != :false
end
# Accesses the current user from the session. Set it to :false if login fails
# so that future calls do not hit the database.
def current_user
@current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
end
# Store the given user id in the session.
def current_user=(new_user)
session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
@current_user = new_user || :false
end
# Check if the user is authorized
#
# Override this method in your controllers if you want to restrict access
# to only a few actions or if you want to check if the user
# has the correct rights.
#
# Example:
#
# # only allow nonbobs
# def authorized?
# current_user.login != "bob"
# end
def authorized?
logged_in?
end
# Filter method to enforce a login requirement.
#
# To require logins for all actions, use this in your controllers:
#
# before_filter :login_required
#
# To require logins for specific actions, use this in your controllers:
#
# before_filter :login_required, :only => [ :edit, :update ]
#
# To skip this in a subclassed controller:
#
# skip_before_filter :login_required
#
def login_required
authorized? || access_denied
end
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
# For server administrator
def check_server_administrator_role
logged_in? && (current_user.login == 'ServerAdmin')
end
# Redirect as appropriate when an access request fails.
#
# The default action is to redirect to the login screen.
#
# Override this method in your controllers if you want to have special
# behavior in case the user is not authorized
# to access the requested action. For example, a popup window might
# simply close itself.
def access_denied
respond_to do |format|
format.html do
store_location
flash[:error] = "You must be logged in to access this feature."
redirect_to :controller => '/session', :action => 'new'
end
format.xml do
request_http_basic_authentication 'Web Password'
end
end
end
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 permission 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")
render :text => "You don't have permission to complete this action.", :status => '401 Unauthorized'
end
end
end
# Store the URI of the current request in the session.
#
# We can return to this location by calling #redirect_back_or_default.
def store_location
session[:return_to] = request.request_uri
end
def store_referer
session[:refer_to] = request.env["HTTP_REFERER"]
end
# Redirect to the URI stored by the most recent store_location call or
# to the passed default.
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
def redirect_to_referer_or_default(default)
redirect_to(session[:refer_to] || default)
session[:refer_to] = nil
end
# Inclusion hook to make #current_user and #logged_in?
# available as ActionView helper methods.
def self.included(base)
base.send :helper_method, :current_user, :logged_in?
end
# Called from #current_user. First attempt to login by the user id stored in the session.
def login_from_session
self.current_user = User.find(session[:user_id]) if session[:user_id]
end
# Called from #current_user. Now, attempt to login by basic authentication information.
def login_from_basic_auth
authenticate_with_http_basic do |username, password|
self.current_user = User.authenticate(username, password)
end
end
# Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
def login_from_cookie
user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
if user && user.remember_token?
user.remember_me
cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
self.current_user = user
end
end
end
コントローラーの編集_
続いてcontrollerを編集する。元のチュートリアルでは、restful_authenticationによって生成された多くのactionをそれら自身のコントローラーに移している。目的は、restfulの振る舞いにできる限りそうようにするため。また、将来の拡張しやすいようにしておくため。
まず、users_controller.rbを編集する。
- 「コントローラ」をクリック
- users_controller.rbを以下のように編集。
class UsersController < ApplicationController
layout 'application'
before_filter :not_logged_in_required, :only => [:new, :create]
before_filter :login_required, :only => [:show, :edit, :update]
before_filter :check_server_administrator_role, :only => [:index, :enable]
def index
@users = User.find(:all)
@numOfUserRooms = Permission.count(:room_id, :group => 'user_id')
end
#This show action only allows users to view their own profile
def show
@user = current_user
@roles = Role.find(:all)
@myRooms = Array.new
@permissions = Permission.find(:all, :conditions => ["user_id = ?", @user.id])
@permissions.each do | permission |
@myRooms.push(permission.room)
end
end
# render new.rhtml
def new
@user = User.new
end
def create
cookies.delete :auth_token
@user = User.new(params[:user])
@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."
redirect_to login_path
rescue ActiveRecord::RecordInvalid
flash[:error] = "There was a problem creating your account."
render :action => 'new'
end
def edit
@user = current_user
end
def update
@user = User.find(current_user)
if @user.update_attributes(params[:user])
flash[:notice] = "User updated"
redirect_to :action => 'show', :id => current_user
else
render :action => 'edit'
end
end
def destroy
@user = User.find(params[:id])
if @user.update_attribute(:enabled, false)
flash[:notice] = "User disabled"
else
flash[:error] = "There was a problem disabling this user."
end
redirect_to :action => 'index'
end
def enable
@user = User.find(params[:id])
if @user.update_attribute(:enabled, true)
flash[:notice] = "User enabled"
else
flash[:error] = "There was a problem enabling this user."
end
redirect_to :action => 'index'
end
end
次にsessions_controller.rbを編集する。
- 「コントローラ」をクリック
- sessions_controller.rbを以下のように編集。
# This controller handles the login/logout function of the site.
class SessionsController < ApplicationController
layout 'application'
before_filter :login_required, :only => :destroy
before_filter :not_logged_in_required, :only => [:new, :create]
# render new.rhtml
def new
end
def create
password_authentication(params[:login], params[:password])
end
def destroy
self.current_user.forget_me if logged_in?
cookies.delete :auth_token
reset_session
flash[:notice] = "You have been logged out."
redirect_to login_path
end
protected
# Updated 2/20/08
def password_authentication(login, password)
user = User.authenticate(login, password)
if user == nil
failed_login("Your username or password is incorrect.")
elsif user.activated_at.blank?
failed_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
end
private
def failed_login(message)
flash.now[:error] = message
render :action => 'new'
end
def successful_login
if params[:remember_me] == "1"
self.current_user.remember_me
cookies[: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
end
次にさらに二つのcontrollerを生成する。
- Passwordsの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは controller
- モデル名は Passwords
- ビューは、edit new
- Accountsの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは controller
- モデル名は Accounts
- ビューは、edit
passwords_controller.rbを編集する。
class PasswordsController < ApplicationController
layout 'application'
before_filter :not_logged_in_required, :only => [:new, :create]
# Enter email address to recover password
def new
end
# Forgot password action
def create
return unless request.post?
if @user = User.find_for_forget(params[:email])
@user.forgot_password
@user.save
flash[:notice] = "A password reset link has been sent to your email address."
redirect_to login_path
else
flash[:notice] = "Could not find a user with that email address."
render :action => 'new'
end
end
# Action triggered by clicking on the /reset_password/:id link recieved via email
# Makes sure the id code is included
# Checks that the id code matches a user in the database
# Then if everything checks out, shows the password reset fields
def edit
if params[:id].nil?
render :action => 'new'
return
end
@user = User.find_by_password_reset_code(params[:id]) if params[:id]
raise if @user.nil?
rescue
logger.error "Invalid Reset Code entered."
flash[:notice] = "Sorry - That is an invalid password reset code. Please check your code and try again. (Perhaps your email client inserted a carriage return?)"
#redirect_back_or_default('/')
redirect_to new_user_path
end
# Reset password action /reset_password/:id
# Checks once again that an id is included and makes sure that the password field isn't blank
def update
if params[:id].nil?
render :action => 'new'
return
end
if params[:password].blank?
flash[:notice] = "Password field cannot be blank."
render :action => 'edit', :id => params[:id]
return
end
@user = User.find_by_password_reset_code(params[:id]) if params[:id]
raise if @user.nil?
return if @user unless params[:password]
if (params[:password] == params[:password_confirmation])
#Uncomment and comment lines with @user to have the user logged in after reset - not recommended
#self.current_user = @user #for the next two lines to work
#current_user.password_confirmation = params[:password_confirmation]
#current_user.password = params[:password]
#@user.reset_password
#flash[:notice] = current_user.save ? "Password reset" : "Password not reset"
@user.password_confirmation = params[:password_confirmation]
@user.password = params[:password]
@user.reset_password
flash[:notice] = @user.save ? "Password reset." : "Password not reset."
else
flash[:notice] = "Password mismatch."
render :action => 'edit', :id => params[:id]
return
end
redirect_to login_path
rescue
logger.error "Invalid Reset Code entered"
flash[:notice] = "Sorry - That is an invalid password reset code. Please check your code and try again. (Perhaps your email client inserted a carriage return?)"
redirect_to new_user_path
end
end
accounts_controller.rbを編集。
class AccountsController < ApplicationController
layout 'application'
before_filter :login_required, :except => :show
before_filter :not_logged_in_required, :only => :show
# Activate action
def show
# Uncomment and change paths to have user logged in after activation - not recommended
#self.current_user = User.find_and_activate!(params[:id])
User.find_and_activate!(params[:id])
flash[:notice] = "Your account has been activated! You can now login."
redirect_to login_path
rescue User::ArgumentError
flash[:notice] = 'Activation code not found. Please try creating a new account.'
redirect_to new_user_path
rescue User::ActivationCodeNotFound
flash[:notice] = 'Activation code not found. Please try creating a new account.'
redirect_to new_user_path
rescue User::AlreadyActivated
flash[:notice] = 'Your account has already been activated. You can log in below.'
redirect_to login_path
end
def edit
end
# Change password action
def update
return unless request.post?
if User.authenticate(current_user.login, params[:old_password])
if ((params[:password] == params[:password_confirmation]) && !params[:password_confirmation].blank?)
current_user.password_confirmation = params[:password_confirmation]
current_user.password = params[:password]
if current_user.save
flash[:notice] = "Password successfully updated."
redirect_to root_path #profile_url(current_user.login)
else
flash[:error] = "An error occured, your password was not changed."
render :action => 'edit'
end
else
flash[:error] = "New password does not match the password confirmation."
@old_password = params[:old_password]
render :action => 'edit'
end
else
flash[:error] = "Your old password is incorrect."
render :action => 'edit'
end
end
end
roles_controller.rbを編集。
class RolesController < ApplicationController
layout 'application'
before_filter :check_server_administrator_role
def index
@user = User.find(params[:user_id])
@all_roles = Role.find(:all)
end
def update
@user = User.find(params[:user_id])
@role = Role.find(params[:id])
unless @user.has_role?(@role.rolename)
@user.roles << @role
end
redirect_to :action => 'index'
end
def destroy
@user = User.find(params[:user_id])
@role = Role.find(params[:id])
if @user.has_role?(@role.rolename)
@user.roles.delete(@role)
end
redirect_to :action => 'index'
end
end
rooms_controller.rbを編集。
class RoomsController < ApplicationController
layout 'application'
before_filter :login_required
# GET /rooms/1
# GET /rooms/1.xml
def show
permission = check_permission(params[:id])
unless permission
flash[:notice] = 'You cannot access the room!'
redirect_to user_path(current_user)
else
@room = permission.room
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @room }
end
end
end
# GET /rooms/new
# GET /rooms/new.xml
def new
@room = Room.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @room }
end
end
# GET /rooms/1/edit
def edit
permission = check_permission(params[:id])
unless permission
flash[:notice] = 'You cannot access the room!'
redirect_to user_path(current_user)
else
@room = permission.room
end
end
# POST /rooms
# POST /rooms.xml
def create
@room = Room.new(params[:room])
respond_to do |format|
if @room.save
@permission = Permission.new()
@permission.user_id = current_user.id
@permission.room_id = @room.id
role = Role.find(:first, :conditions => ["rolename = ?", 'administrator'])
@permission.role_id = role.id
unless @permission.save
flash[:notice] = 'A trouble occurs!'
format.html { render :action => "new" }
format.xml { render :xml => @room.errors, :status => :unprocessable_entity }
end
flash[:notice] = 'Room was successfully created.'
format.html { redirect_to(@room) }
format.xml { render :xml => @room, :status => :created, :location => @room }
else
format.html { render :action => "new" }
format.xml { render :xml => @room.errors, :status => :unprocessable_entity }
end
end
end
# PUT /rooms/1
# PUT /rooms/1.xml
def update
permission = check_permission(params[:id])
unless permission
flash[:notice] = 'You cannot access the room!'
redirect_to user_path(current_user)
else
@room = permission.room
respond_to do |format|
if @room.update_attributes(params[:room])
flash[:notice] = 'Room was successfully updated.'
format.html { redirect_to(@room) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @room.errors, :status => :unprocessable_entity }
end
end
end
end
# DELETE /rooms/1
# DELETE /rooms/1.xml
def destroy
permission = check_permission(params[:id])
unless permission
flash[:notice] = 'You cannot access the room!'
redirect_to user_path(current_user)
else
room = permission.room
flash[:notice] = "Room #{room.roomname} was successfully deleted."
room.destroy
permission.destroy
respond_to do |format|
format.html { redirect_to(user_path(current_user)) }
format.xml { head :ok }
end
end
end
private
def check_permission(room_id)
@user = current_user
permission = Permission.find(:first, :conditions => ["room_id = ? and user_id = ?", room_id,@user.id ])
if permission.nil?
false
else
permission
end
end
end
ビューの編集_
次はビューの編集。まず、最初にこのアプリケーションのレイアウトを作成する。このレイアウトは少なくとも以下を含むこと。
- 「ビュー」をクリック
- 「layout」をクリック
- application.html.erbを編集。以下を加えると、ログイン時にユーザー名が表示され、個人情報の変更(Edit Profile)、パスワードの変更(Change Password)、ログアウト(Log Out)のリンクが表示される。ログインしており、かつ、administratorならば、その旨が表示される。未ログインならば、ログイン(Log In)、アカウント作成(Sign Up)、パスワード忘れ処理(Forgot Password)が表示される。
<% if logged_in? -%>
<p>Current User: <%=h current_user.login -%> <%=link_to "[#{current_user.login}'s page]", user_path(self.current_user)-%>, <%= link_to "[logout]", logout_path -%></p>
<% else -%>
<p>Not Logged in <%= link_to "[login]", login_path -%> or <%= link_to "[signup]", signup_path -%></p>
<% end -%>
app/views/passwords/edit.html.erbの編集。
<h2>パスワードの変更</h2>
<% form_tag url_for(:action => "update", :id => params[:id]) do %>
Password:<br />
<%= password_field_tag :password %><br />
Confirm Password:<br />
<%= password_field_tag :password_confirmation %><br />
<%= submit_tag "Reset Your Password" %>
<% end %>
app/views/passwords/new.html.erbの編集。
<h2>パスワードの再送付</h2>
<% form_tag url_for(:action => 'create') do %>
What is the email address used to create your account?<br />
<%= text_field_tag :email, "", :size => 50 %><br />
<%= submit_tag 'Reset Password' %>
<% end %>
app/views/accounts/edit.html.erbの編集。
<% form_tag url_for(:action => "update") do %>
<p><label for="old_password" class="block">Old Password</label><br />
<%= password_field_tag 'old_password', @old_password, :size => 45 %></p>
<p><label for="password" class="block">New Password</label><br />
<%= password_field_tag 'password', {}, :size => 45 %><br />
<small>Between 4 and 40 characters</small></p>
<p><label for="password_confirmation" class="block">Confirm new password</label><br />
<%= password_field_tag 'password_confirmation', {}, :size => 45 %></p>
<%= submit_tag 'Change password' %>
<% end %>
app/views/roles/_role.html.erbの編集。
- 「ビュー」をクリック
- 「roles」にカーソルを合わせて右クリック
- 「新規作成」→「ERBファイル」
- ファイル名を「_role.html.erb」とし、以下のように編集する。
<li>
<%= role.rolename %>
<% if @user.has_role?(role.rolename) %>
<%= link_to 'remove role', user_role_url(:id => role.id, :user_id => @user.id), :method => :delete %>
<% else %>
<%= link_to 'assign role', user_role_url(:id => role.id, :user_id => @user.id), :method => :put %>
<% end %>
</li>
app/views/roles/index.html.erb:
<h2>Roles for <%=h @user.login.capitalize %></h2> <h3>Roles assigned:</h3> <ul><%= render :partial => 'role', :collection => @user.roles %></ul> <h3>Roles available:</h3> <ul><%= render :partial => 'role', :collection => (@all_roles - @user.roles) %></ul>
他のapp/views/rolesのビューに関しては削除する。
app/views/sessions/new.html.erbの編集。
<h2>Login with User ID and Password:</h2> <% form_tag session_path do %> <p><label for="login">Login</label><br/> <%= text_field_tag 'login' %></p> <p><label for="password">Password</label><br /> <%= password_field_tag 'password' %></p> <p><label for="remember_me">Remember me:</label> <%= check_box_tag 'remember_me' %></p> <p><%= submit_tag 'Log in' %><%= link_to "Sign Up", new_user_path %></p> <% end %>
app/views/user_mailer/activation.html.erbの編集。これがメールの本文。
<%=h @user.login %>, your account has been activated. To visit the site, follow the link below: <%= @url %>
app/views/user_mailer/forgot_password.html.erbの編集。これがメールの本文。
<%=h @user.login %>, to reset your password, please visit <%= @url %>
app/views/user_mailer/reset_password.html.erbの編集。これがメールの本文。
<%=h @user.login %>, Your password has been reset
app/views/user_mailer/signup_notification.html.erbの編集。これがメールの本文。
Your account has been created. Username: <%=h @user.login %> Visit this url to activate your account: <%= @url %>
app/views/users/_user.html.erbの編集。
<tr class="<%= cycle('odd', 'even') %>">
<td><%=h user.login %></td>
<td><%=h user.email %></td>
<td><%= user.enabled ? 'yes' : 'no' %>
<% if user.enabled %>
<%= link_to('disable', user_path(user.id), :method => :delete) %>
<% else %>
<%= link_to('enable', enable_user_path(user.id), :method => :put) %>
<% end %>
</td>
<td><%=h @numOfUserRooms[user.id].nil? ? 0 : @numOfUserRooms[user.id] -%> rooms</td>
</tr>
app/views/users/edit.html.erbの編集。
<h2>Edit Your Account</h2>
<p><%= link_to 'Show Profile', user_path(@user) %> | <%= link_to 'Change Password', change_password_path %></p>
<%= error_messages_for :user %>
<% form_for :user, :url => user_url(@user), :html => { :method => :put } do |f| %>
<p>Email:<br /><%= f.text_field :email, :size => 60 %></p>
<%= submit_tag 'Save' %>
<% end %>
app/views/users/index.html.erbの編集。
<h2>All Users</h2>
<table>
<tr>
<th>Username</th>
<th>Email</th>
<th>Enabled?</th>
<th>Rooms</th>
</tr>
<%= render :partial => 'user', :collection => @users -%>
</table>
app/views/users/new.html.erbの編集。
<%= error_messages_for :user %> <% form_for :user, :url => users_path do |f| %> <p><label for="login">Login</label><br/> <%= f.text_field :login %></p> <p><label for="email">Email</label><br/> <%= f.text_field :email %></p> <p><label for="password">Password</label><br/> <%= f.password_field :password %></p> <p><label for="password_confirmation">Confirm Password</label><br/> <%= f.password_field :password_confirmation %></p> <p><%= submit_tag 'Sign up' %></p> <% end %>
app/views/users/show.html.erbの編集。
<h2>User: <%=h @user.login %></h2>
<% if @user.is_server_admin? -%>
<h3>管理者操作</h3>
<ul>
<li><%= link_to "利用者一覧", users_path %></li>
</ul>
<% end -%>
<h3>登録情報</h3>
<ul>
<li><%= link_to "登録情報の編集", edit_user_path(current_user) %></li>
<li><%= link_to "パスワードの変更", change_password_path %></li>
</ul>
<h3>調査室</h3>
<p><%= link_to "調査室を開設する", new_room_path -%></p>
<ul>
<% @myRooms.each do |room | -%>
<li><%= link_to "#{room.roomname}", room_path(room.id) -%> </li>
<% end %>
</ul>
<p>Joined on: <%= @user.created_at.to_s(:long) %></p>
app/views/rooms/new.html.erbの編集。
<h1>New room</h1>
<% form_for(@room) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :roomname %><br />
<%= f.text_field :roomname %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', user_path(current_user.id) %>
app/views/rooms/edit.html.erbの編集。
<h1>Editing room</h1>
<% form_for(@room) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :roomname %><br />
<%= f.text_field :roomname %>
</p>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
<%= link_to 'Show', @room %> |
<%= link_to 'Back', user_path(current_user.id) %>
app/views/rooms/show.html.erbの編集。
<h2><%=h @room.roomname -%></h2> <ul> <li><%= link_to '調査室の編集', edit_room_path(@room) -%></li> <li><%= link_to '調査室の削除', room_path(@room), :method => 'DELETE' -%></li> <li><%= link_to 'トップページに戻る', user_path(current_user.id) -%></li> </ul>
routes.rbの編集。_
最後にconfig/routes.rbを編集する。「ActionController::Routing::Routes.draw do |map|」から「end」の間に以下を追加する。
# root
map.root :controller => "users", :action => "show"
# login/signup
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
map.activate '/activate/:id', :controller => 'accounts', :action => 'show'
map.forgot_password '/forgot_password', :controller => 'passwords', :action => 'new'
map.reset_password '/reset_password/:id', :controller => 'passwords', :action => 'edit'
map.change_password '/change_password', :controller => 'accounts', :action => 'edit'
map.resources :users, :member => { :enable => :put } do |users|
users.resource :account
users.resources :roles
end
map.resource :session
map.resource :password
map.resources :rooms
public/index.htmlを削除しないとmap.rootで設定したページがトップページにならないので注意。初期ユーザー ID:ServerAdimin、Password:adminでログインできる。Signupした場合は、開発環境ならばWebrickのログにメールが表示される。
サイトを立ち上げるために、データベースを作る必要があるので、マイグレーションを実行する。
- プロジェクト名にカーソルを合わせ右クリック「Rakeタスクを実行」→「db:reset」を選択し、実行。
- fixturesを使っている場合は
- test/fixtures/以下のroles.yml、rooms.yml, users.yml, permissions.ymlの中身を確認しておくこと、不要なら空っぽにしておく
- プロジェクト名にカーソルを合わせ右クリック「Rakeタスクを実行」
- 「db:fixtures:load」を選択し、実行。
あとは、ユーザ認証が必要なコントローラーにbefore_filterを付け加えるだけ。
- 「before_filter :login_required」ならば、ログインしないとそのページは見れない
- 「before_filter :check_server_administrator_role」ならば、ログインかつ湯ユーザー ServerAdmin じゃないとそのページは見れない
- 「before_filter :not_logged_in_required」ならば、ログインしていない状態でないとそのページが見れない。
各種フィルターについては、lib/authenticated_system.rbを参照。新しいフィルターを付け加える場合も、上記ファイルに付け加える。
既存のプロジェクトの修正_
次の点に注意する。
- もともとUserモデルが無かった場合は、既存のモデルとUserモデル間の関係をちゃんと定義すること
- ログインしているユーザの情報はcurrent_userというメソッド(lib/authenticated_system.rbで定義されている)で取得できる。
公開するときのメモ_
- 環境変数 RAILS_ENVを「production」に設定しておくこと。
- Cシェル系の場合
setenv RAILS_ENV production
- bash系の場合
RAILS_ENV=production export RAILS_ENV
- Cシェル系の場合
- passerngerを用いた公開の場合、ソース更新の度にApacheを再起動する必要がある
- メールに関するエラーはpostfixのログ、CGIエラーはapacheのログ、Railsのエラーは、log/production.log。
- postfixのログで「fatal: Recipient addresses must be specified on the command line or via the -t option」とでていたら、sendmailの設定ミス。mail.rbの設定を見直すこと。
# Email settings ActionMailer::Base.delivery_method = :sendmail ActionMailer::Base.sendmail_settings = { :location => "/usr/sbin/sendmail -t" - postfixのログで「lost connection after HELO from localhost[127.0.0.1]」というエラーがでたら、SMTPではなく、sendmailを使った方が良い。