Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Моделирование пользователей
Добавление безопасного пароля
В этом разделе мы добавим последний из базовых атрибутов модели User: безопасный пароль используемый для аутентификации пользователей примера приложения. Методика заключается в запросе у каждого пользователя пароля (с подтверждением), а затем сохранении зашифрованной версии пароля в базе данных. Мы также добавим способ аутентификации пользователя опирающийся на данный пароль, метод, который мы будем использовать в Главе 8 для того чтобы дать пользователям возможность входить на сайт.
Метод для аутентификации будет принимать введеный пароль, шифровать его и сравнивать зашифрованное значение с хранящимся в базе данных. Если они совпадают, то введеный пароль корректен и пользователь аутентифицирован. Сравнивая зашифрованные пароли вместо того чтобы сравнивать пароли непосредственно, мы получаем возможность аутентифицировать пользователей не храня в базе данных сами пароли, тем самым избежав серьезной дыры в системе безопасности нашего приложения.
БОльшая часть механики безопасного пароля будет реализована с помощьо одного Rails-метода называемого has_secure_password (впервые был представлен в Rails 3.1). Поскольку очень многое в дальнейшем зависит от этого единственного метода, трудно разрабатывать безопасные пароли постепенно. Начиная с Раздела 6.3.2 я рекомендую добавить метод has_secure_password заранее, а затем комментировать его перед добавлением каждого нового теста для того чтобы обеспечить правильный цикл TDD. (Поскольку скринкасты позволяют демонстрировать более постепенный подход к разработке, заинтересованным читателям следует посмотреть Ruby on Rails Tutorial screencasts для более полного понимания этого материала.)
Зашифрованный пароль
Мы начнем с необходимого изменения модели данных для пользователей, что подразумевает добавление password_digest столбца в таблицу users ( рис. 6.5). Название digest пришло из терминологии криптографических хэш функций, а само имя password_digest необходимо для работы реализации в Разделе 6.3.4. Как следует зашифровав пароль, мы обеспечим невозможность получения доступа к сайту атакером, даже если он умудрится получить копию базы данных.
Мы будем использовать самую новомодную хэш функцию, называемую bcrypt для необратимого шифрования пароля в виде хэша пароля. Для того чтобы использовать bcrypt в примере приложения нам необходимо добавить гем bcrypt-ruby в наш Gemfile (Листинг 6.21).
source 'https://rubygems.org' ruby '2.0.0' #ruby-gemset=railstutorial_rails_4_0 gem 'rails', '4.0.2' gem 'bootstrap-sass', '2.3.2.0' gem 'bcrypt-ruby', '3.1.2' . . .Листинг 6.21. Добавление bcrypt-ruby в Gemfile.
Затем запускаем bundle install:
$ bundle install
Поскольку мы хотим чтобы пользователи имели столбец password digest, объект user должен отвечать на password_digest, что приводит нас к тесту показанному в Листинге 6.22.
require 'spec_helper' describe User do before do @user = User.new(name: "Example User", email: "user@example.com") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should respond_to(:password_digest) } . . . endЛистинг 6.22. Проверка того, что объект User имеет столбец password_digest. spec/models/user_spec.rb
Для того чтобы получить прохождение этого теста мы вначале генерируем соответствующую миграцию для столбца password_digest:
$ rails generate migration add_password_digest_to_users password_digest:string
Здесь первый аргумент это название миграции и мы также предоставили второй аргумент с названием и типом атрибута который мы хотим создать. (Сравните это с начальной генерацией таблицы users в Листинге 6.1.) Мы можем выбрать любое название для миграции, но было бы удобно, если бы ее название заканчивалось на _to_users, поскольку в этом случае Rails автоматически построит миграцию для добавления столбцов к таблице users. Кроме того, включив второй аргумент, мы дали Rails достаточно информации для построения для нас всей миграции, как это видно в Листинге 6.23.
class AddPasswordDigestToUsers < ActiveRecord::Migration def change add_column :users, :password_digest, :string end endЛистинг 6.23. Миграция для добавления столбца password_digest к таблице users. db/migrate/[ts]_add_password_digest_to_users.rb
Этот код использует метод add_column для добавления столбца password_digest к таблице users.
Мы можем получить прохождение провального теста из Листинга 6.22 запустив миграцию базы данных разработки и подготовив тестовую базу данных:
$ bundle exec rake db:migrate $ bundle exec rake test:prepare $ bundle exec rspec spec/
Пароль и подтверждение
Как видно на наброске рис. 6.1, мы ожидаем что пользователи должны будут подтверждать свои пароли, что является общепринятой в сети практикой минимизирующей риск опечаток при введении пароля. Мы можем реализовать это на уровне контроллера, но принято делать это в модели и использовать Active Record для наложения этого ограничения. Метод заключается в добавлении password и password_confirmation атрибутов к модели User и последующем требовании совпадения этих двух атрибутов перед сохранением записи в базе данных. В отличие от всех остальных атрибутов, что мы видели до этого, атрибуты пароля будут виртуальными — они будут лишь временно существовать в памяти и не будут постоянно храниться в базе данных. Как мы увидим в Разделе 6.3.4, has_secure_password реализует эти виртуальные атрибуты автоматически .
Мы начнем с respond_to тестов для пароля и его подтверждения, как это показано в Листинге 6.24.
require 'spec_helper' describe User do before do @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should respond_to(:password_digest) } it { should respond_to(:password) } it { should respond_to(:password_confirmation) } it { should be_valid } . . . endЛистинг 6.24. Тестирование атрибутов password и password_confirmation. spec/models/user_spec.rb
обратите внимание - мы добавили :password и :password_confirmation в хэш инициализации для User.new:
before do @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end
Мы определенно не хотим чтобы пользователи могли вводить пустые пароли, так что мы добавим еще один тест для валидации наличия пароля:
describe "when password is not present" do before { @user.password = @user.password_confirmation = " " } it { should_not be_valid } end
Поскольку мы будем вскоре тестировать несовпадение пароля, здесь мы будем тестировать существование валидации наличия установив пароль и его подтверждение равными чистой строке.
Мы также хотим убедиться что пароль и его подтверждение совпадают. Случай, когда они совпадают покрыт с помощью it { should be_valid }, так что нам осталось протестировать только случай несовпадения:
describe "when password doesn't match confirmation" do before { @user.password_confirmation = "mismatch" } it { should_not be_valid } end
Собрав все вместе, мы получаем (провальные) тесты в Листинге 6.25.
require 'spec_helper' describe User do before do @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should respond_to(:password_digest) } it { should respond_to(:password) } it { should respond_to(:password_confirmation) } it { should be_valid } . . . describe "when password is not present" do before do @user = User.new(name: "Example User", email: "user@example.com", password: " ", password_confirmation: " ") end it { should_not be_valid } end describe "when password doesn't match confirmation" do before { @user.password_confirmation = "mismatch" } it { should_not be_valid } end endЛистинг 6.25. Тест для пароля и его подтверждения. spec/models/user_spec.rb
Мы можем получить прохождение тестов из Листинга 6.25 используя лишь одну строку кода - как это показано в Листинге 6.26.
class User < ActiveRecord::Base . . . has_secure_password endЛистинг 6.26. Код необходимый для прохождения начальных тестов пароля. app/models/user.rb
Примечательно что одна линия
has_secure_password
приводит к прохождению всех текущих тестов пароля. На самом деле она делает гораздо больше, даже слишком много, мешая будущим тестам краснеть перед позеленением, так что прежде чем двигаться дальше я рекомендую закоментировать эту строку (Листинге 6.27).
class User < ActiveRecord::Base . . . # has_secure_password endЛистинг 6.27. Закомментированние has_secure_password для соблюдения цикла TDD. app/models/user.rb