Ruby on Rails チュートリアル
-
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
|
||
第2版 目次
|
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
第12章Rails 4.0へのアップグレード
本章は、Railsを最新バージョンであるRails 4.0にアップグレードするために追加された補足資料です。本書はRails入門を目的としたチュートリアルであるため、その意味では本書のRails 3.2対応版とRails 4.0対応版には大きな違いは生じません。実際、本章で補足すべきRail 4.0の重要な新機能は、Strong Parameterしかありません。Rail 4.0を使用した本格的なWeb開発を学びたいという要望に応えて、Rails 4.0対応版のRailsチュートリアルも公開いたしました。オンライン版は無料にて提供、電子書籍版 (PDF/EPUB) は http://tatsu-zine.com/books/railstutorial から購入することができます。なお、Rails 3.2対応版とRails 4.0対応版の主な違いについては、Rails 4.0向けチュートリアルのコラム1.1を参照してください。
本章の最初の目的は、Railsチュートリアルのサンプルアプリケーションを、Rails 3.2からRails 4.0にアップグレードすることです (12.1)。次に、主要な変更点である、attr_accessible (6.1.2.2) からStrong Parameter (12.2) への変更について説明します。最後に、2つの小さな機能をサンプルアプリケーションに追加します。この追加機能は、Rails 4.0とは本質的には関係ありませんが、セキュリティ上の理由から、この場を借りて12.3で解説します。
加えて、本章の内容を補うためのスクリーンキャストも制作しました。こちらもrailstutorial.jp/screencasts から購入することができます。本スクリーンキャストは、サンプルアプリケーションをRails 3.2からRails 4.0にアップグレードするところをライブデモする動画です。Strong Parametersやセキュリティ上の更新を実際にデモしながら解説しています。
12.1Rails 3.2からRails 4.0へのアップグレード
Railsとそれを取り囲むエコシステムは比較的成熟してきましたが、Railsは今でも急速に変化し続けています。今回のようなメジャーな更新が発生すると、多くの細かな機能が損なわれるのが普通です。実際、著者自身も既存のRailsプロジェクトをその場でアップグレードしようとした際、しばしば苦い経験をしてきました。そしてそのたびに、最後にはrails new
を使ってアプリケーションを最初から作り直すはめになってしまいました。それというのも、rails new
によって生成される大量の設定ファイルにはさまざまな数値やデフォルト値が設定されているのですが、これらの値はバージョンによって異なっているからです。そしてこれらの値を既存のプロジェクトに反映する作業は非常に困難です。そこで本節では、次のような方針でアップグレードの作業を進めることにします。最初に、空のRails 4.0のプロジェクトを作成し、プロジェクトをとにかく動く状態にします。その後、既存のプロジェクトから順次コードを移植していき、アプリケーションが正しく動作することを1つずつ確認しながら、作業を進めていきます。
Railsアプリケーションのアップグレードは難易度の高い作業です。したがって、本節の難易度も高くなっています。本節をお読みいただければわかるように、Railsのアプリケーションのアップグレードは、残念ながら「本に載っている手順に従うだけで完了するような作業」ではありません。実際、本節で行うのは演習の延長線上にある応用作業です。これまでの演習との違いは、多くの役に立つヒントとマニュアル (スクリーンキャスト) が用意されている点です。たとえば「アップグレードされたスクリーンキャスト」では、著者が悪戦苦闘の末にアップグレードを成功させる様子を見ることができます。なお、「Railsアプリケーションのアップグレードの途中で行き詰まってしまったけど、スクリーンキャストは購入したくない」という場合は、GitHub上のアップグレード済みサンプルアプリケーションを参考にしてください。
12.1.1Rails 4.0のセットアップ
Rails 4.0を使ってアプリケーションをセットアップする手順は、第1章から第3章までの手順と実質的には同じです。具体的には、1) RVMのgemsetの作成 (またはrbenvの環境構築)、2) Railsのインストール、3) Bundlerを使ったgemのインストール、4) RSpecの設定、という手順で進めます。
もしRVMまたはrbenvを既に使っているのであれば、Rails 4.0対応版チュートリアルの第1章に従って、システムを設定することできます。たとえばRVMを使っている場合は、Rails 4.0のサンプルアプリケーション用に新しくgemsetを作成することをお勧めします。その場合は次のコマンドを実行します。
$ rvm use 2.0.0@railstutorial_rails_4_0_upgrade --create
次に、Rails 4.0をインストールします。
$ gem install rails -v 4.0.0
次に、アプリケーションをアップグレードするための新規プロジェクトを作成します。
$ cd ~/rails_projects
$ rails new sample_app_4_0 --skip-test-unit
$ cd sample_app_4_0
この時点で、rails server
を実行してアプリケーションをローカルで走らせ、正常に動作していることを確認してください。
次に、アップグレードに必要な全てのgemをインストールします。具体的には、Gemfileをリスト12.1のように書き換えます。
Gemfile
。 source 'https://rubygems.org'
ruby '2.0.0'
gem 'rails', '4.0.0'
gem 'bootstrap-sass', '2.3.2.0'
gem 'bcrypt-ruby', '3.0.1'
gem 'faker', '1.1.2'
gem 'will_paginate', '3.0.4'
gem 'bootstrap-will_paginate', '0.0.9'
group :development, :test do
gem 'sqlite3', '1.3.7'
gem 'rspec-rails', '2.13.1'
# The following optional lines are part of the advanced setup.
# gem 'guard-rspec', '2.5.0'
# gem 'spork-rails', github: 'sporkrb/spork-rails'
# gem 'guard-spork', '1.5.0'
# gem 'childprocess', '0.3.6'
end
group :test do
gem 'selenium-webdriver', '2.0.0'
gem 'capybara', '2.1.0'
gem 'factory_girl_rails', '4.2.0'
gem 'cucumber-rails', '1.3.0', :require => false
gem 'database_cleaner', github: 'bmabey/database_cleaner'
# Uncomment this line on OS X.
# gem 'growl', '1.0.3'
# Uncomment these lines on Linux.
# gem 'libnotify', '0.8.0'
# Uncomment these lines on Windows.
# gem 'rb-notifu', '0.0.4'
# gem 'win32console', '1.3.2'
end
gem 'sass-rails', '4.0.0'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.0'
gem 'jquery-rails', '2.2.1'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'
group :doc do
gem 'sdoc', '0.3.20', require: false
end
group :production do
gem 'pg', '0.15.1'
gem 'rails_12factor', '0.0.2'
end
gem 'protected_attributes'
リスト12.1の最後の行に注目してください。このgem は、attr_accessible
(6.1.2.2) をRails 4.0のアプリケーションでも使えるようにするために必要なgemです。
gem 'protected_attributes'
なお、attr_accessibleをStrong Parametersに変更し終わったらこのgemは必要なくなるので、このgemは12.2で削除する予定です。
いつもと同様に、bundle install
を実行することでリスト12.1のgemをインストールできます。また、bundle update
も実行しておいてください (念のため、もう一度bundle install
を実行しておくと安心です)。
$ bundle install --without production
$ bundle update
$ bundle install
gemをインストールする際に (rspec-railsと一緒に) RSpecもインストールされますが、generateコマンドを使ってRSpecを再度インストールする必要があります。
$ rails generate rspec:install
それではRails 3.2のアプリケーションの移植作業を始めましょう。最初に、生成されたREADME
ファイルを削除して、Markdown 版の README (リスト3.2) に置き換えてください。
$ rm -f README.rdoc
$ cp ../sample_app_2nd_ed/README.md .
$ cp ../sample_app_2nd_ed/.rspec .
このとき、RSpec の設定ファイル (.rspec
) も一緒にコピーしておいてください。
この時点で、サンプルアプリケーションをGit管理下に置くのが賢い方法です。Railsのアップグレードのような多くのエラーが発生する作業では、Gitは本当に役に立ちます。また、今のうちにRailsが自動生成した.gitignore
ファイルをリスト12.2の内容に置き換えておくことをお勧めします。
.gitignore
ファイル。 # Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea
.secret
リスト12.2では、.secret
ファイルを無視する行が追加されていることに注目してください。このファイルは、セキュリティの向上のために生成されたファイルです。詳細については12.3.1で説明します。
最後に、Capybaraのドメイン固有言語 (DSL: domain-specific language) を明示的にインクルードする設定が必要です。これは、RSpecのrequest spec (統合テスト) 機能を使うために、デフォルトの文法を変更しておく必要があるからです。実際には、リスト12.3に示すように、spec_helper.rb
に1行追加するだけで設定できます。
spec/spec_helper.rb
.
.
.
RSpec.configure do |config|
.
.
.
config.include Capybara::DSL
end
$ git init
$ git add .
$ git commit -m "Initialize repository"
本書の内容を簡潔にするために、以後、Gitコミットの指示を省略しますが、折を見てGitコミットするように心掛けてください。
12.1.2テストをパスするようにする
アップグレードを完了するために、本チュートリアルで全面的に使用されているテストスイートをここでも活用することにします。このようなときには、既存アプリケーションのtestディレクトリまたはテストファイルを (一度にすべてではなく) 1つずつ新しいRailsプロジェクトにコピーし、そのたびにそれに対応するアプリケーションコードを順次コピーする、という手順をお勧めします。最初はテストを実行できる状態にするだけで試行錯誤が必要になることでしょう。それを終え、実際にテストを行うと、当初はほとんどの場合失敗する (赤色になる) はずです。その状態からファイルを変更して、このテストがパスする (緑色になる) ようになればよいわけです。ファイルを変更するとは、つまりそのテストに対応するコードを既存のアプリケーションからコピーしてくるということです。ただし、Rails 3.2とRails 4.0の違い、または関連するgem (特にRSpecとCapybara) の違いによってはこのシナリオ通りにならず、コードをコピーしてきてもテストが失敗することがあります。
著者の経験では、最初にコピーするのはモデルのspecと、それに対応するモデルのコードにしておくのがよいようです。モデルのテストは、コントローラやビューの結合テストと比べるとモデル内で完結している傾向が高いためです。そこで、モデルをテストできるようにするために最初にマイグレーションからコピーしましょう。
$ cp -r ../sample_app_2nd_ed/db/migrate db/
$ rake db:migrate
$ rake test:prepare
../sample_app_2nd_ed/db/migrate
の末尾にスラッシュを追加しないよう注意してください。システムによっては、マイグレーションファイルがコピーされてもディレクトリがコピーされないことがあります。なお、Rails 4.0ではrake db:test:prepare
コマンドを使用するのが一般的ですが、従来のrake db:test:prepare
コマンドも問題なく動作します。
以後、このようにテストとアプリケーションを既存のアプリケーションから順次新しいアプリケーションにコピーすることを繰り返し、移行を完了させます。コピーに使用するツールは、コマンドラインでもGUIファイルブラウザでも好みのもので構いません。
$ cp -r ../sample_app_2nd_ed/spec/models spec/
$ cp -r ../sample_app_2nd_ed/app/models app/
ファイルのコピー時にはエラーが発生することも多く、システム環境に依存するコピー項目もあるので、本書ではコピーするファイルをすべての場合について列挙することはしません。ファイルをどのような順序でコピーするかについては、各自にお任せすることにします。
特に注意して頂きたいのが、generateで生成されたファイル (アプリケーションレイアウトapplication.html.erb
など) には重要なコードが含まれていることがあり、単純な上書きコピーを行えない場合があるということです。このような場合には、ご面倒でもコピー元とコピー先のファイルを開き、内容を確認しながら必要なコードだけを手動でコピーしてください (スクリーンキャストはこういう作業の説明が楽ですね)。
12.1.3特定の問題
アップグレードにともなって発生する多数の問題を事前にすべて予測することは、一般的には極めて困難です。本章では基本的に、一般的な戦略を述べるだけにしておきます。著者が遭遇した特定の問題についてはある程度記載いたしましたが、すべてのトラブルを網羅することは事実上不可能ですので、どうかご容赦願います。アップグレード作業にはどうしても相当なフラストレーションがつきまといます。この困難を突破するには、とにかく忍耐、とにかくネット検索、それしかありません (ご心配なく、ちゃんとカンニングペーパーも用意してあります)。
モデル
protected_attributes gem (リスト12.1) の動作がRails 3.2の「アクセス可能な属性」と異なっているため、マスアサインメントのセキュリティエラーをテストするコードは4.0では動作しなくなりました。このテストコードは削除する必要があります。たとえば、以下のコードはUserモデルのspec (spec/models/user_spec.rb)
ですが、これは削除が必要です。
describe "accessible attributes" do
it "should not allow access to admin" do
expect do
User.new(admin: true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
これに対応するテストがMicropostモデルとRelationshipモデルにもあるので、それらも削除してください。
モデルでは他にも、デフォルトのスコープ (10.1.4.1) に関して以下の非推奨警告が表示されます。
DEPRECATION WARNING: Calling #scope or #default_scope
with a hash is deprecated.
この問題は、次のように修正できます。
default_scope order: 'microposts.created_at DESC'
上のコードを以下に置き換えます。
default_scope -> { order('created_at DESC') }
置き換え対象のコードはapp/models/micropost.rb
にあります。Rails 4.0のdefault_scope
は、従来のハッシュに代えて、Ruby 1.9で導入されたlambda
を引数に取るように変更されました。->
は、Ruby 1.9で導入されたlambda
の略記法です。詳細については4.0チュートリアルの第10章を参照してください。
Micropost spec (spec/models/micropost_spec.rb
) もわずかに変更が必要です。dup
メソッドを使用して@user.micropostsを複製していますが、これをto_a
メソッド (配列への変換) に置き換えます。以下はdup
を使用した既存のコードです。
it "should destroy associated microposts" do
microposts = @user.microposts.dup
@user.destroy
microposts.should_not be_empty
microposts.each do |micropost|
Micropost.find_by_id(micropost.id).should be_nil
end
end
理由は不明ですが、dup
呼び出しは Rails 4.0では動作しなくなりました。to_a
に置き換えることで正常に動作します。
it "should destroy associated microposts" do
microposts = @user.microposts.to_a
@user.destroy
expect(microposts).not_to be_empty
microposts.each do |micropost|
expect(Micropost.where(id: micropost.id)).to be_empty
end
end
コントローラとビュー
Relationshipsはテストの量がかなり少ないので、次はRelationshipsのテストを行うことをお勧めします。テストを行うと、ルーティングが失敗します。これは、Rails 4.0ではconfig/routes.rb
のmatch
でHTTPメソッドをvia
で明示的に指定することが要求されるようになったためです。
resources :users do
member do
get :following, :followers
end
end
resources :sessions, only: [:new, :create, :destroy]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
root to: 'static_pages#home'
match '/signup', to: 'users#new', via: 'get'
match '/signin', to: 'sessions#new', via: 'get'
match '/signout', to: 'sessions#destroy', via: 'delete'
match '/help', to: 'static_pages#help', via: 'get'
match '/about', to: 'static_pages#about', via: 'get'
match '/contact', to: 'static_pages#contact', via: 'get'
次はrequest specです。
it { should have_selector('title', text: 'Sign up') }
上のようなコードは、Capybaraの変更が原因で失敗します。Capybaraのhave_selector
は画面に実際に表示される要素にしか適用できなくなりました (ページタイトルは一部のブラウザではトップバーに表示されますが、「実際に表示される要素」としては扱われません)。これを解決するには、新しいhave_title
メソッドを使用します。
it { should have_title(text: 'Sign up') }
正規表現による置き換えをサポートするエディタ (VimやSublime Textなど) を使用できる場合は、以下の正規表現を使用して一括置き換えを行えます。
have_selector\('title', text: (.*?)\)
置き換える文字列は以下を使用します。
have_title($1)
ただし、静的なページ用のspec (spec/requests/static_pages_spec.rb
) では括弧を使用していないので、以下の正規表現も使用する必要があります。
have_selector 'title', text: (.*?)
置き換える文字列は以下を使用します。
have_title $1
(テキストエディタを使用して置き換えを行うという部分の意味がよくわからない場合は、スクリーンキャストを購入いただければその中で実際の操作をお見せいたします)
Capybaraの変更によって動かなくなった部分は他にもあります。input
フィールドのtitle
タグは画面に表示されないので、have_selector
が効かなくなりました。これを解決するには、強力なXPathメソッドを使用します。このメソッドはHTML5を含むXMLドキュメントの要素を自在にナビゲートできます。変更は以下のとおりです。
describe "toggling the button" do
before { click_button "Follow" }
it { should have_selector('input', value: 'Unfollow') }
end
.
.
.
describe "toggling the button" do
before { click_button "Unfollow" }
it { should have_selector('input', value: 'Follow') }
end
上を以下のように置き換えます。
describe "toggling the button" do
before { click_button "Follow" }
it { should have_xpath("//input[@value='Unfollow']") }
end
.
.
.
describe "toggling the button" do
before { click_button "Unfollow" }
it { should have_xpath("//input[@value='Follow']") }
end
置き換えの対象はspec/requests/user_pages_spec.rb
にあります。
著者が遭遇した最後のエラーは、以下のコードのあいまいな箇所についてCapybaraが警告してきたことです。
it "should be able to delete another user" do
expect { click_link('delete') }.to change(User, :count).by(-1)
end
上のコードはspecs/requests/user_pages_spec.rb
にあります。以前のCapybaraは、古いdeleteリンクを問題なくクリックすることができましたが、新しいバージョンでは動作が厳密になり、リンクをCSS idなどで正確に指定することが要求されるようになりました。これはもちろん歓迎すべき変更です。このおかげで、テスト中に誤ったリンクがクリックされずに済むからです。しかしこのチュートリアルのサンプルでは、どのリンクがクリックされるかは重要ではありません。これを解決するには、match: :first
オプションハッシュを渡して、最初に見つけたdeleteリンクをクリックするようCapybaraに指示します。
it "should be able to delete another user" do
expect do
click_link('delete', match: :first)
end.to change(User, :count).by(-1)
end
これでテストスイートはパスするはずです。
12.1.4仕上げ
残る作業は、スタイルシートのコピーと、Bootstrap JavaScriptをapplication.js
に追加することです。
$ cp -r ../sample_app_2nd_ed/app/assets/* app/assets/
上のコピー中にapplication.js
を上書きしないように注意してください (上書きの警告が表示されたら [いいえ] をクリックします)。次に、application.js
の内容を12.4のとおりに更新し、Bootstrap Javascriptを追加します。
app/assets/javascripts/application.js
.
.
.
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap
//= require_tree .
ここまで問題なく進めることができれば、アプリケーションは4.0で動作するようになるはずです。以下のようにデータベースにサンプルデータを導入してローカルサーバーで実行することで、実際の動作を確認できます。
$ cp ../sample_app_2nd_ed/lib/tasks/* lib/tasks
$ rake db:populate
$ rails server
(上の手順ではスタイルシートがコピーされないことに気付いた方がいるかもしれません。著者もスクリーンキャストを制作中に気付きました。でもこのぐらいは簡単に修正できるはずです)
12.1.5追加機能
Rails 4.0には、本章の範疇を超える追加機能がいくつかあります。中でも代表的なのはTurbolinksでしょう。これはJavaScriptを使用してアプリケーションのユーザーインターフェイスを高速化する技法です。他にRussian doll caching (一度描画したHTMLをインテリジェントに保存して不要な再描画を抑制する新機能) もあります。このようなやや高度なトピックを理解するには、RailsCastsが便利です。
12.2Strong parameters
いよいよ、(Railsチュートリアルにとっては) Rails 3.2とRails 4.0の最大の違いであるStrong Parametersについて説明します。これはattr_accessible
を置き換えるものであり、マスアサインメントの脆弱性 (9.4.1.1) を防止するためのものです。attr_accessible
はモデルに対して適用しますが、Strong Parametersはコントローラ層に対して適用するという点が大きく異なります。認証や認可はコントローラで行われますが、それと同じ場所に適用するという点で、Strong Parametersは従来よりも適切な手法です。
以下の作業は、12.1でアップグレード完了したアプリケーションに対して行うことをお勧めしますが、GitHub上の完成品を使用してもかまいません。後者の場合は、ここより先の変更を適用していないブランチがありますので、それをチェックアウトすることをお勧めします。
$ git clone https://github.com/mhartl/sample_app_4_0_upgrade
$ cd sample_app_4_0_upgrade/
$ git checkout -t origin/only-upgraded
Strong Parametersを実装するために、最初にprotected_attributes gemをGemfile
から削除してください (リスト12.1)。次に、UserモデルとMicropostモデルからattr_accessible
を削除します。
Strong parametersを使用して、どのパラメータを必須とし、どのパラメータを許可するかを指定できます。さらに、params
ハッシュをまるごと渡すとエラーが発生するようになっているので、Railsはデフォルトでマスアサインメントの脆弱性から保護されるようになりました。この変更により、テストは失敗するようになったはずです。
この場合、params
ハッシュでは:user
属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしたいと考えています。これは、以下のように記述することで行うことができます。
params.require(:user).permit(:name, :email, :password, :password_confirmation)
このコードの戻り値は、params
ハッシュのバージョンと、許可された属性です (:user
属性がない場合はエラーになります)。
これらのパラメータを使いやすくするために、user_params
という外部メソッドを使用するのが慣習になっています。このメソッドは適切な初期化ハッシュを返します。変更の結果をリスト12.5に示します。
create
アクションでStrong Parametersを使用する。app/controller/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
sign_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def edit
end
def update
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in @user
redirect_to @user
else
render 'edit'
end
end
.
.
.
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
同じようなコードを使用して、マイクロポストも同様に保護します (リスト12.6)。
create
アクション。app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
.
.
.
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
以上で完了です。ついにサンプルアプリケーションはマスアサインメントの脆弱性に対する免疫を獲得しました。必要な更新も問題なく行えています。テストスイートを実行することでこのことを確認できます。
12.3セキュリティのアップデート
本章の最後に、小規模なセキュリティアップデートを2つ紹介します。通常、マイナーな変更の紹介は次の版に回すのですが、本章の冒頭に記したとおり、セキュリティ問題は常に最優先課題であるため、ここに記載いたします。
12.3.1秘密鍵
1番目のセキュリティ変更は、secret_token.rb
を更新して秘密鍵を動的に生成するようにします。この秘密鍵は、セッション変数を暗号化するためにRailsで使用されます。この秘密鍵は、デフォルトではリスト12.7に示したようにランダムな文字列がハードコードされます。ソースコードが非公開のWebアプリケーションであればこれでもよいのですが、Githubなどでソースを公開する場合にはこのままでは危険です。このトークンを一般にさらしてしまうと、悪意のある攻撃者がセッションcookiesを改ざんする可能性が生じます。
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
SampleApp::Application.config.secret_key_base = 'ced345e01611593
c1b783bae98e4e56dbaee787747e92a141565f7c61d0ab2c6f98f7396fb4b178
258301e2713816e158461af58c14b695901692f91e72b6200'
これを解決する方法は、秘密鍵をローカルで動的に生成してファイルに保存することです。実はこの問題はPhenoelit作 “Rails脆弱性自動レポート用ボット” が著者に親切にも警告してくれたおかげで気付いたもので、同ページに解決法も示されています。リスト12.8に示したように、この秘密鍵は必要に応じて読み出して利用されます。ここで特に注意して頂きたいのは、リスト12.2にも示したように、.secret
ファイルを.gitignore
に追記し、このファイルが間違っても一般からアクセス可能なリポジトリに置かれることのないようにする必要があるという点です。
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly, such as by adding
# .secret to your .gitignore file.
require 'securerandom'
def secure_token
token_file = Rails.root.join('.secret')
if File.exist?(token_file)
# Use the existing token.
File.read(token_file).chomp
else
# Generate a new token and store it in token_file.
token = SecureRandom.hex(64)
File.write(token_file, token)
token
end
end
SampleApp40::Application.config.secret_key_base = secure_token
12.3.2記憶トークンを暗号化する
最後のセキュリティアップデートは、記憶トークン (8.2.1) を暗号化することです。この暗号化のタイミングは、記憶トークンをユーザーのブラウザに保存した後、かつ記憶トークンをデータベースに保存する前でなくてはなりません。こうすることにより、万一データベースに対して不正アクセスが行われても、攻撃者は記憶トークンを使用して特定のユーザーとして不正にサインインすることはできなくなります。本章では必要なコードを示すだけにとどめます。詳細については、Rails 4.0用のチュートリアルの [このアカウント設定を保存する] を参照してください。
最初に、新しいメソッドを2つ追加します (Userインスタンスは不要なのでUser
クラスに追加します)。これにより、新しいユーザートークンを生成してSHA1暗号化アルゴリズムで暗号化します (リスト12.9)。
before_create
コールバックを使用してremember_token
属性を作成する。app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
before_create :create_remember_token
.
.
.
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
.
.
.
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
end
次に、sign_in
メソッドとcurrent_user
メソッドを更新して、cookies内の新しいトークンを使用するようにし、続いてトークンを暗号化してデータベースに保存します (リスト12.10)。(追記: ソルト (暗号を強化するために元の平文に追加する短い文字列のこと) を追加していないSHA1ハッシュは一般にはセキュアではないと言われることがありますが、これは元の平文がパスワードのような短くかつランダムではない文字列の場合です。ここで暗号化する平文は十分長くかつランダムなので、生成されたハッシュは本質的にクラック不可能です。)
app/helpers/sessions_helper.rb
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
.
.
.
def current_user
remember_token = User.encrypt(cookies[:remember_token])
@current_user ||= User.find_by(remember_token: remember_token)
end
.
.
.
end
少々残念なことに、記憶トークンをセキュアにするための変更を行ったことにより、従来のテストで使用していたサインインの取り扱い方法が使えなくなってしまいました。これを解決するには、テストユーティリティのsign_in
ヘルパーを変更し、Capybaraを使用せずにオプションを渡すようにします (リスト12.11)。詳細については4.0用チュートリアルの [このアカウント設定を保存する] を参照してください。
sign_in
ヘルパー。spec/support/utilities.rb
.
.
.
def sign_in(user, options={})
if options[:no_capybara]
# Capybaraを使用していない場合にもサインインする。
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
no_capybara
オプションが必要な部分は3箇所のみです (リスト12.12の1箇所とリスト12.13の2箇所)。ここで、リスト12.13ではpatch
メソッドが導入されたことに注目してください。これはHTTPのPATCH動詞に対応します。現時点ではPUTからPATCHへの変更は必須ではありませんが、Rails 4.0ではモデルオブジェクトの更新にはPATCHを使用することが推奨されており、将来のバージョンでは必須になる予定です。詳細については、4.0用のチュートリアル、特に「コラム 3.3 GETやその他のHTTPメソッドについて」を参照してください。
no_capybara
オプションを追加する。spec/controllers/relationships_controller_spec.rb
require 'spec_helper'
describe RelationshipsController do
let(:user) { FactoryGirl.create(:user) }
let(:other_user) { FactoryGirl.create(:user) }
before { sign_in user, no_capybara: true }
.
.
.
end
no_capybara
を追加するspec/requests/authentication_pages_spec.rb
require 'spec_helper'
describe "Authentication" do
.
.
.
describe "authorization" do
.
.
.
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
before { sign_in user, no_capybara: true }
describe "visiting Users#edit page" do
before { visit edit_user_path(wrong_user) }
it { should have_title(full_title('')) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { response.should redirect_to(root_url) }
end
end
describe "as non-admin user" do
let(:user) { FactoryGirl.create(:user) }
let(:non_admin) { FactoryGirl.create(:user) }
before { sign_in non_admin, no_capybara: true }
describe "submitting a DELETE request to the Users#destroy action" do
before { delete user_path(user) }
specify { response.should redirect_to(root_url) }
end
end
end
end
以上で、必要な変更は完了です。テストスイートは緑色 (成功) になるはずです。3.2から4.0へのアップグレードについては、追加のスクリーンキャストもご覧ください。Strong Parametersやセキュリティアップデートの実装を手順を追って動画で解説しています。アップグレード中に行き詰まってしまった場合にもお勧めです。
アプリケーションのアップグレードとセキュリティアップデートの適用が完了したら、次はRails 4.0用Railsチュートリアルに進むことになるでしょう (そうするかどうかはもちろん皆様の自由です)。3.2用チュートリアルを完了させた今なら、4.0用チュートリアルは短時間で完了させることができるでしょう。4.0用チュートリアルもやっておけば、新機能について学ぶことができ、3.2用チュートリアルから十分に見直された記述に触れることもできます。4.0のすべては無理としても、3.2から4.0の変更点について言及しているコラム1.1は読んでおくことをお勧めします。
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!