├── init.rb
├── cucumber.yml
├── .rspec
├── Gemfile
├── lib
├── clearance
│ ├── version.rb
│ ├── password_strategies.rb
│ ├── testing
│ │ ├── assertion_error.rb
│ │ ├── helpers.rb
│ │ └── deny_access_matcher.rb
│ ├── engine.rb
│ ├── rack_session.rb
│ ├── testing.rb
│ ├── configuration.rb
│ ├── password_strategies
│ │ ├── blowfish.rb
│ │ └── sha1.rb
│ ├── session.rb
│ ├── authentication.rb
│ └── user.rb
├── generators
│ └── clearance
│ │ ├── install
│ │ ├── templates
│ │ │ ├── user.rb
│ │ │ ├── clearance.rb
│ │ │ ├── README
│ │ │ └── db
│ │ │ │ └── migrate
│ │ │ │ └── upgrade_clearance_to_diesel.rb
│ │ └── install_generator.rb
│ │ ├── views
│ │ └── views_generator.rb
│ │ └── features
│ │ └── features_generator.rb
└── clearance.rb
├── features
├── support
│ ├── aruba.rb
│ ├── clearance.rb
│ └── env.rb
├── engine
│ ├── visitor_signs_out.feature
│ ├── visitor_signs_up.feature
│ ├── visitor_signs_in.feature
│ └── visitor_resets_password.feature
├── step_definitions
│ ├── configuration_steps.rb
│ └── engine
│ │ └── clearance_steps.rb
├── integration_with_test_unit.feature
└── integration.feature
├── .gitignore
├── Appraisals
├── gemfiles
├── 3.1.4.gemfile
├── 3.2.3.gemfile
├── 3.0.12.gemfile
├── 3.0.12.gemfile.lock
├── 3.2.3.gemfile.lock
└── 3.1.4.gemfile.lock
├── app
├── views
│ ├── passwords
│ │ ├── create.html.erb
│ │ ├── new.html.erb
│ │ └── edit.html.erb
│ ├── users
│ │ ├── new.html.erb
│ │ └── _form.html.erb
│ ├── sessions
│ │ ├── new.html.erb
│ │ └── _form.html.erb
│ ├── clearance_mailer
│ │ └── change_password.html.erb
│ └── layouts
│ │ └── application.html.erb
├── mailers
│ └── clearance_mailer.rb
└── controllers
│ └── clearance
│ ├── sessions_controller.rb
│ ├── users_controller.rb
│ └── passwords_controller.rb
├── spec
├── factories.rb
├── spec_helper.rb
├── controllers
│ ├── flashes_controller_spec.rb
│ ├── denies_controller_spec.rb
│ ├── forgeries_controller_spec.rb
│ ├── users_controller_spec.rb
│ ├── sessions_controller_spec.rb
│ └── passwords_controller_spec.rb
├── configuration_spec.rb
├── mailers
│ └── clearance_mailer_spec.rb
├── clearance
│ ├── rack_session_spec.rb
│ └── session_spec.rb
├── support
│ ├── clearance.rb
│ └── cookies.rb
└── models
│ ├── clearance_user_spec.rb
│ ├── sha1_spec.rb
│ ├── blowfish_spec.rb
│ └── user_spec.rb
├── .travis.yml
├── db
├── migrate
│ └── 20110111224543_create_diesel_clearance_users.rb
└── schema.rb
├── Rakefile
├── config
└── routes.rb
├── LICENSE
├── CONTRIBUTING.md
├── clearance.gemspec
├── Gemfile.lock
├── README.md
└── NEWS.md
/init.rb:
--------------------------------------------------------------------------------
1 | require 'clearance'
--------------------------------------------------------------------------------
/cucumber.yml:
--------------------------------------------------------------------------------
1 | default: -r features
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format progress
3 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/lib/clearance/version.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | VERSION = '0.16.2'
3 | end
4 |
--------------------------------------------------------------------------------
/features/support/aruba.rb:
--------------------------------------------------------------------------------
1 | Before do
2 | @aruba_timeout_seconds = 60
3 | end
4 |
--------------------------------------------------------------------------------
/lib/generators/clearance/install/templates/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | include Clearance::User
3 | end
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.swp
3 | *.swo
4 | *~
5 | !.keep
6 | .bundle
7 | db/*.sqlite3
8 | log/*.log
9 | pkg
10 | tmp/
11 |
--------------------------------------------------------------------------------
/lib/generators/clearance/install/templates/clearance.rb:
--------------------------------------------------------------------------------
1 | Clearance.configure do |config|
2 | config.mailer_sender = 'reply@example.com'
3 | end
4 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | ['3.2.3', '3.1.4', '3.0.12'].each do |rails_version|
2 | appraise "#{rails_version}" do
3 | gem "rails", rails_version
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/gemfiles/3.1.4.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "3.1.4"
6 |
7 | gemspec :path=>"../"
--------------------------------------------------------------------------------
/gemfiles/3.2.3.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "3.2.3"
6 |
7 | gemspec :path=>"../"
--------------------------------------------------------------------------------
/gemfiles/3.0.12.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "3.0.12"
6 |
7 | gemspec :path=>"../"
--------------------------------------------------------------------------------
/app/views/passwords/create.html.erb:
--------------------------------------------------------------------------------
1 |
2 | You will receive an email within the next few minutes.
3 | It contains instructions for changing your password.
4 |
5 |
--------------------------------------------------------------------------------
/app/views/users/new.html.erb:
--------------------------------------------------------------------------------
1 | Sign up
2 |
3 | <%= form_for @user do |form| %>
4 | <%= render :partial => '/users/form', :object => form %>
5 | <%= form.submit 'Sign up' %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/spec/factories.rb:
--------------------------------------------------------------------------------
1 | FactoryGirl.define do
2 |
3 | sequence :email do |n|
4 | "user#{n}@example.com"
5 | end
6 |
7 | factory :user do
8 | email
9 | password "password"
10 | end
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/lib/generators/clearance/views/views_generator.rb:
--------------------------------------------------------------------------------
1 | require 'diesel/generators/views_base'
2 |
3 | module Clearance
4 | module Generators
5 | class ViewsGenerator < Diesel::Generators::ViewsBase
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/clearance/password_strategies.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | module PasswordStrategies
3 | autoload :SHA1, 'clearance/password_strategies/sha1'
4 | autoload :Blowfish, 'clearance/password_strategies/blowfish'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/generators/clearance/features/features_generator.rb:
--------------------------------------------------------------------------------
1 | require 'diesel/generators/features_base'
2 |
3 | module Clearance
4 | module Generators
5 | class FeaturesGenerator < Diesel::Generators::FeaturesBase
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/clearance.rb:
--------------------------------------------------------------------------------
1 | require 'clearance/configuration'
2 | require 'clearance/session'
3 | require 'clearance/rack_session'
4 | require 'clearance/authentication'
5 | require 'clearance/user'
6 | require 'clearance/engine'
7 | require 'clearance/password_strategies'
8 |
--------------------------------------------------------------------------------
/app/views/users/_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form.label :email %>
3 | <%= form.text_field :email, :type => "email" %>
4 |
5 |
6 | <%= form.label :password %>
7 | <%= form.password_field :password %>
8 |
9 |
--------------------------------------------------------------------------------
/app/views/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 | Sign in
2 |
3 | <%= render :partial => '/sessions/form' %>
4 |
5 |
6 | -
7 | <%= link_to "Sign up", sign_up_path %>
8 |
9 | -
10 | <%= link_to "Forgot password?", new_password_path %>
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.2
4 | - 1.9.3
5 | before_install:
6 | - gem update --system
7 | before_script:
8 | - "bundle exec rake db:migrate"
9 | gemfile:
10 | - gemfiles/3.0.12.gemfile
11 | - gemfiles/3.1.4.gemfile
12 | - gemfiles/3.2.3.gemfile
13 | branches:
14 | only:
15 | - master
16 |
--------------------------------------------------------------------------------
/lib/clearance/testing/assertion_error.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | module Testing
3 | if RUBY_VERSION > "1.9"
4 | require 'minitest/unit'
5 | AssertionError = MiniTest::Assertion
6 | else
7 | require 'test/unit/assertionfailederror'
8 | AssertionError = Test::Unit::AssertionFailedError
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/clearance_mailer/change_password.html.erb:
--------------------------------------------------------------------------------
1 | Someone, hopefully you, has requested that we send you a link to change your password.
2 |
3 | Here's the link:
4 |
5 | <%= edit_user_password_url(@user, :token => @user.confirmation_token.html_safe) %>
6 |
7 | If you didn't request this, ignore this email. Don't worry. Your password hasn't been changed.
8 |
--------------------------------------------------------------------------------
/lib/clearance/engine.rb:
--------------------------------------------------------------------------------
1 | require "clearance"
2 | require "rails"
3 |
4 | module Clearance
5 | class Engine < Rails::Engine
6 | initializer "clearance.filter" do |app|
7 | app.config.filter_parameters += [:token, :password]
8 | end
9 |
10 | config.app_middleware.insert_after ActionDispatch::Cookies, Clearance::RackSession
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/features/support/clearance.rb:
--------------------------------------------------------------------------------
1 | require 'clearance'
2 |
3 | Clearance.configure do |config|
4 | end
5 |
6 | class ApplicationController < ActionController::Base
7 | include Clearance::Authentication
8 |
9 | def show
10 | render :text => "", :layout => 'application'
11 | end
12 | end
13 |
14 | class User < ActiveRecord::Base
15 | include Clearance::User
16 | end
17 |
--------------------------------------------------------------------------------
/lib/clearance/rack_session.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | class RackSession
3 | def initialize(app)
4 | @app = app
5 | end
6 |
7 | def call(env)
8 | session = Clearance::Session.new(env)
9 | env[:clearance] = session
10 | response = @app.call(env)
11 | session.add_cookie_to_headers response[1]
12 | response
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/features/engine/visitor_signs_out.feature:
--------------------------------------------------------------------------------
1 | Feature: Sign out
2 |
3 | In order to protect my account from unauthorized access
4 | As a signed in user
5 | I want to sign out
6 |
7 | Scenario: User signs out
8 | Given I am signed up as "email@example.com"
9 | When I sign in as "email@example.com"
10 | Then I should be signed in
11 | When I sign out
12 | Then I should be signed out
13 |
--------------------------------------------------------------------------------
/app/views/sessions/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_for :session, :url => session_path do |form| %>
2 |
3 | <%= form.label :email %>
4 | <%= form.text_field :email, :type => "email" %>
5 |
6 |
7 | <%= form.label :password %>
8 | <%= form.password_field :password %>
9 |
10 |
11 | <%= form.submit "Sign in" %>
12 |
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/mailers/clearance_mailer.rb:
--------------------------------------------------------------------------------
1 | class ClearanceMailer < ActionMailer::Base
2 | def change_password(user)
3 | @user = user
4 | mail :from => Clearance.configuration.mailer_sender,
5 | :to => @user.email,
6 | :subject => I18n.t(:change_password,
7 | :scope => [:clearance, :models, :clearance_mailer],
8 | :default => "Change your password")
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/views/passwords/new.html.erb:
--------------------------------------------------------------------------------
1 | Reset your password
2 |
3 |
4 | We will email you a link to reset your password.
5 |
6 |
7 | <%= form_for :password, :url => passwords_path do |form| %>
8 |
9 | <%= form.label :email, "Email address" %>
10 | <%= form.text_field :email, :type => "email" %>
11 |
12 |
13 | <%= form.submit "Reset password" %>
14 |
15 | <% end %>
16 |
--------------------------------------------------------------------------------
/app/views/passwords/edit.html.erb:
--------------------------------------------------------------------------------
1 | Change your password
2 |
3 |
4 | Your password has been reset. Choose a new password below.
5 |
6 |
7 | <%= form_for(:user,
8 | :url => user_password_path(@user, :token => @user.confirmation_token),
9 | :html => { :method => :put }) do |form| %>
10 |
11 | <%= form.label :password, "Choose password" %>
12 | <%= form.password_field :password %>
13 |
14 |
15 | <%= form.submit "Save this password" %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/lib/clearance/testing/helpers.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | module Testing
3 | module Helpers
4 | def sign_in_as(user)
5 | @controller.current_user = user
6 | return user
7 | end
8 |
9 | def sign_in
10 | sign_in_as FactoryGirl.create(:user)
11 | end
12 |
13 | def sign_out
14 | @controller.current_user = nil
15 | end
16 |
17 | def setup_controller_request_and_response
18 | super
19 | @request.env[:clearance] = Clearance::Session.new(@request.env)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/clearance/testing.rb:
--------------------------------------------------------------------------------
1 | require 'clearance/testing/assertion_error'
2 | require 'clearance/testing/deny_access_matcher'
3 | require 'clearance/testing/helpers'
4 |
5 | if defined?(ActionController::TestCase)
6 | ActionController::TestCase.extend Clearance::Testing::Matchers
7 | class ActionController::TestCase
8 | include Clearance::Testing::Helpers
9 | end
10 | end
11 |
12 | if defined?(RSpec) && RSpec.respond_to?(:configure)
13 | RSpec.configure do |config|
14 | config.include Clearance::Testing::Matchers
15 | config.include Clearance::Testing::Helpers
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= javascript_include_tag :defaults %>
5 | <%= csrf_meta_tag %>
6 |
7 |
8 |
15 |
16 | <% flash.each do |key, value| -%>
17 |
<%=h value %>
18 | <% end %>
19 |
20 | <%= yield %>
21 |
22 |
23 |
--------------------------------------------------------------------------------
/db/migrate/20110111224543_create_diesel_clearance_users.rb:
--------------------------------------------------------------------------------
1 | class CreateDieselClearanceUsers < ActiveRecord::Migration
2 | def self.up
3 | create_table(:users) do |t|
4 | t.string :email
5 | t.string :encrypted_password, :limit => 128
6 | t.string :salt, :limit => 128
7 | t.string :confirmation_token, :limit => 128
8 | t.string :remember_token, :limit => 128
9 | t.timestamps
10 | end
11 |
12 | add_index :users, :email
13 | add_index :users, :remember_token
14 | end
15 |
16 | def self.down
17 | drop_table :users
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/features/engine/visitor_signs_up.feature:
--------------------------------------------------------------------------------
1 | Feature: Sign up
2 |
3 | In order to access protected sections of the site
4 | As a visitor
5 | I want to sign up
6 |
7 | Scenario: Visitor signs up with invalid email
8 | When I sign up with "invalidemail" and "password"
9 | Then I am told to enter a valid email address
10 |
11 | Scenario: Visitor signs up with blank password
12 | When I sign up with "email@example.com" and ""
13 | Then I am told to enter a password
14 |
15 | Scenario: Visitor signs up with valid data
16 | When I sign up with "email@example.com" and "password"
17 | Then I should be signed in
18 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 |
3 | PROJECT_ROOT = File.expand_path("../..", __FILE__)
4 | $LOAD_PATH << File.join(PROJECT_ROOT, "lib")
5 |
6 | require 'rails/all'
7 | require 'rails/test_help'
8 |
9 | Bundler.require
10 |
11 | require 'diesel/testing'
12 | require 'rspec/rails'
13 | require 'bourne'
14 | require 'timecop'
15 | require 'factory_girl_rails'
16 | require 'shoulda-matchers'
17 |
18 | require 'clearance/testing'
19 |
20 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
21 |
22 | RSpec.configure do |config|
23 | config.mock_with :mocha
24 | config.use_transactional_fixtures = true
25 | config.include FactoryGirl::Syntax::Methods
26 | end
27 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'rubygems'
4 | require 'bundler/setup'
5 | require 'bundler/gem_tasks'
6 |
7 | require 'rake'
8 | require 'diesel/tasks'
9 | require 'cucumber/rake/task'
10 | require 'rspec/core/rake_task'
11 | require 'appraisal'
12 |
13 | task :default do
14 | if ENV['BUNDLE_GEMFILE'] =~ /gemfiles/
15 | exec 'rake spec cucumber'
16 | else
17 | Rake::Task['appraise'].execute
18 | end
19 | end
20 |
21 | task :appraise => ['appraisal:install'] do
22 | exec 'rake appraisal spec cucumber'
23 | end
24 |
25 | RSpec::Core::RakeTask.new(:spec)
26 |
27 | Cucumber::Rake::Task.new(:cucumber) do |t|
28 | t.fork = true
29 | t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'progress')]
30 | end
31 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :passwords,
3 | :controller => 'clearance/passwords',
4 | :only => [:new, :create]
5 |
6 | resource :session,
7 | :controller => 'clearance/sessions',
8 | :only => [:new, :create, :destroy]
9 |
10 | resources :users, :controller => 'clearance/users', :only => [:new, :create] do
11 | resource :password,
12 | :controller => 'clearance/passwords',
13 | :only => [:create, :edit, :update]
14 | end
15 |
16 | match 'sign_up' => 'clearance/users#new', :as => 'sign_up'
17 | match 'sign_in' => 'clearance/sessions#new', :as => 'sign_in'
18 | match 'sign_out' => 'clearance/sessions#destroy', :via => :delete, :as => 'sign_out'
19 | end
20 |
--------------------------------------------------------------------------------
/lib/generators/clearance/install/templates/README:
--------------------------------------------------------------------------------
1 |
2 | *******************************************************************************
3 |
4 | Next steps:
5 |
6 | 1. Configure the mailer to create full URLs in emails:
7 |
8 | # config/environments/{development,test}.rb
9 | config.action_mailer.default_url_options = { :host => 'localhost:3000' }
10 |
11 | In production it should be your app's domain name.
12 |
13 | 2. Display flashes. For example, in your application layout:
14 |
15 | <% flash.each do |key, value| -%>
16 | <%= value %>
17 | <% end -%>
18 |
19 | 3. Migrate:
20 |
21 | rake db:migrate
22 |
23 | *******************************************************************************
24 |
--------------------------------------------------------------------------------
/spec/controllers/flashes_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | class FlashesController < ActionController::Base
4 | include Clearance::Authentication
5 |
6 | def set_flash
7 | flash[:notice] = params[:message]
8 | redirect_to view_flash_url
9 | end
10 |
11 | def view_flash
12 | render :text => "#{flash[:notice]}"
13 | end
14 | end
15 |
16 | describe FlashesController do
17 | before do
18 | Rails.application.routes.draw do
19 | match "set_flash" => "flashes#set_flash"
20 | match "view_flash" => "flashes#view_flash"
21 | end
22 | end
23 |
24 | after do
25 | Rails.application.reload_routes!
26 | end
27 |
28 | it "sets and views a flash" do
29 | visit "/set_flash?message=hello"
30 | page.should have_content("hello")
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::Configuration do
4 | describe "when no user_model_name is specified" do
5 | before do
6 | Clearance.configure do |config|
7 | end
8 | end
9 |
10 | it "defaults to User" do
11 | Clearance.configuration.user_model.should == ::User
12 | end
13 | end
14 |
15 | describe "when a custom user_model_name is specified" do
16 | before do
17 | MyUser = Class.new
18 | Clearance.configure do |config|
19 | config.user_model = MyUser
20 | end
21 | end
22 |
23 | after do
24 | Clearance.configure do |config|
25 | config.user_model = ::User
26 | end
27 | end
28 |
29 | it "is used instead of User" do
30 | Clearance.configuration.user_model.should == ::MyUser
31 | end
32 | end
33 | end
--------------------------------------------------------------------------------
/spec/mailers/clearance_mailer_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ClearanceMailer do
4 | before do
5 | @user = create(:user)
6 | @user.forgot_password!
7 | @email = ClearanceMailer.change_password(@user)
8 | end
9 |
10 | it "should be from DO_NOT_REPLY" do
11 | Clearance.configuration.mailer_sender.should =~ /#{@email.from[0]}/i
12 | end
13 |
14 | it "should be sent to user" do
15 | @email.to.first.should =~ /#{@user.email}/i
16 | end
17 |
18 | it "should contain a link to edit the user's password" do
19 | host = ActionMailer::Base.default_url_options[:host]
20 | regexp = %r{http://#{host}/users/#{@user.id}/password/edit\?token=#{@user.confirmation_token}}
21 | @email.body.to_s.should =~ regexp
22 | end
23 |
24 | it "should set its subject" do
25 | @email.subject.should =~ /Change your password/
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/clearance/rack_session_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::RackSession do
4 | it "injects a clearance session into the environment" do
5 | expected_session = "the session"
6 | expected_session.stubs(:add_cookie_to_headers)
7 | Clearance::Session.stubs(:new => expected_session)
8 | headers = { "X-Roaring-Lobster" => "Red" }
9 |
10 | app = Rack::Builder.new do
11 | use Clearance::RackSession
12 | run lambda { |env| Rack::Response.new(env[:clearance], 200, headers).finish }
13 | end
14 |
15 | env = Rack::MockRequest.env_for("/")
16 |
17 | response = Rack::MockResponse.new(*app.call(env))
18 |
19 | Clearance::Session.should have_received(:new).with(env)
20 | response.body.should == expected_session
21 | expected_session.should have_received(:add_cookie_to_headers).with(has_entries(headers))
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/features/engine/visitor_signs_in.feature:
--------------------------------------------------------------------------------
1 | Feature: Sign in
2 |
3 | In order to get access to protected sections of the site
4 | As a visitor
5 | I want to sign in
6 |
7 | Scenario: Visitor is not signed up
8 | When I sign in as "unknown.email@example.com"
9 | Then I am told email or password is bad
10 | And I should be signed out
11 |
12 | Scenario: Visitor enters wrong password
13 | Given I am signed up as "email@example.com"
14 | When I sign in as "email@example.com" and "badpassword"
15 | Then I am told email or password is bad
16 | And I should be signed out
17 |
18 | Scenario: Visitor signs in successfully
19 | Given I am signed up as "email@example.com"
20 | When I sign in as "email@example.com"
21 | Then I should be signed in
22 |
23 | Scenario: Visitor signs in successfully with uppercase email
24 | Given I am signed up as "email@example.com"
25 | When I sign in as "Email@example.com"
26 | Then I should be signed in
27 |
--------------------------------------------------------------------------------
/spec/support/clearance.rb:
--------------------------------------------------------------------------------
1 | require 'clearance'
2 |
3 | Clearance.configure do |config|
4 | end
5 |
6 | class ApplicationController < ActionController::Base
7 | include Clearance::Authentication
8 | end
9 |
10 | class User < ActiveRecord::Base
11 | include Clearance::User
12 | end
13 |
14 | module Clearance
15 | module Test
16 | module Redirects
17 | def redirect_to_url_after_create
18 | redirect_to(@controller.send(:url_after_create))
19 | end
20 |
21 | def redirect_to_url_after_update
22 | redirect_to(@controller.send(:url_after_update))
23 | end
24 |
25 | def redirect_to_url_after_destroy
26 | redirect_to(@controller.send(:url_after_destroy))
27 | end
28 |
29 | def redirect_to_url_already_confirmed
30 | redirect_to(@controller.send(:url_already_confirmed))
31 | end
32 | end
33 | end
34 | end
35 |
36 | RSpec.configure do |config|
37 | config.include Clearance::Test::Redirects
38 | end
39 |
--------------------------------------------------------------------------------
/app/controllers/clearance/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Clearance::SessionsController < ApplicationController
2 | unloadable
3 |
4 | skip_before_filter :authorize, :only => [:new, :create, :destroy]
5 | protect_from_forgery :except => :create
6 |
7 | def new
8 | render :template => 'sessions/new'
9 | end
10 |
11 | def create
12 | @user = authenticate(params)
13 | if @user.nil?
14 | flash_failure_after_create
15 | render :template => 'sessions/new', :status => :unauthorized
16 | else
17 | sign_in(@user)
18 | redirect_back_or(url_after_create)
19 | end
20 | end
21 |
22 | def destroy
23 | sign_out
24 | redirect_to(url_after_destroy)
25 | end
26 |
27 | private
28 |
29 | def flash_failure_after_create
30 | flash.now[:notice] = translate(:bad_email_or_password,
31 | :scope => [:clearance, :controllers, :sessions],
32 | :default => "Bad email or password.")
33 | end
34 |
35 | def url_after_create
36 | '/'
37 | end
38 |
39 | def url_after_destroy
40 | sign_in_url
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/models/clearance_user_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::User do
4 | subject do
5 | Class.new do
6 | def self.validates_presence_of(*args); end
7 | def self.validates_uniqueness_of(*args); end
8 | def self.validates_format_of(*args); end
9 | def self.before_validation(*args); end
10 | def self.before_create(*args); end
11 |
12 | include Clearance::User
13 | end.new
14 | end
15 |
16 | describe "when Clearance.configuration.password_strategy is set" do
17 | let(:mock_password_strategy) { Module.new }
18 |
19 | before { Clearance.configuration.password_strategy = mock_password_strategy }
20 |
21 | it "includes the value it is set to" do
22 | subject.should be_kind_of(mock_password_strategy)
23 | end
24 | end
25 |
26 | describe "when Clearance.configuration.password_strategy is not set" do
27 | before { Clearance.configuration.password_strategy = nil }
28 |
29 | it "includes Clearance::PasswordStrategies::SHA1" do
30 | subject.should be_kind_of(Clearance::PasswordStrategies::SHA1)
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2008-2011 thoughtbot, inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/clearance/configuration.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | class Configuration
3 | attr_accessor :mailer_sender, :cookie_expiration, :password_strategy, :user_model
4 |
5 | def initialize
6 | @mailer_sender = 'reply@example.com'
7 | @cookie_expiration = lambda { 1.year.from_now.utc }
8 | end
9 |
10 | def user_model
11 | @user_model || ::User
12 | end
13 | end
14 |
15 | class << self
16 | attr_accessor :configuration
17 | end
18 |
19 | # Configure Clearance someplace sensible,
20 | # like config/initializers/clearance.rb
21 | #
22 | # If you want users to only be signed in during the current session
23 | # instead of being remembered, do this:
24 | #
25 | # config.cookie_expiration = lambda { }
26 | #
27 | # @example
28 | # Clearance.configure do |config|
29 | # config.mailer_sender = 'me@example.com'
30 | # config.cookie_expiration = lambda { 2.weeks.from_now.utc }
31 | # config.password_strategy = MyPasswordStrategy
32 | # config.user_model = MyNamespace::MyUser
33 | # end
34 | def self.configure
35 | self.configuration ||= Configuration.new
36 | yield(configuration)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/clearance/password_strategies/blowfish.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 |
3 | module Clearance
4 | module PasswordStrategies
5 | module Blowfish
6 | # Am I authenticated with given password?
7 | #
8 | # @param [String] plain-text password
9 | # @return [true, false]
10 | # @example
11 | # user.authenticated?('password')
12 | def authenticated?(password)
13 | encrypted_password == encrypt(password)
14 | end
15 |
16 | protected
17 |
18 | def encrypt_password
19 | initialize_salt_if_necessary
20 | if password.present?
21 | self.encrypted_password = encrypt(password)
22 | end
23 | end
24 |
25 | def generate_hash(string)
26 | cipher = OpenSSL::Cipher::Cipher.new('bf-cbc').encrypt
27 | cipher.key = Digest::SHA256.digest(salt)
28 | cipher.update(string) << cipher.final
29 | end
30 |
31 | def encrypt(string)
32 | generate_hash("--#{salt}--#{string}--")
33 | end
34 |
35 | def initialize_salt_if_necessary
36 | if salt.blank?
37 | self.salt = generate_random_code
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/clearance/password_strategies/sha1.rb:
--------------------------------------------------------------------------------
1 | require 'digest/sha1'
2 |
3 | module Clearance
4 | module PasswordStrategies
5 | module SHA1
6 | # Am I authenticated with given password?
7 | #
8 | # @param [String] plain-text password
9 | # @return [true, false]
10 | # @example
11 | # user.authenticated?('password')
12 | def authenticated?(password)
13 | encrypted_password == encrypt(password)
14 | end
15 |
16 | protected
17 |
18 | def encrypt_password
19 | initialize_salt_if_necessary
20 | if password.present?
21 | self.encrypted_password = encrypt(password)
22 | end
23 | end
24 |
25 | def generate_hash(string)
26 | if RUBY_VERSION >= '1.9'
27 | Digest::SHA1.hexdigest(string).encode('UTF-8')
28 | else
29 | Digest::SHA1.hexdigest(string)
30 | end
31 | end
32 |
33 | def encrypt(string)
34 | generate_hash("--#{salt}--#{string}--")
35 | end
36 |
37 | def initialize_salt_if_necessary
38 | if salt.blank?
39 | self.salt = generate_random_code
40 | end
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/models/sha1_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::PasswordStrategies::SHA1 do
4 | subject do
5 | Class.new do
6 | attr_accessor :salt, :password, :encrypted_password
7 | include Clearance::PasswordStrategies::SHA1
8 |
9 | def generate_random_code; "code"; end
10 | end.new
11 | end
12 |
13 | describe "#encrypt_password" do
14 | context "when the password is set" do
15 | let(:salt) { "salt" }
16 | let(:password) { "password" }
17 |
18 | before do
19 | subject.salt = salt
20 | subject.password = password
21 | subject.send(:encrypt_password)
22 | end
23 |
24 | it "should encrypt the password using SHA1 into encrypted_password" do
25 | expected = Digest::SHA1.hexdigest("--#{salt}--#{password}--")
26 |
27 | subject.encrypted_password.should == expected
28 | end
29 | end
30 |
31 | context "when the salt is not set" do
32 | before do
33 | subject.salt = nil
34 |
35 | subject.send(:encrypt_password)
36 | end
37 |
38 | it "should initialize the salt" do
39 | subject.salt.should_not be_nil
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/app/controllers/clearance/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Clearance::UsersController < ApplicationController
2 | unloadable
3 |
4 | skip_before_filter :authorize, :only => [:new, :create]
5 | before_filter :redirect_to_root, :only => [:new, :create], :if => :signed_in?
6 |
7 | def new
8 | @user = user_from_params
9 | render :template => 'users/new'
10 | end
11 |
12 | def create
13 | @user = user_from_params
14 | if @user.save
15 | sign_in(@user)
16 | redirect_back_or(url_after_create)
17 | else
18 | flash_failure_after_create
19 | render :template => 'users/new'
20 | end
21 | end
22 |
23 | private
24 |
25 | def flash_failure_after_create
26 | flash.now[:notice] = translate(:bad_email_or_password,
27 | :scope => [:clearance, :controllers, :passwords],
28 | :default => "Must be a valid email address. Password can't be blank.")
29 | end
30 |
31 | def url_after_create
32 | '/'
33 | end
34 |
35 | def user_from_params
36 | user_params = params[:user] || Hash.new
37 | email, password = user_params.delete(:email), user_params.delete(:password)
38 | Clearance.configuration.user_model.new(user_params).tap do |user|
39 | user.email = email
40 | user.password = password
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/models/blowfish_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::PasswordStrategies::Blowfish do
4 | subject do
5 | Class.new do
6 | attr_accessor :salt, :password, :encrypted_password
7 | include Clearance::PasswordStrategies::Blowfish
8 |
9 | def generate_random_code; "code"; end
10 | end.new
11 | end
12 |
13 | describe "#encrypt_password" do
14 | context "when the password is set" do
15 | let(:salt) { "salt" }
16 | let(:password) { "password" }
17 |
18 | before do
19 | subject.salt = salt
20 | subject.password = password
21 | subject.send(:encrypt_password)
22 | end
23 |
24 | it "should encrypt the password using Blowfish into encrypted_password" do
25 | cipher = OpenSSL::Cipher::Cipher.new('bf-cbc').encrypt
26 | cipher.key = Digest::SHA256.digest(salt)
27 | expected = cipher.update("--#{salt}--#{password}--") << cipher.final
28 |
29 | subject.encrypted_password.should == expected
30 | end
31 | end
32 |
33 | context "when the salt is not set" do
34 | before do
35 | subject.salt = nil
36 |
37 | subject.send(:encrypt_password)
38 | end
39 |
40 | it "should initialize the salt" do
41 | subject.salt.should_not be_nil
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/generators/clearance/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | require 'diesel/generators/install_base'
2 |
3 | module Clearance
4 | module Generators
5 | class InstallGenerator < Diesel::Generators::InstallBase
6 | def install
7 | template "clearance.rb", "config/initializers/clearance.rb"
8 |
9 | inject_into_class "app/controllers/application_controller.rb", ApplicationController do
10 | " include Clearance::Authentication\n"
11 | end
12 |
13 | user_model = "app/models/user.rb"
14 | if File.exists?(user_model)
15 | inject_into_class user_model, User do
16 | "include Clearance::User"
17 | end
18 | else
19 | template "user.rb", user_model
20 | end
21 |
22 | if File.exists?("spec")
23 | template "spec/factories.rb", "spec/factories/clearance.rb"
24 | else
25 | template "spec/factories.rb", "test/factories/clearance.rb"
26 | end
27 |
28 | readme "README"
29 | end
30 |
31 | private
32 |
33 | def migrations
34 | if users_table_exists?
35 | super.reject { |name| name.include?("create") } + ["db/migrate/upgrade_clearance_to_diesel.rb"]
36 | else
37 | super
38 | end
39 | end
40 |
41 | def users_table_exists?
42 | ActiveRecord::Base.connection.table_exists?(:users)
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/controllers/denies_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | class DeniesController < ActionController::Base
4 | include Clearance::Authentication
5 | before_filter :authorize, :only => :show
6 |
7 | def new
8 | render :text => "New page"
9 | end
10 |
11 | def show
12 | render :text => "Show page"
13 | end
14 |
15 | protected
16 |
17 | def authorize
18 | deny_access("Access denied.")
19 | end
20 | end
21 |
22 | describe DeniesController do
23 | before do
24 | Rails.application.routes.draw do
25 | resource :deny, :only => [:new, :show]
26 | match 'sign_in' => 'clearance/sessions#new', :as => 'sign_in'
27 | end
28 | end
29 |
30 | after do
31 | Rails.application.reload_routes!
32 | end
33 |
34 | context "signed in user" do
35 | before { sign_in }
36 |
37 | it "allows access to new" do
38 | get :new
39 | subject.should_not deny_access
40 | end
41 |
42 | it "denies access to show" do
43 | get :show
44 | subject.should deny_access(:redirect => '/')
45 | end
46 | end
47 |
48 | context "visitor" do
49 | it "allows access to new" do
50 | get :new
51 | subject.should_not deny_access
52 | end
53 |
54 | it "denies access to show" do
55 | get :show
56 | subject.should deny_access
57 | subject.should deny_access(:redirect => sign_in_url, :flash => "Access denied.")
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/controllers/forgeries_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | class ForgeriesController < ActionController::Base
4 | include Clearance::Authentication
5 | protect_from_forgery
6 | before_filter :authorize
7 |
8 | # This is off in test by default, but we need it for this test
9 | self.allow_forgery_protection = true
10 |
11 | def create
12 | redirect_to :action => 'index'
13 | end
14 | end
15 |
16 | describe ForgeriesController do
17 | context "signed in user" do
18 | before do
19 | Rails.application.routes.draw do
20 | resources :forgeries
21 | match 'sign_in' => 'clearance/sessions#new', :as => 'sign_in'
22 | end
23 |
24 | @user = create(:user)
25 | @user.update_attribute(:remember_token, "old-token")
26 | @request.cookies["remember_token"] = "old-token"
27 | @request.session[:_csrf_token] = "golden-ticket"
28 | end
29 |
30 | after do
31 | Rails.application.reload_routes!
32 | end
33 |
34 | it "succeeds with authentic token" do
35 | post :create, :authenticity_token => "golden-ticket"
36 | subject.should redirect_to(:action => 'index')
37 | end
38 |
39 | it "fails with invalid token" do
40 | post :create, :authenticity_token => "hax0r"
41 | subject.should deny_access
42 | end
43 |
44 | it "fails with no token" do
45 | post :create
46 | subject.should deny_access
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended to check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(:version => 20110111224543) do
15 |
16 | create_table "users", :force => true do |t|
17 | t.string "email"
18 | t.string "encrypted_password", :limit => 128
19 | t.string "salt", :limit => 128
20 | t.string "confirmation_token", :limit => 128
21 | t.string "remember_token", :limit => 128
22 | t.datetime "created_at", :null => false
23 | t.datetime "updated_at", :null => false
24 | end
25 |
26 | add_index "users", ["email"], :name => "index_users_on_email"
27 | add_index "users", ["remember_token"], :name => "index_users_on_remember_token"
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | We love pull requests. Here's a quick guide:
2 |
3 | 1. Fork the repo.
4 |
5 | 2. Run the tests. We only take pull requests with passing tests, and it's great
6 | to know that you have a clean slate: `bundle && rake db:migrate && rake`
7 |
8 | 3. Add a test for your change. Only refactoring and documentation changes
9 | require no new tests. If you are adding functionality or fixing a bug, we need
10 | a test!
11 |
12 | 4. Make the test pass.
13 |
14 | 5. Push to your fork and submit a pull request.
15 |
16 |
17 | At this point you're waiting on us. We like to at least comment on, if not
18 | accept, pull requests within three business days (and, typically, one business
19 | day). We may suggest some changes or improvements or alternatives.
20 |
21 | Some things that will increase the chance that your pull request is accepted,
22 | taken straight from the Ruby on Rails guide:
23 |
24 | * Use Rails idioms and helpers
25 | * Include tests that fail without your code, and pass with it
26 | * Update the documentation, the surrounding one, examples elsewhere, guides,
27 | whatever is affected by your contribution
28 |
29 | Syntax:
30 |
31 | * Two spaces, no tabs.
32 | * No trailing whitespace. Blank lines should not have any space.
33 | * Prefer &&/|| over and/or.
34 | * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
35 | * a = b and not a=b.
36 | * Follow the conventions you see used in the source already.
37 |
38 | And in case we didn't emphasize it enough: we love tests!
39 |
--------------------------------------------------------------------------------
/lib/clearance/session.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | class Session
3 | REMEMBER_TOKEN_COOKIE = "remember_token".freeze
4 |
5 | def initialize(env)
6 | @env = env
7 | end
8 |
9 | def signed_in?
10 | current_user.present?
11 | end
12 |
13 | def current_user
14 | @current_user ||= with_remember_token do |token|
15 | Clearance.configuration.user_model.find_by_remember_token(token)
16 | end
17 | end
18 |
19 | def sign_in(user)
20 | @current_user = user
21 | end
22 |
23 | def sign_out
24 | current_user.reset_remember_token! if signed_in?
25 | @current_user = nil
26 | cookies.delete(REMEMBER_TOKEN_COOKIE)
27 | end
28 |
29 | def add_cookie_to_headers(headers)
30 | if signed_in?
31 | Rack::Utils.set_cookie_header!(headers,
32 | REMEMBER_TOKEN_COOKIE,
33 | :value => current_user.remember_token,
34 | :expires => Clearance.configuration.cookie_expiration.call,
35 | :path => "/")
36 | end
37 | end
38 |
39 | private
40 |
41 | def with_remember_token
42 | if token = remember_token
43 | yield token
44 | end
45 | end
46 |
47 | def remember_token
48 | cookies[REMEMBER_TOKEN_COOKIE]
49 | end
50 |
51 | def cookies
52 | @cookies ||= @env['action_dispatch.cookies'] || Rack::Request.new(@env).cookies
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/features/step_definitions/configuration_steps.rb:
--------------------------------------------------------------------------------
1 | When /^I configure ActionMailer to use "([^"]+)" as a host$/ do |host|
2 | mailer_config = "config.action_mailer.default_url_options = { :host => '#{host}' }"
3 | path = "config/application.rb"
4 | in_current_dir do
5 | contents = IO.read(path)
6 | contents.sub!(/(class .* < Rails::Application)/, "\\1\n#{mailer_config}")
7 | File.open(path, "w") { |file| file.write(contents) }
8 | end
9 | end
10 |
11 | When /^I configure a root route$/ do
12 | route = "root :to => 'home#show'"
13 | path = "config/routes.rb"
14 | in_current_dir do
15 | contents = IO.read(path)
16 | contents.sub!(/(\.routes\.draw do)/, "\\1\n#{route}\n")
17 | File.open(path, "w") { |file| file.write(contents) }
18 | end
19 | write_file("app/controllers/home_controller.rb", <<-CONTROLLER)
20 | class HomeController < ApplicationController
21 | def show
22 | render :text => "", :layout => "application"
23 | end
24 | end
25 | CONTROLLER
26 | end
27 |
28 | When /^I disable Capybara Javascript emulation$/ do
29 | in_current_dir do
30 | path = "features/support/env.rb"
31 | contents = IO.read(path)
32 | contents.sub!(%{require 'cucumber/rails/capybara_javascript_emulation'},
33 | "# Disabled")
34 | File.open(path, "w") { |file| file.write(contents) }
35 | end
36 | end
37 |
38 | When /^I copy the locked Gemfile from this project$/ do
39 | in_current_dir do
40 | FileUtils.cp(File.join(PROJECT_ROOT, 'Gemfile.lock'), 'Gemfile.lock')
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/generators/clearance/install/templates/db/migrate/upgrade_clearance_to_diesel.rb:
--------------------------------------------------------------------------------
1 | class UpgradeClearanceToDiesel < ActiveRecord::Migration
2 | def self.up
3 | <%
4 | existing_columns = ActiveRecord::Base.connection.columns(:users).collect { |each| each.name }
5 | columns = [
6 | [:email, 't.string :email'],
7 | [:encrypted_password, 't.string :encrypted_password, :limit => 128'],
8 | [:salt, 't.string :salt, :limit => 128'],
9 | [:confirmation_token, 't.string :confirmation_token, :limit => 128'],
10 | [:remember_token, 't.string :remember_token, :limit => 128']
11 | ].delete_if {|c| existing_columns.include?(c.first.to_s)}
12 | -%>
13 | change_table(:users) do |t|
14 | <% columns.each do |c| -%>
15 | <%= c.last %>
16 | <% end -%>
17 | end
18 |
19 | <%
20 | existing_indexes = ActiveRecord::Base.connection.indexes(:users)
21 | index_names = existing_indexes.collect { |each| each.name }
22 | new_indexes = [
23 | [:index_users_on_email, 'add_index :users, :email'],
24 | [:index_users_on_remember_token, 'add_index :users, :remember_token']
25 | ].delete_if { |each| index_names.include?(each.first.to_s) }
26 | -%>
27 | <% new_indexes.each do |each| -%>
28 | <%= each.last %>
29 | <% end -%>
30 | end
31 |
32 | def self.down
33 | change_table(:users) do |t|
34 | <% unless columns.empty? -%>
35 | t.remove <%= columns.collect { |each| ":#{each.first}" }.join(',') %>
36 | <% end -%>
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/features/integration_with_test_unit.feature:
--------------------------------------------------------------------------------
1 | Feature: integrate with test unit
2 |
3 | Background:
4 | When I successfully run `bundle exec rails new testapp`
5 | And I cd to "testapp"
6 | And I remove the file "public/index.html"
7 | And I remove the file "app/views/layouts/application.html.erb"
8 | And I configure ActionMailer to use "localhost" as a host
9 | And I configure a root route
10 | And I add the "factory_girl_rails" gem
11 | And I run `bundle install --local`
12 |
13 | Scenario: generate a Rails app, run the generators, and run the tests
14 | When I successfully run `bundle exec rails generate clearance:install`
15 | And I successfully run `bundle exec rake db:migrate --trace`
16 | And I successfully run `bundle exec rails generate controller posts index`
17 | And I add the "cucumber-rails" gem
18 | And I write to "test/test_helper.rb" with:
19 | """
20 | ENV["RAILS_ENV"] = "test"
21 | require File.expand_path('../../config/environment', __FILE__)
22 | require 'rails/test_help'
23 |
24 | class ActiveSupport::TestCase
25 | fixtures :all
26 | end
27 |
28 | require 'clearance/testing'
29 | """
30 | And I write to "test/functionals/posts_controller_test.rb" with:
31 | """
32 | require 'test_helper'
33 |
34 | class PostsControllerTest < ActionController::TestCase
35 | test "should get index" do
36 | sign_in
37 | get :index
38 | assert_response :success
39 | end
40 | end
41 | """
42 | And I successfully run `bundle exec rake --trace`
43 | Then the output should contain "1 tests, 1 assertions, 0 failures"
44 |
--------------------------------------------------------------------------------
/clearance.gemspec:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2 | require 'clearance/version'
3 | require 'date'
4 |
5 | Gem::Specification.new do |s|
6 | s.name = %q{clearance}
7 | s.version = Clearance::VERSION
8 | s.email = %q{support@thoughtbot.com}
9 | s.homepage = %q{http://github.com/thoughtbot/clearance}
10 |
11 | s.authors = ["Dan Croak", "Mike Burns", "Jason Morrison", "Joe Ferris", "Eugene Bolshakov", "Nick Quaranto", "Josh Nichols", "Mike Breen", "Jon Yurek", "Chad Pytel"]
12 | s.date = Date.today.to_s
13 | s.summary = %q{Rails authentication & authorization with email & password.}
14 | s.description = %q{Rails authentication & authorization with email & password.}
15 | s.extra_rdoc_files = %w(LICENSE README.md)
16 | s.files = `git ls-files`.split("\n")
17 | s.test_files = `git ls-files -- {test,features}/*`.split("\n")
18 |
19 | s.rdoc_options = ["--charset=UTF-8"]
20 | s.require_paths = ["lib"]
21 |
22 | s.add_dependency('rails', '>= 3.0')
23 | s.add_dependency('diesel', '~> 0.1.5')
24 |
25 | s.add_development_dependency('bundler', '~> 1.1.0')
26 | s.add_development_dependency('appraisal', '~> 0.4.1')
27 | s.add_development_dependency('cucumber-rails', '~> 1.1.1')
28 | s.add_development_dependency('rspec-rails', '~> 2.9.0')
29 | s.add_development_dependency('sqlite3')
30 | s.add_development_dependency('bourne', '~> 1.1.2')
31 | s.add_development_dependency('timecop')
32 | s.add_development_dependency('capybara', '~> 1.1.2')
33 | s.add_development_dependency('factory_girl_rails', '~> 3.1.0')
34 | s.add_development_dependency('shoulda-matchers', '~> 1.1.0')
35 | s.add_development_dependency('database_cleaner')
36 | s.add_development_dependency('launchy')
37 | s.add_development_dependency('aruba', '~> 0.4.11')
38 | end
39 |
--------------------------------------------------------------------------------
/features/engine/visitor_resets_password.feature:
--------------------------------------------------------------------------------
1 | Feature: Password reset
2 |
3 | In order to sign in even if I forgot my password
4 | As a user
5 | I want to reset my password
6 |
7 | Scenario: User is not signed up
8 | When I reset the password for "unknown.email@example.com"
9 | Then I am told email is unknown
10 |
11 | Scenario: User is signed up and requests password reset
12 | Given I signed up with "email@example.com"
13 | When I reset the password for "email@example.com"
14 | Then instructions for changing my password are emailed to "email@example.com"
15 |
16 | Scenario: User tries to reset his password with a blank password
17 | Given I signed up with "email@example.com"
18 | When I reset the password for "email@example.com"
19 | And I follow the password reset link sent to "email@example.com"
20 | And I update my password with ""
21 | Then I am told to enter a password
22 | And I should be signed out
23 |
24 | Scenario: User is signed up and updates his password
25 | Given I signed up with "email@example.com"
26 | When I reset the password for "email@example.com"
27 | And I follow the password reset link sent to "email@example.com"
28 | And I update my password with "newpassword"
29 | Then I should be signed in
30 | When I sign out
31 | Then I should be signed out
32 | When I sign in with "email@example.com" and "newpassword"
33 | Then I should be signed in
34 |
35 | Scenario: User who was created before Clearance was installed creates password for first time
36 | Given a user "email@example.com" exists without a salt, remember token, or password
37 | When I reset the password for "email@example.com"
38 | When I follow the password reset link sent to "email@example.com"
39 | And I update my password with "newpassword"
40 | Then I should be signed in
41 |
42 |
--------------------------------------------------------------------------------
/spec/support/cookies.rb:
--------------------------------------------------------------------------------
1 | RSpec::Matchers.define :set_cookie do |name, expected_value, expected_expires_at|
2 | match do |subject|
3 | @headers = subject
4 | @expected_name = name
5 | @expected_value = expected_value
6 | @expected_expires_at = expected_expires_at
7 |
8 | extract_cookies
9 | find_expected_cookie
10 | parse_expiration
11 | parse_value
12 | parse_path
13 |
14 | ensure_cookie_set
15 | ensure_expiration_correct
16 | ensure_path_is_correct
17 | end
18 |
19 | def extract_cookies
20 | @cookie_headers = @headers['Set-Cookie'] || []
21 | @cookie_headers = [@cookie_headers] if @cookie_headers.respond_to?(:to_str)
22 | end
23 |
24 | def find_expected_cookie
25 | @cookie = @cookie_headers.detect do |header|
26 | header =~ /^#{@expected_name}=[^;]*(;|$)/
27 | end
28 | end
29 |
30 | def parse_expiration
31 | if @cookie && result = @cookie.match(/; expires=(.*?)(;|$)/)
32 | @expires_at = Time.parse(result[1])
33 | end
34 | end
35 |
36 | def parse_value
37 | if @cookie && result = @cookie.match(/=(.*?)(?:;|$)/)
38 | @value = result[1]
39 | end
40 | end
41 |
42 | def parse_path
43 | if @cookie && result = @cookie.match(/; path=(.*?)(;|$)/)
44 | @path = result[1]
45 | end
46 | end
47 |
48 | def ensure_cookie_set
49 | @value.should == @expected_value
50 | end
51 |
52 | def ensure_expiration_correct
53 | @expires_at.should_not be_nil
54 | @expires_at.should be_within(100).of(@expected_expires_at)
55 | end
56 |
57 | def ensure_path_is_correct
58 | @path.should == "/"
59 | end
60 |
61 | failure_message_for_should do
62 | "Expected #{expectation} got #{result}"
63 | end
64 |
65 | def expectation
66 | "a cookie named #{@expected_name} with value #{@expected_value.inspect} expiring at #{@expected_expires_at.inspect}"
67 | end
68 |
69 | def result
70 | if @cookie
71 | @cookie
72 | else
73 | @cookie_headers.join("; ")
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/features/integration.feature:
--------------------------------------------------------------------------------
1 | Feature: integrate with application
2 |
3 | Background:
4 | When I successfully run `bundle exec rails new testapp`
5 | And I cd to "testapp"
6 | And I remove the file "public/index.html"
7 | And I remove the file "app/views/layouts/application.html.erb"
8 | And I configure ActionMailer to use "localhost" as a host
9 | And I configure a root route
10 | And I add the "cucumber-rails" gem
11 | And I add the "capybara" gem
12 | And I add the "rspec-rails" gem
13 | And I add the "factory_girl_rails" gem
14 | And I add the "database_cleaner" gem
15 | And I add the "clearance" gem from this project
16 | And I run `bundle install --local`
17 | And I successfully run `bundle exec rails generate cucumber:install`
18 | And I disable Capybara Javascript emulation
19 | And I successfully run `bundle exec rails generate clearance:features`
20 |
21 | Scenario: generate a Rails app, run the generators, and run the tests
22 | When I successfully run `bundle exec rails generate clearance:install`
23 | Then the output should contain "Next steps"
24 | When I successfully run `bundle exec rake db:migrate --trace`
25 | And I successfully run `bundle exec rake --trace`
26 | Then the output should contain "passed"
27 | And the output should not contain "failed"
28 | And the output should not contain "Could not find generator"
29 |
30 | Scenario: Developer already has a users table in their database
31 | When I write to "db/migrate/001_create_users.rb" with:
32 | """
33 | class CreateUsers < ActiveRecord::Migration
34 | def self.up
35 | create_table(:users) do |t|
36 | t.string :email
37 | t.string :name
38 | end
39 | end
40 | def self.down
41 | end
42 | end
43 | """
44 | And I successfully run `bundle exec rake db:migrate --trace`
45 | And I successfully run `bundle exec rails generate clearance:install`
46 | And I successfully run `bundle exec rake db:migrate --trace`
47 | And I successfully run `bundle exec rake --trace`
48 | Then the output should contain "passed"
49 | And the output should not contain "failed"
50 | And the output should not contain "Could not find generator"
51 |
52 |
--------------------------------------------------------------------------------
/spec/controllers/users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::UsersController do
4 | describe "when signed out" do
5 | before { sign_out }
6 |
7 | describe "on GET to #new" do
8 | before { get :new }
9 |
10 | it { should respond_with(:success) }
11 | it { should render_template(:new) }
12 | it { should_not set_the_flash }
13 | end
14 |
15 | describe "on GET to #new with email" do
16 | before do
17 | @email = "a@example.com"
18 | get :new, :user => { :email => @email }
19 | end
20 |
21 | it "should set assigned user's email" do
22 | assigns(:user).email.should == @email
23 | end
24 | end
25 |
26 | describe "on POST to #create with valid attributes" do
27 | before do
28 | user_attributes = FactoryGirl.attributes_for(:user)
29 | @old_user_count = User.count
30 | post :create, :user => user_attributes
31 | end
32 |
33 | it { should assign_to(:user) }
34 |
35 | it "should create a new user" do
36 | User.count.should == @old_user_count + 1
37 | end
38 |
39 | it { should redirect_to_url_after_create }
40 | end
41 |
42 | describe "on POST to #create with valid attributes and a session return url" do
43 | before do
44 | user_attributes = FactoryGirl.attributes_for(:user)
45 | @old_user_count = User.count
46 | @return_url = '/url_in_the_session'
47 | @request.session[:return_to] = @return_url
48 | post :create, :user => user_attributes
49 | end
50 |
51 | it { should assign_to(:user) }
52 |
53 | it "should create a new user" do
54 | User.count.should == @old_user_count + 1
55 | end
56 |
57 | it { should redirect_to(@return_url) }
58 | end
59 | end
60 |
61 | describe "A signed-in user" do
62 | before do
63 | @user = create(:user)
64 | sign_in_as @user
65 | end
66 |
67 | describe "GET to new" do
68 | before { get :new }
69 | it "redirects to the home page" do
70 | should redirect_to(root_url)
71 | end
72 | end
73 |
74 | describe "POST to create" do
75 | before { post :create, :user => {} }
76 | it "redirects to the home page" do
77 | should redirect_to(root_url)
78 | end
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/features/support/env.rb:
--------------------------------------------------------------------------------
1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
2 | # It is recommended to regenerate this file in the future when you upgrade to a
3 | # newer version of cucumber-rails. Consider adding your own code to a new file
4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb
5 | # files.
6 |
7 | ENV["RAILS_ENV"] ||= "test"
8 |
9 | PROJECT_ROOT = File.expand_path("../../..", __FILE__)
10 | $LOAD_PATH << File.join(PROJECT_ROOT, "lib")
11 |
12 | require 'rails/all'
13 |
14 | Bundler.require
15 |
16 | require 'diesel/testing'
17 | require 'diesel/testing/integration'
18 | require 'cucumber/rails/application'
19 | require 'cucumber/rails/action_controller'
20 | require 'rails/test_help'
21 | require 'cucumber/rails/world'
22 | require 'cucumber/rails/hooks'
23 | require 'cucumber/rails/capybara'
24 |
25 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
26 | # order to ease the transition to Capybara we set the default here. If you'd
27 | # prefer to use XPath just remove this line and adjust any selectors in your
28 | # steps to use the XPath syntax.
29 | Capybara.default_selector = :css
30 | Capybara.save_and_open_page_path = 'tmp'
31 |
32 | # By default, any exception happening in your Rails application will bubble up
33 | # to Cucumber so that your scenario will fail. This is a different from how
34 | # your application behaves in the production environment, where an error page will
35 | # be rendered instead.
36 | #
37 | # Sometimes we want to override this default behaviour and allow Rails to rescue
38 | # exceptions and display an error page (just like when the app is running in production).
39 | # Typical scenarios where you want to do this is when you test your error pages.
40 | # There are two ways to allow Rails to rescue exceptions:
41 | #
42 | # 1) Tag your scenario (or feature) with @allow-rescue
43 | #
44 | # 2) Set the value below to true. Beware that doing this globally is not
45 | # recommended as it will mask a lot of errors for you!
46 | #
47 | ActionController::Base.allow_rescue = false
48 |
49 | # Remove/comment out the lines below if your app doesn't have a database.
50 | # For some databases (like MongoDB and CouchDB) you may need to use :truncation instead.
51 | begin
52 | DatabaseCleaner.strategy = :transaction
53 | rescue NameError
54 | raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
55 | end
56 |
--------------------------------------------------------------------------------
/lib/clearance/testing/deny_access_matcher.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | module Testing
3 | module Matchers
4 | # Ensures a controller denied access.
5 | #
6 | # @example
7 | # it { should deny_access }
8 | # it { should deny_access(:flash => "Denied access.") }
9 | # it { should deny_access(:redirect => sign_in_url) }
10 | def deny_access(opts = {})
11 | DenyAccessMatcher.new(self, opts)
12 | end
13 |
14 | class DenyAccessMatcher
15 | attr_reader :failure_message, :negative_failure_message
16 |
17 | def initialize(context, opts)
18 | @context = context
19 | @flash = opts[:flash]
20 | @url = opts[:redirect]
21 |
22 | @failure_message = ""
23 | @negative_failure_message = ""
24 | end
25 |
26 | def matches?(controller)
27 | @controller = controller
28 | sets_the_flash? && redirects_to_url?
29 | end
30 |
31 | def description
32 | "deny access"
33 | end
34 |
35 | private
36 |
37 | def sets_the_flash?
38 | if @flash.blank?
39 | true
40 | else
41 | if flash_notice_value == @flash
42 | @negative_failure_message << "Didn't expect to set the flash to #{@flash}"
43 | true
44 | else
45 | @failure_message << "Expected the flash to be set to #{@flash} but was #{flash_notice_value}"
46 | false
47 | end
48 | end
49 | end
50 |
51 | def flash_notice_value
52 | if flash_notice.respond_to?(:values)
53 | flash_notice.values.first
54 | else
55 | flash_notice
56 | end
57 | end
58 |
59 | def flash_notice
60 | @controller.flash[:notice]
61 | end
62 |
63 | def redirects_to_url?
64 | @url ||= denied_access_url
65 | begin
66 | @context.send(:assert_redirected_to, @url)
67 | @negative_failure_message << "Didn't expect to redirect to #{@url}."
68 | true
69 | rescue Clearance::Testing::AssertionError
70 | @failure_message << "Expected to redirect to #{@url} but did not."
71 | false
72 | end
73 | end
74 |
75 | def denied_access_url
76 | if @controller.signed_in?
77 | '/'
78 | else
79 | @controller.sign_in_url
80 | end
81 | end
82 | end
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/app/controllers/clearance/passwords_controller.rb:
--------------------------------------------------------------------------------
1 | class Clearance::PasswordsController < ApplicationController
2 | unloadable
3 |
4 | skip_before_filter :authorize, :only => [:new, :create, :edit, :update]
5 | before_filter :forbid_missing_token, :only => [:edit, :update]
6 | before_filter :forbid_non_existent_user, :only => [:edit, :update]
7 |
8 | def new
9 | render :template => 'passwords/new'
10 | end
11 |
12 | def create
13 | if user = Clearance.configuration.user_model.find_by_email(
14 | params[:password][:email])
15 | user.forgot_password!
16 | ::ClearanceMailer.change_password(user).deliver
17 | render :template => 'passwords/create'
18 | else
19 | flash_failure_after_create
20 | render :template => 'passwords/new'
21 | end
22 | end
23 |
24 | def edit
25 | @user = Clearance.configuration.user_model.find_by_id_and_confirmation_token(
26 | params[:user_id], params[:token].to_s)
27 | render :template => 'passwords/edit'
28 | end
29 |
30 | def update
31 | @user = Clearance.configuration.user_model.find_by_id_and_confirmation_token(
32 | params[:user_id], params[:token].to_s)
33 |
34 | if @user.update_password(params[:user][:password])
35 | sign_in(@user)
36 | redirect_to(url_after_update)
37 | else
38 | flash_failure_after_update
39 | render :template => 'passwords/edit'
40 | end
41 | end
42 |
43 | private
44 |
45 | def forbid_missing_token
46 | if params[:token].to_s.blank?
47 | flash_failure_when_forbidden
48 | render :template => 'passwords/new'
49 | end
50 | end
51 |
52 | def forbid_non_existent_user
53 | unless Clearance.configuration.user_model.find_by_id_and_confirmation_token(
54 | params[:user_id], params[:token].to_s)
55 | flash_failure_when_forbidden
56 | render :template => 'passwords/new'
57 | end
58 | end
59 |
60 | def flash_failure_when_forbidden
61 | flash.now[:notice] = translate(:forbidden,
62 | :scope => [:clearance, :controllers, :passwords],
63 | :default => "Please double check the URL or try submitting the form again.")
64 | end
65 |
66 | def flash_failure_after_create
67 | flash.now[:notice] = translate(:unknown_email,
68 | :scope => [:clearance, :controllers, :passwords],
69 | :default => "Unknown email.")
70 | end
71 |
72 | def flash_failure_after_update
73 | flash.now[:notice] = translate(:blank_password,
74 | :scope => [:clearance, :controllers, :passwords],
75 | :default => "Password can't be blank.")
76 | end
77 |
78 | def url_after_create
79 | sign_in_url
80 | end
81 |
82 | def url_after_update
83 | '/'
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/controllers/sessions_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::SessionsController do
4 | describe "on GET to /sessions/new" do
5 | before { get :new }
6 |
7 | it { should respond_with(:success) }
8 | it { should render_template(:new) }
9 | it { should_not set_the_flash }
10 | end
11 |
12 | describe "on POST to #create with good credentials" do
13 | before do
14 | @user = create(:user)
15 | @user.update_attribute(:remember_token, "old-token")
16 | post :create, :session => {
17 | :email => @user.email,
18 | :password => @user.password }
19 | end
20 |
21 | it { should redirect_to_url_after_create }
22 |
23 | it "sets the user in the clearance session" do
24 | controller.current_user.should == @user
25 | end
26 |
27 | it "should not change the remember token" do
28 | @user.reload.remember_token.should == "old-token"
29 | end
30 | end
31 |
32 | describe "on POST to #create with good credentials and a session return url" do
33 | before do
34 | @user = create(:user)
35 | @return_url = '/url_in_the_session'
36 | @request.session[:return_to] = @return_url
37 | post :create, :session => {
38 | :email => @user.email,
39 | :password => @user.password }
40 | end
41 |
42 | it "redirects to the return URL" do
43 | should redirect_to(@return_url)
44 | end
45 | end
46 |
47 | describe "on POST to #create with good credentials and a request return url" do
48 | before do
49 | @user = create(:user)
50 | @return_url = '/url_in_the_request'
51 | post :create, :session => {
52 | :email => @user.email,
53 | :password => @user.password },
54 | :return_to => @return_url
55 | end
56 |
57 | it "redirects to the return URL" do
58 | should redirect_to(@return_url)
59 | end
60 | end
61 |
62 | describe "on POST to #create with good credentials and a session return url and request return url" do
63 | before do
64 | @user = create(:user)
65 | @return_url = '/url_in_the_session'
66 | @request.session[:return_to] = @return_url
67 | post :create, :session => {
68 | :email => @user.email,
69 | :password => @user.password },
70 | :return_to => '/url_in_the_request'
71 | end
72 |
73 | it "redirects to the return url" do
74 | should redirect_to(@return_url)
75 | end
76 | end
77 |
78 | describe "on DELETE to #destroy given a signed out user" do
79 | before do
80 | sign_out
81 | delete :destroy
82 | end
83 | it { should redirect_to_url_after_destroy }
84 | end
85 |
86 | describe "on DELETE to #destroy with a cookie" do
87 | before do
88 | @user = create(:user)
89 | @user.update_attribute(:remember_token, "old-token")
90 | @request.cookies["remember_token"] = "old-token"
91 | delete :destroy
92 | end
93 |
94 | it { should redirect_to_url_after_destroy }
95 |
96 | it "should reset the remember token" do
97 | @user.reload.remember_token.should_not == "old-token"
98 | end
99 |
100 | it "should unset the current user" do
101 | @controller.current_user.should be_nil
102 | end
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/spec/clearance/session_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::Session do
4 | before { Timecop.freeze }
5 | after { Timecop.return }
6 |
7 | it "finds a user from a cookie" do
8 | user = create(:user)
9 | env = env_with_remember_token(user.remember_token)
10 |
11 | session = Clearance::Session.new(env)
12 | session.should be_signed_in
13 | session.current_user.should == user
14 | end
15 |
16 | it "returns nil for an unknown user" do
17 | user = create(:user)
18 | env = env_with_remember_token("bogus")
19 |
20 | session = Clearance::Session.new(env)
21 | session.should_not be_signed_in
22 | session.current_user.should be_nil
23 | end
24 |
25 | it "returns nil without a remember token" do
26 | env = env_without_remember_token
27 | session = Clearance::Session.new(env)
28 | session.should_not be_signed_in
29 | session.current_user.should be_nil
30 | end
31 |
32 | it "signs in a given user" do
33 | user = create(:user)
34 | session = Clearance::Session.new(env_without_remember_token)
35 | session.sign_in user
36 | session.current_user.should == user
37 | end
38 |
39 | it "sets a remember token cookie with a default expiration of 1 year from now" do
40 | user = create(:user)
41 | headers = {}
42 | session = Clearance::Session.new(env_without_remember_token)
43 | session.sign_in user
44 | session.add_cookie_to_headers headers
45 | headers.should set_cookie("remember_token", user.remember_token, 1.year.from_now)
46 | end
47 |
48 | it "sets a remember token cookie with a custom expiration" do
49 | custom_expiration = 1.day.from_now
50 | with_custom_expiration 1.day.from_now do
51 | user = create(:user)
52 | headers = {}
53 | session = Clearance::Session.new(env_without_remember_token)
54 | session.sign_in user
55 | session.add_cookie_to_headers headers
56 | headers.should set_cookie("remember_token", user.remember_token, 1.day.from_now)
57 | Clearance.configuration.cookie_expiration.call.should be_within(100).of(1.year.from_now)
58 | end
59 | end
60 |
61 | it "doesn't set a remember token when signed out" do
62 | headers = {}
63 | session = Clearance::Session.new(env_without_remember_token)
64 | session.add_cookie_to_headers headers
65 | headers.should_not set_cookie("remember_token")
66 | end
67 |
68 | it "signs out a user" do
69 | user = create(:user)
70 | old_remember_token = user.remember_token
71 | env = env_with_remember_token(old_remember_token)
72 |
73 | session = Clearance::Session.new(env)
74 | session.sign_out
75 | session.current_user.should be_nil
76 | user.reload.remember_token.should_not == old_remember_token
77 | end
78 |
79 | def env_with_remember_token(token)
80 | env_with_cookies("remember_token" => token)
81 | end
82 |
83 | def env_without_remember_token
84 | env_with_cookies({})
85 | end
86 |
87 | def env_with_cookies(cookies)
88 | Rack::MockRequest.env_for("/", "HTTP_COOKIE" => serialize_cookies(cookies))
89 | end
90 |
91 | def serialize_cookies(hash)
92 | header = {}
93 | hash.each do |key, value|
94 | Rack::Utils.set_cookie_header!(header, key, value)
95 | end
96 | header['Set-Cookie']
97 | end
98 |
99 | def with_custom_expiration(custom_duration)
100 | Clearance.configuration.cookie_expiration = lambda { custom_duration }
101 | ensure
102 | restore_default_config
103 | end
104 |
105 | def restore_default_config
106 | Clearance.configuration = nil
107 | Clearance.configure {}
108 | end
109 | end
110 |
--------------------------------------------------------------------------------
/features/step_definitions/engine/clearance_steps.rb:
--------------------------------------------------------------------------------
1 | # Existing users
2 |
3 | require 'factory_girl_rails'
4 |
5 | Given /^(?:I am|I have|I) signed up (?:as|with) "(.*)"$/ do |email|
6 | FactoryGirl.create(:user, :email => email)
7 | end
8 |
9 | Given /^a user "([^"]*)" exists without a salt, remember token, or password$/ do |email|
10 | user = FactoryGirl.create(:user, :email => email)
11 | sql = "update users set salt = NULL, encrypted_password = NULL, remember_token = NULL where id = #{user.id}"
12 | ActiveRecord::Base.connection.update(sql)
13 | end
14 |
15 | # Sign up
16 |
17 | When /^I sign up (?:with|as) "(.*)" and "(.*)"$/ do |email, password|
18 | visit sign_up_path
19 | page.should have_css("input[type='email']")
20 |
21 | fill_in "Email", :with => email
22 | fill_in "Password", :with => password
23 | click_button "Sign up"
24 | end
25 |
26 | # Sign in
27 |
28 | Given /^I sign in$/ do
29 | email = FactoryGirl.generate(:email)
30 | steps %{
31 | I have signed up with "#{email}"
32 | I sign in with "#{email}"
33 | }
34 | end
35 |
36 | When /^I sign in (?:with|as) "([^"]*)"$/ do |email|
37 | step %{I sign in with "#{email}" and "password"}
38 | end
39 |
40 | When /^I sign in (?:with|as) "([^"]*)" and "([^"]*)"$/ do |email, password|
41 | visit sign_in_path
42 | page.should have_css("input[type='email']")
43 |
44 | fill_in "Email", :with => email
45 | fill_in "Password", :with => password
46 | click_button "Sign in"
47 | end
48 |
49 | # Sign out
50 |
51 | When "I sign out" do
52 | visit "/"
53 | click_link "Sign out"
54 | end
55 |
56 | # Reset password
57 |
58 | When /^I reset the password for "(.*)"$/ do |email|
59 | visit new_password_path
60 | page.should have_css("input[type='email']")
61 |
62 | fill_in "Email address", :with => email
63 | click_button "Reset password"
64 | end
65 |
66 | Then /^instructions for changing my password are emailed to "(.*)"$/ do |email|
67 | page.should have_content("instructions for changing your password")
68 |
69 | user = User.find_by_email!(email)
70 | assert !user.confirmation_token.blank?
71 | assert !ActionMailer::Base.deliveries.empty?
72 | result = ActionMailer::Base.deliveries.any? do |email|
73 | email.to == [user.email] &&
74 | email.subject =~ /password/i &&
75 | email.body =~ /#{user.confirmation_token}/
76 | end
77 | assert result
78 | end
79 |
80 | When /^I follow the password reset link sent to "(.*)"$/ do |email|
81 | user = User.find_by_email!(email)
82 | visit edit_user_password_path(:user_id => user,
83 | :token => user.confirmation_token)
84 | end
85 |
86 | When /^I change the password of "(.*)" without token$/ do |email|
87 | user = User.find_by_email!(email)
88 | visit edit_user_password_path(:user_id => user)
89 | end
90 |
91 | When /^I update my password with "(.*)"$/ do |password|
92 | fill_in "Choose password", :with => password
93 | click_button "Save this password"
94 | end
95 |
96 | # Flashes
97 |
98 | Then /^I am told email or password is bad$/ do
99 | page.should have_content("Bad email or password")
100 | end
101 |
102 | Then /^I am told email is unknown$/ do
103 | page.should have_content("Unknown email")
104 | end
105 |
106 | Then /^I am told to enter a valid email address$/ do
107 | page.should have_content("Must be a valid email address")
108 | end
109 |
110 | Then /^I am told to enter a password$/ do
111 | page.should have_content("Password can't be blank")
112 | end
113 |
114 | # Verification
115 |
116 | Then /^I should be signed in$/ do
117 | visit "/"
118 | page.should have_content "Sign out"
119 | end
120 |
121 | Then /^I should be signed out$/ do
122 | visit "/"
123 | page.should have_content "Sign in"
124 | end
125 |
--------------------------------------------------------------------------------
/lib/clearance/authentication.rb:
--------------------------------------------------------------------------------
1 | module Clearance
2 | module Authentication
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | helper_method :current_user, :signed_in?, :signed_out?
7 | hide_action :current_user, :current_user=,
8 | :signed_in?, :signed_out?,
9 | :sign_in, :sign_out,
10 | :authorize, :deny_access
11 | end
12 |
13 | # Finds the user from the rack clearance session
14 | #
15 | # @return [User, nil]
16 | def current_user
17 | clearance_session.current_user
18 | end
19 |
20 | # Set the current user
21 | #
22 | # @param [User]
23 | def current_user=(user)
24 | clearance_session.sign_in user
25 | end
26 |
27 | # Is the current user signed in?
28 | #
29 | # @return [true, false]
30 | def signed_in?
31 | clearance_session.signed_in?
32 | end
33 |
34 | # Is the current user signed out?
35 | #
36 | # @return [true, false]
37 | def signed_out?
38 | !signed_in?
39 | end
40 |
41 | # Sign user in to cookie.
42 | #
43 | # @param [User]
44 | #
45 | # @example
46 | # sign_in(@user)
47 | def sign_in(user)
48 | clearance_session.sign_in user
49 | end
50 |
51 | # Sign user out of cookie.
52 | #
53 | # @example
54 | # sign_out
55 | def sign_out
56 | clearance_session.sign_out
57 | end
58 |
59 | # Find the user by the given params or return nil.
60 | # By default, uses email and password.
61 | # Redefine this method and User.authenticate for other mechanisms
62 | # such as username and password.
63 | #
64 | # @example
65 | # @user = authenticate(params)
66 | def authenticate(params)
67 | Clearance.configuration.user_model.authenticate(params[:session][:email],
68 | params[:session][:password])
69 | end
70 |
71 | # Deny the user access if they are signed out.
72 | #
73 | # @example
74 | # before_filter :authorize
75 | def authorize
76 | deny_access unless signed_in?
77 | end
78 |
79 | # Store the current location and redirect to sign in.
80 | # Display a failure flash message if included.
81 | #
82 | # @param [String] optional flash message to display to denied user
83 | def deny_access(flash_message = nil)
84 | store_location
85 | flash[:notice] = flash_message if flash_message
86 | if signed_in?
87 | redirect_to(url_after_denied_access_when_signed_in)
88 | else
89 | redirect_to(url_after_denied_access_when_signed_out)
90 | end
91 | end
92 |
93 | # CSRF protection in Rails >= 3.0.4
94 | # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
95 | def handle_unverified_request
96 | super
97 | sign_out
98 | end
99 |
100 | protected
101 |
102 | def clearance_session
103 | request.env[:clearance]
104 | end
105 |
106 | def store_location
107 | if request.get?
108 | session[:return_to] = request.fullpath
109 | end
110 | end
111 |
112 | def redirect_back_or(default)
113 | redirect_to(return_to || default)
114 | clear_return_to
115 | end
116 |
117 | def return_to
118 | session[:return_to] || params[:return_to]
119 | end
120 |
121 | def clear_return_to
122 | session[:return_to] = nil
123 | end
124 |
125 | def redirect_to_root
126 | redirect_to('/')
127 | end
128 |
129 | def url_after_denied_access_when_signed_in
130 | '/'
131 | end
132 |
133 | def url_after_denied_access_when_signed_out
134 | sign_in_url
135 | end
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/gemfiles/3.0.12.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: /home/mike/clearance
3 | specs:
4 | clearance (0.16.2)
5 | diesel (~> 0.1.5)
6 | rails (>= 3.0)
7 |
8 | GEM
9 | remote: http://rubygems.org/
10 | specs:
11 | abstract (1.0.0)
12 | actionmailer (3.0.12)
13 | actionpack (= 3.0.12)
14 | mail (~> 2.2.19)
15 | actionpack (3.0.12)
16 | activemodel (= 3.0.12)
17 | activesupport (= 3.0.12)
18 | builder (~> 2.1.2)
19 | erubis (~> 2.6.6)
20 | i18n (~> 0.5.0)
21 | rack (~> 1.2.5)
22 | rack-mount (~> 0.6.14)
23 | rack-test (~> 0.5.7)
24 | tzinfo (~> 0.3.23)
25 | activemodel (3.0.12)
26 | activesupport (= 3.0.12)
27 | builder (~> 2.1.2)
28 | i18n (~> 0.5.0)
29 | activerecord (3.0.12)
30 | activemodel (= 3.0.12)
31 | activesupport (= 3.0.12)
32 | arel (~> 2.0.10)
33 | tzinfo (~> 0.3.23)
34 | activeresource (3.0.12)
35 | activemodel (= 3.0.12)
36 | activesupport (= 3.0.12)
37 | activesupport (3.0.12)
38 | addressable (2.2.7)
39 | appraisal (0.4.1)
40 | bundler
41 | rake
42 | arel (2.0.10)
43 | aruba (0.4.11)
44 | childprocess (>= 0.2.3)
45 | cucumber (>= 1.1.1)
46 | ffi (>= 1.0.11)
47 | rspec (>= 2.7.0)
48 | bourne (1.1.2)
49 | mocha (= 0.10.5)
50 | builder (2.1.2)
51 | capybara (1.1.2)
52 | mime-types (>= 1.16)
53 | nokogiri (>= 1.3.3)
54 | rack (>= 1.0.0)
55 | rack-test (>= 0.5.4)
56 | selenium-webdriver (~> 2.0)
57 | xpath (~> 0.1.4)
58 | childprocess (0.3.1)
59 | ffi (~> 1.0.6)
60 | cucumber (1.1.9)
61 | builder (>= 2.1.2)
62 | diff-lcs (>= 1.1.2)
63 | gherkin (~> 2.9.0)
64 | json (>= 1.4.6)
65 | term-ansicolor (>= 1.0.6)
66 | cucumber-rails (1.1.1)
67 | capybara (>= 1.1.1)
68 | cucumber (>= 1.1.0)
69 | nokogiri (>= 1.5.0)
70 | database_cleaner (0.7.2)
71 | diesel (0.1.5)
72 | railties
73 | diff-lcs (1.1.3)
74 | erubis (2.6.6)
75 | abstract (>= 1.0.0)
76 | factory_girl (3.1.1)
77 | activesupport (>= 3.0.0)
78 | factory_girl_rails (3.1.0)
79 | factory_girl (~> 3.1.0)
80 | railties (>= 3.0.0)
81 | ffi (1.0.11)
82 | gherkin (2.9.3)
83 | json (>= 1.4.6)
84 | i18n (0.5.0)
85 | json (1.6.6)
86 | launchy (2.1.0)
87 | addressable (~> 2.2.6)
88 | mail (2.2.19)
89 | activesupport (>= 2.3.6)
90 | i18n (>= 0.4.0)
91 | mime-types (~> 1.16)
92 | treetop (~> 1.4.8)
93 | metaclass (0.0.1)
94 | mime-types (1.18)
95 | mocha (0.10.5)
96 | metaclass (~> 0.0.1)
97 | multi_json (1.2.0)
98 | nokogiri (1.5.2)
99 | polyglot (0.3.3)
100 | rack (1.2.5)
101 | rack-mount (0.6.14)
102 | rack (>= 1.0.0)
103 | rack-test (0.5.7)
104 | rack (>= 1.0)
105 | rails (3.0.12)
106 | actionmailer (= 3.0.12)
107 | actionpack (= 3.0.12)
108 | activerecord (= 3.0.12)
109 | activeresource (= 3.0.12)
110 | activesupport (= 3.0.12)
111 | bundler (~> 1.0)
112 | railties (= 3.0.12)
113 | railties (3.0.12)
114 | actionpack (= 3.0.12)
115 | activesupport (= 3.0.12)
116 | rake (>= 0.8.7)
117 | rdoc (~> 3.4)
118 | thor (~> 0.14.4)
119 | rake (0.9.2.2)
120 | rdoc (3.12)
121 | json (~> 1.4)
122 | rspec (2.9.0)
123 | rspec-core (~> 2.9.0)
124 | rspec-expectations (~> 2.9.0)
125 | rspec-mocks (~> 2.9.0)
126 | rspec-core (2.9.0)
127 | rspec-expectations (2.9.1)
128 | diff-lcs (~> 1.1.3)
129 | rspec-mocks (2.9.0)
130 | rspec-rails (2.9.0)
131 | actionpack (>= 3.0)
132 | activesupport (>= 3.0)
133 | railties (>= 3.0)
134 | rspec (~> 2.9.0)
135 | rubyzip (0.9.6.1)
136 | selenium-webdriver (2.20.0)
137 | childprocess (>= 0.2.5)
138 | ffi (~> 1.0)
139 | multi_json (~> 1.0)
140 | rubyzip
141 | shoulda-matchers (1.1.0)
142 | activesupport (>= 3.0.0)
143 | sqlite3 (1.3.5)
144 | term-ansicolor (1.0.7)
145 | thor (0.14.6)
146 | timecop (0.3.5)
147 | treetop (1.4.10)
148 | polyglot
149 | polyglot (>= 0.3.1)
150 | tzinfo (0.3.32)
151 | xpath (0.1.4)
152 | nokogiri (~> 1.3)
153 |
154 | PLATFORMS
155 | ruby
156 |
157 | DEPENDENCIES
158 | appraisal (~> 0.4.1)
159 | aruba (~> 0.4.11)
160 | bourne (~> 1.1.2)
161 | bundler (~> 1.1.0)
162 | capybara (~> 1.1.2)
163 | clearance!
164 | cucumber-rails (~> 1.1.1)
165 | database_cleaner
166 | factory_girl_rails (~> 3.1.0)
167 | launchy
168 | rails (= 3.0.12)
169 | rspec-rails (~> 2.9.0)
170 | shoulda-matchers (~> 1.1.0)
171 | sqlite3
172 | timecop
173 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | clearance (0.16.2)
5 | diesel (~> 0.1.5)
6 | rails (>= 3.0)
7 |
8 | GEM
9 | remote: http://rubygems.org/
10 | specs:
11 | actionmailer (3.2.1)
12 | actionpack (= 3.2.1)
13 | mail (~> 2.4.0)
14 | actionpack (3.2.1)
15 | activemodel (= 3.2.1)
16 | activesupport (= 3.2.1)
17 | builder (~> 3.0.0)
18 | erubis (~> 2.7.0)
19 | journey (~> 1.0.1)
20 | rack (~> 1.4.0)
21 | rack-cache (~> 1.1)
22 | rack-test (~> 0.6.1)
23 | sprockets (~> 2.1.2)
24 | activemodel (3.2.1)
25 | activesupport (= 3.2.1)
26 | builder (~> 3.0.0)
27 | activerecord (3.2.1)
28 | activemodel (= 3.2.1)
29 | activesupport (= 3.2.1)
30 | arel (~> 3.0.0)
31 | tzinfo (~> 0.3.29)
32 | activeresource (3.2.1)
33 | activemodel (= 3.2.1)
34 | activesupport (= 3.2.1)
35 | activesupport (3.2.1)
36 | i18n (~> 0.6)
37 | multi_json (~> 1.0)
38 | addressable (2.2.6)
39 | appraisal (0.4.1)
40 | bundler
41 | rake
42 | arel (3.0.2)
43 | aruba (0.4.11)
44 | childprocess (>= 0.2.3)
45 | cucumber (>= 1.1.1)
46 | ffi (>= 1.0.11)
47 | rspec (>= 2.7.0)
48 | bourne (1.1.2)
49 | mocha (= 0.10.5)
50 | builder (3.0.0)
51 | capybara (1.1.2)
52 | mime-types (>= 1.16)
53 | nokogiri (>= 1.3.3)
54 | rack (>= 1.0.0)
55 | rack-test (>= 0.5.4)
56 | selenium-webdriver (~> 2.0)
57 | xpath (~> 0.1.4)
58 | childprocess (0.3.0)
59 | ffi (~> 1.0.6)
60 | cucumber (1.1.4)
61 | builder (>= 2.1.2)
62 | diff-lcs (>= 1.1.2)
63 | gherkin (~> 2.7.1)
64 | json (>= 1.4.6)
65 | term-ansicolor (>= 1.0.6)
66 | cucumber-rails (1.1.1)
67 | capybara (>= 1.1.1)
68 | cucumber (>= 1.1.0)
69 | nokogiri (>= 1.5.0)
70 | database_cleaner (0.7.1)
71 | diesel (0.1.5)
72 | railties
73 | diff-lcs (1.1.3)
74 | erubis (2.7.0)
75 | factory_girl (3.1.1)
76 | activesupport (>= 3.0.0)
77 | factory_girl_rails (3.1.0)
78 | factory_girl (~> 3.1.0)
79 | railties (>= 3.0.0)
80 | ffi (1.0.11)
81 | gherkin (2.7.6)
82 | json (>= 1.4.6)
83 | hike (1.2.1)
84 | i18n (0.6.0)
85 | journey (1.0.3)
86 | json (1.6.6)
87 | launchy (2.0.5)
88 | addressable (~> 2.2.6)
89 | mail (2.4.4)
90 | i18n (>= 0.4.0)
91 | mime-types (~> 1.16)
92 | treetop (~> 1.4.8)
93 | metaclass (0.0.1)
94 | mime-types (1.17.2)
95 | mocha (0.10.5)
96 | metaclass (~> 0.0.1)
97 | multi_json (1.0.4)
98 | nokogiri (1.5.0)
99 | polyglot (0.3.3)
100 | rack (1.4.1)
101 | rack-cache (1.2)
102 | rack (>= 0.4)
103 | rack-ssl (1.3.2)
104 | rack
105 | rack-test (0.6.1)
106 | rack (>= 1.0)
107 | rails (3.2.1)
108 | actionmailer (= 3.2.1)
109 | actionpack (= 3.2.1)
110 | activerecord (= 3.2.1)
111 | activeresource (= 3.2.1)
112 | activesupport (= 3.2.1)
113 | bundler (~> 1.0)
114 | railties (= 3.2.1)
115 | railties (3.2.1)
116 | actionpack (= 3.2.1)
117 | activesupport (= 3.2.1)
118 | rack-ssl (~> 1.3.2)
119 | rake (>= 0.8.7)
120 | rdoc (~> 3.4)
121 | thor (~> 0.14.6)
122 | rake (0.9.2.2)
123 | rdoc (3.12)
124 | json (~> 1.4)
125 | rspec (2.9.0)
126 | rspec-core (~> 2.9.0)
127 | rspec-expectations (~> 2.9.0)
128 | rspec-mocks (~> 2.9.0)
129 | rspec-core (2.9.0)
130 | rspec-expectations (2.9.1)
131 | diff-lcs (~> 1.1.3)
132 | rspec-mocks (2.9.0)
133 | rspec-rails (2.9.0)
134 | actionpack (>= 3.0)
135 | activesupport (>= 3.0)
136 | railties (>= 3.0)
137 | rspec (~> 2.9.0)
138 | rubyzip (0.9.5)
139 | selenium-webdriver (2.18.0)
140 | childprocess (>= 0.2.5)
141 | ffi (~> 1.0.9)
142 | multi_json (~> 1.0.4)
143 | rubyzip
144 | shoulda-matchers (1.1.0)
145 | activesupport (>= 3.0.0)
146 | sprockets (2.1.2)
147 | hike (~> 1.2)
148 | rack (~> 1.0)
149 | tilt (~> 1.1, != 1.3.0)
150 | sqlite3 (1.3.5)
151 | term-ansicolor (1.0.7)
152 | thor (0.14.6)
153 | tilt (1.3.3)
154 | timecop (0.3.5)
155 | treetop (1.4.10)
156 | polyglot
157 | polyglot (>= 0.3.1)
158 | tzinfo (0.3.33)
159 | xpath (0.1.4)
160 | nokogiri (~> 1.3)
161 |
162 | PLATFORMS
163 | ruby
164 |
165 | DEPENDENCIES
166 | appraisal (~> 0.4.1)
167 | aruba (~> 0.4.11)
168 | bourne (~> 1.1.2)
169 | bundler (~> 1.1.0)
170 | capybara (~> 1.1.2)
171 | clearance!
172 | cucumber-rails (~> 1.1.1)
173 | database_cleaner
174 | factory_girl_rails (~> 3.1.0)
175 | launchy
176 | rspec-rails (~> 2.9.0)
177 | shoulda-matchers (~> 1.1.0)
178 | sqlite3
179 | timecop
180 |
--------------------------------------------------------------------------------
/gemfiles/3.2.3.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: /home/mike/clearance
3 | specs:
4 | clearance (0.16.2)
5 | diesel (~> 0.1.5)
6 | rails (>= 3.0)
7 |
8 | GEM
9 | remote: http://rubygems.org/
10 | specs:
11 | actionmailer (3.2.3)
12 | actionpack (= 3.2.3)
13 | mail (~> 2.4.4)
14 | actionpack (3.2.3)
15 | activemodel (= 3.2.3)
16 | activesupport (= 3.2.3)
17 | builder (~> 3.0.0)
18 | erubis (~> 2.7.0)
19 | journey (~> 1.0.1)
20 | rack (~> 1.4.0)
21 | rack-cache (~> 1.2)
22 | rack-test (~> 0.6.1)
23 | sprockets (~> 2.1.2)
24 | activemodel (3.2.3)
25 | activesupport (= 3.2.3)
26 | builder (~> 3.0.0)
27 | activerecord (3.2.3)
28 | activemodel (= 3.2.3)
29 | activesupport (= 3.2.3)
30 | arel (~> 3.0.2)
31 | tzinfo (~> 0.3.29)
32 | activeresource (3.2.3)
33 | activemodel (= 3.2.3)
34 | activesupport (= 3.2.3)
35 | activesupport (3.2.3)
36 | i18n (~> 0.6)
37 | multi_json (~> 1.0)
38 | addressable (2.2.7)
39 | appraisal (0.4.1)
40 | bundler
41 | rake
42 | arel (3.0.2)
43 | aruba (0.4.11)
44 | childprocess (>= 0.2.3)
45 | cucumber (>= 1.1.1)
46 | ffi (>= 1.0.11)
47 | rspec (>= 2.7.0)
48 | bourne (1.1.2)
49 | mocha (= 0.10.5)
50 | builder (3.0.0)
51 | capybara (1.1.2)
52 | mime-types (>= 1.16)
53 | nokogiri (>= 1.3.3)
54 | rack (>= 1.0.0)
55 | rack-test (>= 0.5.4)
56 | selenium-webdriver (~> 2.0)
57 | xpath (~> 0.1.4)
58 | childprocess (0.3.1)
59 | ffi (~> 1.0.6)
60 | cucumber (1.1.9)
61 | builder (>= 2.1.2)
62 | diff-lcs (>= 1.1.2)
63 | gherkin (~> 2.9.0)
64 | json (>= 1.4.6)
65 | term-ansicolor (>= 1.0.6)
66 | cucumber-rails (1.1.1)
67 | capybara (>= 1.1.1)
68 | cucumber (>= 1.1.0)
69 | nokogiri (>= 1.5.0)
70 | database_cleaner (0.7.2)
71 | diesel (0.1.5)
72 | railties
73 | diff-lcs (1.1.3)
74 | erubis (2.7.0)
75 | factory_girl (3.1.1)
76 | activesupport (>= 3.0.0)
77 | factory_girl_rails (3.1.0)
78 | factory_girl (~> 3.1.0)
79 | railties (>= 3.0.0)
80 | ffi (1.0.11)
81 | gherkin (2.9.3)
82 | json (>= 1.4.6)
83 | hike (1.2.1)
84 | i18n (0.6.0)
85 | journey (1.0.3)
86 | json (1.6.6)
87 | launchy (2.1.0)
88 | addressable (~> 2.2.6)
89 | mail (2.4.4)
90 | i18n (>= 0.4.0)
91 | mime-types (~> 1.16)
92 | treetop (~> 1.4.8)
93 | metaclass (0.0.1)
94 | mime-types (1.18)
95 | mocha (0.10.5)
96 | metaclass (~> 0.0.1)
97 | multi_json (1.2.0)
98 | nokogiri (1.5.2)
99 | polyglot (0.3.3)
100 | rack (1.4.1)
101 | rack-cache (1.2)
102 | rack (>= 0.4)
103 | rack-ssl (1.3.2)
104 | rack
105 | rack-test (0.6.1)
106 | rack (>= 1.0)
107 | rails (3.2.3)
108 | actionmailer (= 3.2.3)
109 | actionpack (= 3.2.3)
110 | activerecord (= 3.2.3)
111 | activeresource (= 3.2.3)
112 | activesupport (= 3.2.3)
113 | bundler (~> 1.0)
114 | railties (= 3.2.3)
115 | railties (3.2.3)
116 | actionpack (= 3.2.3)
117 | activesupport (= 3.2.3)
118 | rack-ssl (~> 1.3.2)
119 | rake (>= 0.8.7)
120 | rdoc (~> 3.4)
121 | thor (~> 0.14.6)
122 | rake (0.9.2.2)
123 | rdoc (3.12)
124 | json (~> 1.4)
125 | rspec (2.9.0)
126 | rspec-core (~> 2.9.0)
127 | rspec-expectations (~> 2.9.0)
128 | rspec-mocks (~> 2.9.0)
129 | rspec-core (2.9.0)
130 | rspec-expectations (2.9.1)
131 | diff-lcs (~> 1.1.3)
132 | rspec-mocks (2.9.0)
133 | rspec-rails (2.9.0)
134 | actionpack (>= 3.0)
135 | activesupport (>= 3.0)
136 | railties (>= 3.0)
137 | rspec (~> 2.9.0)
138 | rubyzip (0.9.6.1)
139 | selenium-webdriver (2.20.0)
140 | childprocess (>= 0.2.5)
141 | ffi (~> 1.0)
142 | multi_json (~> 1.0)
143 | rubyzip
144 | shoulda-matchers (1.1.0)
145 | activesupport (>= 3.0.0)
146 | sprockets (2.1.2)
147 | hike (~> 1.2)
148 | rack (~> 1.0)
149 | tilt (~> 1.1, != 1.3.0)
150 | sqlite3 (1.3.5)
151 | term-ansicolor (1.0.7)
152 | thor (0.14.6)
153 | tilt (1.3.3)
154 | timecop (0.3.5)
155 | treetop (1.4.10)
156 | polyglot
157 | polyglot (>= 0.3.1)
158 | tzinfo (0.3.32)
159 | xpath (0.1.4)
160 | nokogiri (~> 1.3)
161 |
162 | PLATFORMS
163 | ruby
164 |
165 | DEPENDENCIES
166 | appraisal (~> 0.4.1)
167 | aruba (~> 0.4.11)
168 | bourne (~> 1.1.2)
169 | bundler (~> 1.1.0)
170 | capybara (~> 1.1.2)
171 | clearance!
172 | cucumber-rails (~> 1.1.1)
173 | database_cleaner
174 | factory_girl_rails (~> 3.1.0)
175 | launchy
176 | rails (= 3.2.3)
177 | rspec-rails (~> 2.9.0)
178 | shoulda-matchers (~> 1.1.0)
179 | sqlite3
180 | timecop
181 |
--------------------------------------------------------------------------------
/gemfiles/3.1.4.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: /home/mike/clearance
3 | specs:
4 | clearance (0.16.2)
5 | diesel (~> 0.1.5)
6 | rails (>= 3.0)
7 |
8 | GEM
9 | remote: http://rubygems.org/
10 | specs:
11 | actionmailer (3.1.4)
12 | actionpack (= 3.1.4)
13 | mail (~> 2.3.0)
14 | actionpack (3.1.4)
15 | activemodel (= 3.1.4)
16 | activesupport (= 3.1.4)
17 | builder (~> 3.0.0)
18 | erubis (~> 2.7.0)
19 | i18n (~> 0.6)
20 | rack (~> 1.3.6)
21 | rack-cache (~> 1.1)
22 | rack-mount (~> 0.8.2)
23 | rack-test (~> 0.6.1)
24 | sprockets (~> 2.0.3)
25 | activemodel (3.1.4)
26 | activesupport (= 3.1.4)
27 | builder (~> 3.0.0)
28 | i18n (~> 0.6)
29 | activerecord (3.1.4)
30 | activemodel (= 3.1.4)
31 | activesupport (= 3.1.4)
32 | arel (~> 2.2.3)
33 | tzinfo (~> 0.3.29)
34 | activeresource (3.1.4)
35 | activemodel (= 3.1.4)
36 | activesupport (= 3.1.4)
37 | activesupport (3.1.4)
38 | multi_json (~> 1.0)
39 | addressable (2.2.7)
40 | appraisal (0.4.1)
41 | bundler
42 | rake
43 | arel (2.2.3)
44 | aruba (0.4.11)
45 | childprocess (>= 0.2.3)
46 | cucumber (>= 1.1.1)
47 | ffi (>= 1.0.11)
48 | rspec (>= 2.7.0)
49 | bourne (1.1.2)
50 | mocha (= 0.10.5)
51 | builder (3.0.0)
52 | capybara (1.1.2)
53 | mime-types (>= 1.16)
54 | nokogiri (>= 1.3.3)
55 | rack (>= 1.0.0)
56 | rack-test (>= 0.5.4)
57 | selenium-webdriver (~> 2.0)
58 | xpath (~> 0.1.4)
59 | childprocess (0.3.1)
60 | ffi (~> 1.0.6)
61 | cucumber (1.1.9)
62 | builder (>= 2.1.2)
63 | diff-lcs (>= 1.1.2)
64 | gherkin (~> 2.9.0)
65 | json (>= 1.4.6)
66 | term-ansicolor (>= 1.0.6)
67 | cucumber-rails (1.1.1)
68 | capybara (>= 1.1.1)
69 | cucumber (>= 1.1.0)
70 | nokogiri (>= 1.5.0)
71 | database_cleaner (0.7.2)
72 | diesel (0.1.5)
73 | railties
74 | diff-lcs (1.1.3)
75 | erubis (2.7.0)
76 | factory_girl (3.1.1)
77 | activesupport (>= 3.0.0)
78 | factory_girl_rails (3.1.0)
79 | factory_girl (~> 3.1.0)
80 | railties (>= 3.0.0)
81 | ffi (1.0.11)
82 | gherkin (2.9.3)
83 | json (>= 1.4.6)
84 | hike (1.2.1)
85 | i18n (0.6.0)
86 | json (1.6.6)
87 | launchy (2.1.0)
88 | addressable (~> 2.2.6)
89 | mail (2.3.3)
90 | i18n (>= 0.4.0)
91 | mime-types (~> 1.16)
92 | treetop (~> 1.4.8)
93 | metaclass (0.0.1)
94 | mime-types (1.18)
95 | mocha (0.10.5)
96 | metaclass (~> 0.0.1)
97 | multi_json (1.2.0)
98 | nokogiri (1.5.2)
99 | polyglot (0.3.3)
100 | rack (1.3.6)
101 | rack-cache (1.2)
102 | rack (>= 0.4)
103 | rack-mount (0.8.3)
104 | rack (>= 1.0.0)
105 | rack-ssl (1.3.2)
106 | rack
107 | rack-test (0.6.1)
108 | rack (>= 1.0)
109 | rails (3.1.4)
110 | actionmailer (= 3.1.4)
111 | actionpack (= 3.1.4)
112 | activerecord (= 3.1.4)
113 | activeresource (= 3.1.4)
114 | activesupport (= 3.1.4)
115 | bundler (~> 1.0)
116 | railties (= 3.1.4)
117 | railties (3.1.4)
118 | actionpack (= 3.1.4)
119 | activesupport (= 3.1.4)
120 | rack-ssl (~> 1.3.2)
121 | rake (>= 0.8.7)
122 | rdoc (~> 3.4)
123 | thor (~> 0.14.6)
124 | rake (0.9.2.2)
125 | rdoc (3.12)
126 | json (~> 1.4)
127 | rspec (2.9.0)
128 | rspec-core (~> 2.9.0)
129 | rspec-expectations (~> 2.9.0)
130 | rspec-mocks (~> 2.9.0)
131 | rspec-core (2.9.0)
132 | rspec-expectations (2.9.1)
133 | diff-lcs (~> 1.1.3)
134 | rspec-mocks (2.9.0)
135 | rspec-rails (2.9.0)
136 | actionpack (>= 3.0)
137 | activesupport (>= 3.0)
138 | railties (>= 3.0)
139 | rspec (~> 2.9.0)
140 | rubyzip (0.9.6.1)
141 | selenium-webdriver (2.20.0)
142 | childprocess (>= 0.2.5)
143 | ffi (~> 1.0)
144 | multi_json (~> 1.0)
145 | rubyzip
146 | shoulda-matchers (1.1.0)
147 | activesupport (>= 3.0.0)
148 | sprockets (2.0.3)
149 | hike (~> 1.2)
150 | rack (~> 1.0)
151 | tilt (~> 1.1, != 1.3.0)
152 | sqlite3 (1.3.5)
153 | term-ansicolor (1.0.7)
154 | thor (0.14.6)
155 | tilt (1.3.3)
156 | timecop (0.3.5)
157 | treetop (1.4.10)
158 | polyglot
159 | polyglot (>= 0.3.1)
160 | tzinfo (0.3.32)
161 | xpath (0.1.4)
162 | nokogiri (~> 1.3)
163 |
164 | PLATFORMS
165 | ruby
166 |
167 | DEPENDENCIES
168 | appraisal (~> 0.4.1)
169 | aruba (~> 0.4.11)
170 | bourne (~> 1.1.2)
171 | bundler (~> 1.1.0)
172 | capybara (~> 1.1.2)
173 | clearance!
174 | cucumber-rails (~> 1.1.1)
175 | database_cleaner
176 | factory_girl_rails (~> 3.1.0)
177 | launchy
178 | rails (= 3.1.4)
179 | rspec-rails (~> 2.9.0)
180 | shoulda-matchers (~> 1.1.0)
181 | sqlite3
182 | timecop
183 |
--------------------------------------------------------------------------------
/lib/clearance/user.rb:
--------------------------------------------------------------------------------
1 | require 'digest/sha1'
2 |
3 | module Clearance
4 | module User
5 | extend ActiveSupport::Concern
6 |
7 | # Hook for all Clearance::User modules.
8 | #
9 | # If you need to override parts of Clearance::User,
10 | # extend and include à la carte.
11 | #
12 | # @example
13 | # include Clearance::User::Callbacks
14 | #
15 | # @see Validations
16 | # @see Callbacks
17 | included do
18 | attr_accessor :password_changing
19 | attr_reader :password
20 |
21 | include Validations
22 | include Callbacks
23 |
24 | include (Clearance.configuration.password_strategy || Clearance::PasswordStrategies::SHA1)
25 | end
26 |
27 | module ClassMethods
28 | # Authenticate with email and password.
29 | #
30 | # @param [String, String] email and password
31 | # @return [User, nil] authenticated user or nil
32 | # @example
33 | # User.authenticate("email@example.com", "password")
34 | def authenticate(email, password)
35 | return nil unless user = find_by_email(email.to_s.downcase)
36 | return user if user.authenticated?(password)
37 | end
38 | end
39 |
40 | module Validations
41 | extend ActiveSupport::Concern
42 |
43 | # Hook for validations.
44 | #
45 | # :email must be present, unique, formatted
46 | #
47 | # If password is required,
48 | # :password must be present, confirmed
49 | included do
50 | validates_presence_of :email, :unless => :email_optional?
51 | validates_uniqueness_of :email, :allow_blank => true
52 | validates_format_of :email, :with => %r{^[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$}i, :allow_blank => true
53 |
54 | validates_presence_of :password, :unless => :password_optional?
55 | end
56 | end
57 |
58 | module Callbacks
59 | extend ActiveSupport::Concern
60 |
61 | # Hook for callbacks.
62 | #
63 | # salt, token, password encryption are handled before_save.
64 | included do
65 | before_validation :downcase_email
66 | before_create :generate_remember_token
67 | end
68 | end
69 |
70 | # Set the remember token.
71 | #
72 | # @deprecated Use {#reset_remember_token!} instead
73 | def remember_me!
74 | warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
75 | reset_remember_token!
76 | end
77 |
78 | # Reset the remember token.
79 | #
80 | # @example
81 | # user.reset_remember_token!
82 | def reset_remember_token!
83 | generate_remember_token
84 | save(:validate => false)
85 | end
86 |
87 | # Mark my account as forgotten password.
88 | #
89 | # @example
90 | # user.forgot_password!
91 | def forgot_password!
92 | generate_confirmation_token
93 | save(:validate => false)
94 | end
95 |
96 | # Update my password.
97 | #
98 | # @return [true, false] password was updated or not
99 | # @example
100 | # user.update_password('new-password')
101 | def update_password(new_password)
102 | self.password_changing = true
103 | self.password = new_password
104 | if valid?
105 | self.confirmation_token = nil
106 | generate_remember_token
107 | end
108 | save
109 | end
110 |
111 | def password=(unencrypted_password)
112 | @password = unencrypted_password
113 | encrypt_password
114 | end
115 |
116 | protected
117 |
118 | def generate_random_code(length = 20)
119 | if RUBY_VERSION >= '1.9'
120 | SecureRandom.hex(length).encode('UTF-8')
121 | else
122 | SecureRandom.hex(length)
123 | end
124 | end
125 |
126 | def generate_remember_token
127 | self.remember_token = generate_random_code
128 | end
129 |
130 | def generate_confirmation_token
131 | self.confirmation_token = generate_random_code
132 | end
133 |
134 | # Always false. Override to allow other forms of authentication
135 | # (username, facebook, etc).
136 | # @return [Boolean] true if the email field be left blank for this user
137 | def email_optional?
138 | false
139 | end
140 |
141 | # True if the password has been set and the password is not being
142 | # updated and we are not updating the password. Override to allow
143 | # other forms of authentication (username, facebook, etc).
144 | # @return [Boolean] true if the password field can be left blank for this user
145 | def password_optional?
146 | encrypted_password.present? && password.blank? && password_changing.blank?
147 | end
148 |
149 | def password_required?
150 | # warn "[DEPRECATION] password_required?: use !password_optional? instead"
151 | !password_optional?
152 | end
153 |
154 | def downcase_email
155 | self.email = email.to_s.downcase
156 | end
157 | end
158 | end
159 |
--------------------------------------------------------------------------------
/spec/controllers/passwords_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Clearance::PasswordsController do
4 | include Shoulda::Matchers::ActionMailer
5 |
6 | it { should route(:get, '/users/1/password/edit').
7 | to(:controller => 'clearance/passwords', :action => 'edit', :user_id => '1') }
8 |
9 | describe "a signed up user" do
10 | before do
11 | @user = create(:user)
12 | end
13 |
14 | describe "on GET to #new" do
15 | before { get :new, :user_id => @user.to_param }
16 |
17 | it { should respond_with(:success) }
18 | it { should render_template(:new) }
19 | end
20 |
21 | describe "on POST to #create" do
22 | describe "with correct email address" do
23 | before do
24 | ActionMailer::Base.deliveries.clear
25 | post :create, :password => { :email => @user.email }
26 | end
27 |
28 | it "should generate a token for the change your password email" do
29 | @user.reload.confirmation_token.should_not be_nil
30 | end
31 |
32 | it { should have_sent_email.with_subject(/change your password/i) }
33 |
34 | it { should respond_with(:success) }
35 | end
36 |
37 | describe "with incorrect email address" do
38 | before do
39 | email = "user1@example.com"
40 | (Clearance.configuration.user_model.exists?(['email = ?', email])).should_not be
41 | ActionMailer::Base.deliveries.clear
42 | @user.reload.confirmation_token.should == @user.confirmation_token
43 |
44 | post :create, :password => { :email => email }
45 | end
46 |
47 | it "should not generate a token for the change your password email" do
48 | @user.reload.confirmation_token.should == @user.confirmation_token
49 | end
50 |
51 | it "should not send a password reminder email" do
52 | ActionMailer::Base.deliveries.should be_empty
53 | end
54 |
55 | it { should set_the_flash.to(/unknown email/i).now }
56 | it { should render_template(:new) }
57 | end
58 | end
59 | end
60 |
61 | describe "a signed up user and forgotten password" do
62 | before do
63 | @user = create(:user)
64 | @user.forgot_password!
65 | end
66 |
67 | describe "on GET to #edit with correct id and token" do
68 | before do
69 | get :edit, :user_id => @user.to_param,
70 | :token => @user.confirmation_token
71 | end
72 |
73 | it "should find the user" do
74 | assigns(:user).should == @user
75 | end
76 |
77 | it { should respond_with(:success) }
78 | it { should render_template(:edit) }
79 | end
80 |
81 | describe "on GET to #edit with correct id but blank token" do
82 | before do
83 | get :edit, :user_id => @user.to_param, :token => ""
84 | end
85 |
86 | it { should set_the_flash.to(/double check the URL/i).now }
87 | it { should render_template(:new) }
88 | end
89 |
90 | describe "on GET to #edit with correct id but no token" do
91 | before do
92 | get :edit, :user_id => @user.to_param
93 | end
94 |
95 | it { should set_the_flash.to(/double check the URL/i).now }
96 | it { should render_template(:new) }
97 | end
98 |
99 | describe "on PUT to #update with password" do
100 | before do
101 | new_password = "new_password"
102 | @encrypted_new_password = @user.send(:encrypt, new_password)
103 | @user.encrypted_password.should_not == @encrypted_new_password
104 |
105 | put(:update,
106 | :user_id => @user,
107 | :token => @user.confirmation_token,
108 | :user => {
109 | :password => new_password
110 | })
111 | @user.reload
112 | end
113 |
114 | it "should update password" do
115 | @user.encrypted_password.should == @encrypted_new_password
116 | end
117 |
118 | it "should clear confirmation token" do
119 | @user.confirmation_token.should be_nil
120 | end
121 |
122 | it "should set remember token" do
123 | @user.remember_token.should_not be_nil
124 | end
125 |
126 | it { should redirect_to_url_after_update }
127 | end
128 |
129 | describe "on PUT to #update with blank password" do
130 | before do
131 | put(:update,
132 | :user_id => @user.to_param,
133 | :token => @user.confirmation_token,
134 | :user => {
135 | :password => ''
136 | })
137 | @user.reload
138 | end
139 |
140 | it "should not update password" do
141 | @user.encrypted_password.should_not == @encrypted_new_password
142 | end
143 |
144 | it "should not clear token" do
145 | @user.confirmation_token.should_not be_nil
146 | end
147 |
148 | it "should not be signed in" do
149 | cookies[:remember_token].should be_nil
150 | end
151 |
152 | it { should set_the_flash.to(/password can't be blank/i).now }
153 | it { should respond_with(:success) }
154 | it { should render_template(:edit) }
155 | end
156 |
157 | describe "on PUT to #update with an empty token after the user sets a password" do
158 | before do
159 | put :update,
160 | :user_id => @user.to_param,
161 | :token => @user.confirmation_token,
162 | :user => { :password => 'good password' }
163 | put :update,
164 | :user_id => @user.to_param,
165 | :token => [nil],
166 | :user => { :password => 'new password' }
167 | end
168 |
169 | it { should set_the_flash.to(/double check the URL/i).now }
170 | it { should render_template(:new) }
171 | end
172 | end
173 |
174 | describe "given two users and user one signs in" do
175 | before do
176 | @user_one = create(:user)
177 | @user_two = create(:user)
178 | sign_in_as @user_one
179 | end
180 | end
181 | end
182 |
--------------------------------------------------------------------------------
/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe User do
4 | it { should have_db_index(:email) }
5 | it { should have_db_index(:remember_token) }
6 |
7 | describe "When signing up" do
8 | it { should validate_presence_of(:email) }
9 | it { should validate_presence_of(:password) }
10 | it { should allow_value("foo@example.co.uk").for(:email) }
11 | it { should allow_value("foo@example.com").for(:email) }
12 | it { should_not allow_value("foo@").for(:email) }
13 | it { should_not allow_value("foo@example..com").for(:email) }
14 | it { should_not allow_value("foo@.example.com").for(:email) }
15 | it { should_not allow_value("foo").for(:email) }
16 | it { should_not allow_value("example.com").for(:email) }
17 |
18 | it "should store email in down case" do
19 | user = create(:user, :email => "John.Doe@example.com")
20 | user.email.should == "john.doe@example.com"
21 | end
22 | end
23 |
24 | describe "When multiple users have signed up" do
25 | before { create(:user) }
26 | it { should validate_uniqueness_of(:email) }
27 | end
28 |
29 | describe "A user" do
30 | before do
31 | @user = create(:user)
32 | @password = @user.password
33 | end
34 |
35 | it "is authenticated with correct email and password" do
36 | (Clearance.configuration.user_model.authenticate(@user.email, @password)).should be
37 | @user.should be_authenticated(@password)
38 | end
39 |
40 | it "is authenticated with correct uppercased email and correct password" do
41 | (Clearance.configuration.user_model.authenticate(@user.email.upcase, @password)).should be
42 | @user.should be_authenticated(@password)
43 | end
44 |
45 | it "is authenticated with incorrect credentials" do
46 | (Clearance.configuration.user_model.authenticate(@user.email, 'bad_password')).should_not be
47 | @user.should_not be_authenticated('bad password')
48 | end
49 | end
50 |
51 | describe "When resetting authentication with reset_remember_token!" do
52 | before do
53 | @user = create(:user)
54 | @user.remember_token = "old-token"
55 | @user.reset_remember_token!
56 | end
57 |
58 | it "should change the remember token" do
59 | @user.remember_token.should_not == "old-token"
60 | end
61 | end
62 |
63 | describe "An email confirmed user" do
64 | before do
65 | @user = create(:user)
66 | @old_encrypted_password = @user.encrypted_password
67 | end
68 |
69 | describe "who updates password" do
70 | before do
71 | @user.update_password("new_password")
72 | end
73 |
74 | it "should change encrypted password" do
75 | @user.encrypted_password.should_not == @old_encrypted_password
76 | end
77 | end
78 | end
79 |
80 | it "should not generate the same remember token for users with the same password at the same time" do
81 | Time.stubs(:now => Time.now)
82 | password = 'secret'
83 | first_user = create(:user, :password => password)
84 | second_user = create(:user, :password => password)
85 |
86 | second_user.remember_token.should_not == first_user.remember_token
87 | end
88 |
89 | describe "An user" do
90 | before do
91 | @user = create(:user)
92 | @old_encrypted_password = @user.encrypted_password
93 | end
94 |
95 | describe "who requests password reminder" do
96 | before do
97 | @user.confirmation_token.should be_nil
98 | @user.forgot_password!
99 | end
100 |
101 | it "should generate confirmation token" do
102 | @user.confirmation_token.should_not be_nil
103 | end
104 |
105 | describe "and then updates password" do
106 | describe 'with password' do
107 | before do
108 | @user.update_password("new_password")
109 | end
110 |
111 | it "should change encrypted password" do
112 | @user.encrypted_password.should_not == @old_encrypted_password
113 | end
114 |
115 | it "should clear confirmation token" do
116 | @user.confirmation_token.should be_nil
117 | end
118 | end
119 |
120 | describe 'with blank password' do
121 | before do
122 | @user.update_password("")
123 | end
124 |
125 | it "does not change encrypted password" do
126 | @user.encrypted_password.should == @old_encrypted_password
127 | end
128 |
129 | it "does not clear confirmation token" do
130 | @user.confirmation_token.should_not be_nil
131 | end
132 | end
133 | end
134 | end
135 | end
136 |
137 | describe "a user with an optional email" do
138 | before do
139 | @user = User.new
140 | class << @user
141 | def email_optional?
142 | true
143 | end
144 | end
145 | end
146 |
147 | subject { @user }
148 |
149 | it { should allow_value(nil).for(:email) }
150 | it { should allow_value("").for(:email) }
151 | end
152 |
153 | describe "a user with an optional password" do
154 | before do
155 | @user = User.new
156 | class << @user
157 | def password_optional?
158 | true
159 | end
160 | end
161 | end
162 |
163 | subject { @user }
164 |
165 | it { should allow_value(nil).for(:password) }
166 | it { should allow_value("").for(:password) }
167 | end
168 |
169 | describe "user factory" do
170 | it "should create a valid user with just an overridden password" do
171 | build(:user, :password => "test").should be_valid
172 | end
173 | end
174 |
175 | describe "when user exists before Clearance was installed" do
176 | before do
177 | @user = create(:user)
178 | sql = "update users set salt = NULL, encrypted_password = NULL, remember_token = NULL where id = #{@user.id}"
179 | ActiveRecord::Base.connection.update(sql)
180 | @user.reload.salt.should be_nil
181 | @user.reload.encrypted_password.should be_nil
182 | @user.reload.remember_token.should be_nil
183 | end
184 |
185 | it "should initialize salt, generate remember token, and save encrypted password on update_password" do
186 | @user.update_password('password')
187 | @user.salt.should_not be_nil
188 | @user.encrypted_password.should_not be_nil
189 | @user.remember_token.should_not be_nil
190 | end
191 | end
192 |
193 | describe "The password setter on a User" do
194 | let(:password) { "a-password" }
195 | before { subject.send(:password=, password) }
196 |
197 | it "sets password to the plain-text password" do
198 | subject.password.should == password
199 | end
200 |
201 | it "also sets encrypted_password" do
202 | subject.encrypted_password.should_not be_nil
203 | end
204 | end
205 | end
206 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Clearance [](http://travis-ci.org/thoughtbot/clearance?branch=master)
2 | =========
3 |
4 | Rails authentication & authorization with email & password.
5 |
6 | [We have clearance, Clarence.](http://www.youtube.com/watch?v=fVq4_HhBK8Y)
7 |
8 | Clearance was extracted out of [Airbrake](http://airbrakeapp.com/).
9 |
10 | Help
11 | ----
12 |
13 | * [Documentation](http://rdoc.info/gems/clearance) at RDoc.info.
14 | * [Patches and bugs](http://github.com/thoughtbot/clearance/issues) at Github Issues.
15 | * [Mailing list](http://groups.google.com/group/thoughtbot-clearance) at Google Groups.
16 |
17 | Installation
18 | ------------
19 |
20 | Clearance is a Rails engine for Rails 3. It is currently tested against Rails 3.0.12 and Rails 3.1.4.
21 |
22 | Include the gem in your Gemfile:
23 |
24 | gem "clearance"
25 |
26 | Make sure the development database exists, then run the generator:
27 |
28 | rails generate clearance:install
29 |
30 | This:
31 |
32 | * inserts Clearance::User into your User model
33 | * inserts Clearance::Authentication into your ApplicationController
34 | * creates a migration that either creates a users table or adds only missing columns
35 |
36 | Follow the instructions that are output from the generator.
37 |
38 | Use the [0.8.x](https://github.com/thoughtbot/clearance/tree/v0.8.8)
39 | series of Clearance if you have a Rails 2 app.
40 |
41 | Usage
42 | -----
43 |
44 | If you want to authorize users for a controller action, use the authorize
45 | method in a before_filter.
46 |
47 | class WidgetsController < ApplicationController
48 | before_filter :authorize
49 |
50 | def index
51 | @widgets = Widget.all
52 | end
53 | end
54 |
55 | If you want to reference the current user in a controller, view, or helper, use
56 | the current_user method.
57 |
58 | def index
59 | current_user.articles
60 | end
61 |
62 | If you want to know whether the current user is signed in or out, you can use
63 | these methods in controllers, views, or helpers:
64 |
65 | signed_in?
66 | signed_out?
67 |
68 | Typically, you want to have something like this in your app, maybe in a layout:
69 |
70 | <% if signed_in? %>
71 | <%= current_user.email %>
72 | <%= link_to "Sign out", sign_out_path, :method => :delete %>
73 | <% else %>
74 | <%= link_to "Sign in", sign_in_path %>
75 | <% end %>
76 |
77 | If you ever want to authenticate the user some place other than sessions/new,
78 | maybe in an API:
79 |
80 | User.authenticate("email@example.com", "password")
81 |
82 | Clearance will deliver one email on your app's behalf: when a user resets their password. Therefore, you should change the default email address that email comes from:
83 |
84 | # config/initializers/clearance.rb
85 | Clearance.configure do |config|
86 | config.mailer_sender = "me@example.com"
87 | end
88 |
89 | Rack
90 | ----
91 |
92 | Clearance adds its session to the Rack environment hash so middleware and other
93 | Rack applications can interact with it:
94 |
95 | class Bubblegum::Middleware
96 | def initialize(app)
97 | @app = app
98 | end
99 |
100 | def call(env)
101 | if env[:clearance].signed_in?
102 | env[:clearance].current_user.bubble_gum
103 | end
104 | @app.call(env)
105 | end
106 | end
107 |
108 |
109 | Overriding defaults
110 | -------------------
111 |
112 | Clearance is intended to be small, simple, well-tested, and easy to override defaults.
113 |
114 | Overriding routes
115 | -----------------
116 |
117 | See [config/routes.rb](https://github.com/thoughtbot/clearance/blob/master/config/routes.rb) for the default behavior.
118 |
119 | To override a Clearance route, redefine it:
120 |
121 | resource :session, :controller => 'sessions'
122 |
123 | Overriding controllers
124 | ----------------------
125 |
126 | See [app/controllers/clearance](https://github.com/thoughtbot/clearance/tree/master/app/controllers/clearance) for the default behavior.
127 |
128 | To override a Clearance controller, subclass it:
129 |
130 | class SessionsController < Clearance::SessionsController
131 | def new
132 | # my special new action
133 | end
134 |
135 | def url_after_create
136 | my_special_path
137 | end
138 | end
139 |
140 | You may want to override entire actions:
141 |
142 | def new
143 | end
144 |
145 | Or, you may want to override private methods that actions use:
146 |
147 | url_after_create
148 | url_after_update
149 | url_after_destroy
150 | flash_failure_after_create
151 | flash_failure_after_update
152 | flash_failure_when_forbidden
153 | forbid_missing_token
154 | forbid_non_existent_user
155 |
156 | Overriding translations
157 | -----------------------
158 |
159 | All flash messages and email subject lines are stored in [i18n translations](http://guides.rubyonrails.org/i18n.html). Override them like any other translation.
160 |
161 | Overriding views
162 | ----------------
163 |
164 | See [app/views](https://github.com/thoughtbot/clearance/tree/master/app/views) for the default behavior.
165 |
166 | To override those **views**, create them in your own `app/views` directory.
167 |
168 | There is a shortcut to copy all Clearance views into your app:
169 |
170 | rails generate clearance:views
171 |
172 | Overriding the model
173 | --------------------
174 |
175 | If you want to override the **model** behavior, you can include sub-modules of `Clearance::User`:
176 |
177 | extend Clearance::User::ClassMethods
178 | include Clearance::User::Validations
179 | include Clearance::User::Callbacks
180 |
181 | `ClassMethods` contains the `User.authenticate(email, password)` method.
182 |
183 | `Validations` contains validations for email and password.
184 |
185 | `Callbacks` contains `ActiveRecord` callbacks downcasing the email and generating a remember token.
186 |
187 | Overriding the password strategy
188 | --------------------------------
189 |
190 | By default, Clearance uses SHA1 encryption of the user's password. You can provide your own password strategy by creating a module that conforms to an API of two instance methods:
191 |
192 | def authenticated?
193 | end
194 |
195 | def encrypt_password
196 | end
197 |
198 | See [lib/clearance/password_strategies/sha1.rb](https://github.com/thoughtbot/clearance/blob/master/lib/clearance/password_strategies/sha1.rb) for the default behavior. Also see [lib/clearance/password_strategies/blowfish.rb](https://github.com/thoughtbot/clearance/blob/master/lib/clearance/password_strategies/blowfish.rb) for another password strategy. Switching password strategies will cause your existing users' passwords to not work.
199 |
200 | Once you have an API-compliant module, load it with:
201 |
202 | Clearance.configure do |config|
203 | config.password_strategy = MyPasswordStrategy
204 | end
205 |
206 | For example:
207 |
208 | # default
209 | config.password_strategy = Clearance::PasswordStrategies::SHA1
210 | # Blowfish
211 | config.password_strategy = Clearance::PasswordStrategies::Blowfish
212 |
213 |
214 | Optional Cucumber features
215 | --------------------------
216 |
217 | Clearance's Cucumber features are dependent on:
218 |
219 | * Cucumber
220 | * Capybara
221 | * RSpec
222 | * Factory Girl
223 |
224 | As your app evolves, you want to know that authentication still works. If you've
225 | installed [Cucumber](http://cukes.info) into your app:
226 |
227 | rails generate cucumber:install
228 |
229 | Then, you can use the Clearance features generator:
230 |
231 | rails generate clearance:features
232 |
233 | Edit your Gemfile to include:
234 |
235 | gem 'factory_girl_rails'
236 |
237 | Edit config/enviroments/test.rb to include the following:
238 |
239 | config.action_mailer.default_url_options = { :host => 'localhost:3000' }
240 |
241 | Then run your tests!
242 |
243 | rake
244 |
245 | Testing
246 | -------
247 |
248 | If you want to write Rails functional tests or controller specs with Clearance,
249 | you'll need to require the included test helpers and matchers.
250 |
251 | For example, in spec/support/clearance.rb or test/test_helper.rb:
252 |
253 | require 'clearance/testing'
254 |
255 | This will make Clearance::Authentication methods work in your controllers
256 | during functional tests and provide access to helper methods like:
257 |
258 | sign_in
259 | sign_in_as(user)
260 | sign_out
261 |
262 | And matchers like:
263 |
264 | deny_access
265 |
266 | Example:
267 |
268 | context "a visitor" do
269 | before { get :show }
270 | it { should deny_access }
271 | end
272 |
273 | context "a user" do
274 | before do
275 | sign_in
276 | get :show
277 | end
278 |
279 | it { should respond_with(:success) }
280 | end
281 |
282 | Contributing
283 | ------------
284 |
285 | Please see CONTRIBUTING.md for details.
286 |
287 | Credits
288 | -------
289 |
290 | 
291 |
292 | Clearance is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
293 |
294 | Thank you to all [the contributors](https://github.com/thoughtbot/clearance/contributors)!
295 |
296 | The names and logos for thoughtbot are trademarks of thoughtbot, inc.
297 |
298 | License
299 | -------
300 |
301 | Clearance is Copyright © 2009-2011 thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.
302 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | New for 0.16.2:
2 |
3 | * Change default email sender to deploy@example.com .
4 |
5 | New for 0.16.1:
6 |
7 | * Behave correctly when Rails whitelist attributes mass assignment
8 | protection is turned on
9 | * Fix for Rails 3.2.x modifying the HTTP cookie headers in rack requests
10 |
11 | New for 0.16.0:
12 |
13 | * Blowfish password encryption strategy (Chris Dillon)
14 |
15 | New for 0.15.0:
16 |
17 | * The User model can be swapped out using the Clearance.configure method.
18 | * Remove User::InstanceMethods to silence a Rails 3.2 deprecation warning.
19 | * Bump development dependency of cucumber-rails to 1.1.1.
20 |
21 | New for 0.14.0:
22 |
23 | * Support clearance session management from the Rack environment (Joe Ferris)
24 |
25 | New for 0.13.2:
26 |
27 | * Fixed the denies_access matcher (Chad Pytel, Joe Ferris)
28 |
29 | New for 0.13.0:
30 |
31 | * [#170] In Clearance's optional generated features, use pure Capybara instead of depending on Cucumber's removed web_steps, paths, and selectors. (Dan Croak)
32 | * [#167] Extract SHA-1-specific code out of `User` into `PasswordStrategies` module. (Vladimir Andrijevik)
33 | * [#164] Extract sign in form so that other methods can be added easily. (Subhash Chandra)
34 | * [#165] Test against Rails 3.1. (Dan Croak) Required upgrades to Diesel and Appraisal. (Dan Croak, Mike Burns, Chad Pytel)
35 | * [#160] Improved README documentation for overrides. (Dan Croak)
36 |
37 | New for 0.12.0:
38 |
39 | * [#129] Denying access redirects to root_url when signed in, sign_in_url when signed out. (Dan Croak)
40 | * Using flash :notice key everywhere now instead of :success and :failure. More in line with Rails conventions. (Dan Croak)
41 | * [#149] redirect_back_or on sign up. (Dan Croak)
42 | * [#147] Resetting password no longer redirects to sign in page. It displays a message telling them to look for an email. (Dan Croak)
43 | * Removed redundant flash messages. ("Signed in.", "Signed out.", and "You are now signed up.") (Dan Croak)
44 |
45 | New for 0.11.2:
46 |
47 | * Rails 3.1.rc compatible. (Prem Sichanugrist and Dan Croak)
48 | * Cucumber features no longer require password. (Dan Croak)
49 | * No more Clearance shoulda_macros. Instead providing RSpec- and Test::Unit-compliant test matchers (sign_in, sign_in_as, should deny_access, etc). (Dan Croak)
50 |
51 | New for 0.11.1:
52 |
53 | * [#146] Redirect to home page after sign up. (Dan Croak)
54 | * [#145] Remove dependency on dynamic_form. Replaced with flashes due to limited number of failure cases. (Dan Croak)
55 | * Moving ClearanceMailer to app/mailers. Moving spec to spec/mailers. (Dan Croak)
56 | * [#148] Removing :case_sensitive option from validates_uniqueness_of. It was unnecessary and causes a small performance problem on some apps. (Dan Croak)
57 | * Only development dependency in gemspec should be bundler. All others are derived by bundling. (Dan Croak)
58 |
59 | New for 0.11.0:
60 |
61 | * [#141] Removing password confirmation. (Dan Croak)
62 | * [#143] Use ActiveSupport::Concern and ActiveSupport::SecureRandom to clean up code. (Dan Croak)
63 | * New controller#authenticate(params) method. Redefine username & password or other styles of authentication. (Dan Croak)
64 | * before_filter :authenticate API replaced with more aptly-named before_filter :authorize. (Dan Croak)
65 |
66 | New for 0.10.5:
67 |
68 | * Closing CSRF hole for Rails >= 3.0.4 apps (Mack Earnhardt)
69 |
70 | New for 0.10.4:
71 |
72 | * Formtastic views generator removed. (Dan Croak)
73 | * Emails forced to be downcased (particularly for iPhone user case). (Adam Conrad)
74 | * Suite converted from test/unit to RSpec. (Joe Ferris)
75 | * [#135] Password reset requires a password. (Joel Meador)
76 | * [#138] Use HTML5 email fields. (Dan Croak)
77 |
78 | New for 0.10.3.2:
79 |
80 | * Fix gemspec to include all necessary files.
81 |
82 | New for 0.10.3.1:
83 |
84 | * Ensure everything within features inside any engine directory is included in the gemspec
85 |
86 | New for 0.10.3:
87 |
88 | * Include features/engines in gemspec file list so generator works as expected
89 |
90 | New for 0.10.2:
91 |
92 | * Replaced test/rails_root & general testing strategy with Diesel. (Joe Ferris)
93 | * Conveniences in factories for password/confirmation.
94 | * New generator command: rails generate clearance:install.
95 | * Step definitions are now prefixed with visitor_ to use thoughtbot convention. (Dan Croak)
96 | * When Clearance installed in an app that already has users, allow old users to sign in by resetting their password.
97 |
98 | New for 0.10.1:
99 |
100 | * replaced ActionController::Forbidden with a user-friendly flash message. (Dan Croak)
101 | * improved language of Cucumber steps by allowing a little more flexibility. (Dan Croak)
102 |
103 | New for 0.10.0:
104 |
105 | * Lots of README cleanup
106 | * Better email validation regex
107 | * Removed email confirmation step, was mostly a hassle and can always be added back in
108 | at the application level (instead of engine level) if necessary
109 | * Removed disable_with on forms since it does not allow IE users to submit forms. See more:
110 |
111 | https://github.com/rails/jquery-ujs/issues#issue/30
112 | http://bugs.jquery.com/ticket/7061
113 |
114 | New for 0.9.1:
115 |
116 | Forgot to update the changelog in a while, this is going to be brief:
117 |
118 | * This release supports Rails 3, capybara, and shoulda 2.10+.
119 |
120 | New for 0.8.9:
121 |
122 | * Removed unnecessary db index. (Rich Thornett, doctorzaius)
123 | * [#79] Allow customization of cookie duration. (Ron Newman, Dan Croak)
124 | * [#77] rake generator:cleanup needed to be... cleaned up. (Ron Newman)
125 |
126 | New for 0.8.8 (02/25/2010):
127 |
128 | * Fixed sign_in and sign_out not setting current_user (Joe Ferris)
129 |
130 | New for 0.8.7 (02/21/2010):
131 |
132 | * [#43] Fixed global sign out bug. (Ryan McGreary)
133 | * [#69] Allow Rails apps to before_filter :authenticate the entire app
134 | in ApplicationController and still have password recovery work without
135 | overriding any controllers. (Claudio Poli, Dan Croak)
136 | * [#72] #[21] Rails3 fix for ActionController/ActionDispatch change.
137 | (Joseph Holsten, Peter Haza, Dan Croak)
138 |
139 | New for 0.8.6 (02/17/2010):
140 |
141 | * Clearance features capitalization should match view text (Bobby Wilson)
142 | * [#39] skip :authenticate before_filter in controllers so apps can easily
143 | authenticate a whole site without subclassing (Matthew Ford)
144 | * [#45] Added randomness to token and salt generation (Ryan McGeary)
145 | * [#43] Reset the remember_token on sign out instead of sign in. Allows for the same
146 | user to sign in from two locations at once. (Ryan McGeary)
147 | * [#62] Append the version number to generated update migrations (Joe Ferris)
148 | * Allow overridden user models to skip email/password validations
149 | conditionally. This makes username/facebook integration easier. (Joe Ferris)
150 |
151 | New for 0.8.5 (01/20/2010):
152 |
153 | * replaced routing hack with Clearance::Routes.draw(map) to give
154 | more control to the application developer. (Dan Croak)
155 | * removed attr_accessible from Clearance::User. (Dan Croak)
156 | * fixed bug in password reset feature. (Ben Orenstein, Dan Croak)
157 | * use Jeweler for gemming. (Dan Croak)
158 | * remove dependency on root_path, use '/' instead. (Dan Croak)
159 | * use Clearance.configure block to set mailer sender instead of
160 | DO_NOT_REPLY constant. (Dan Croak)
161 |
162 | New for 0.8.4 (12/08/2009):
163 |
164 | * [#48] remove unnecessary require 'factory_girl' in generator (Dan Croak)
165 | * reference gemcutter (not github) as the gem source in README (Dan Croak)
166 | * add IRC, rdoc.info links to README (Dan Croak)
167 | * move user confirmation email trigger into model (Chad Pytel)
168 |
169 | New for 0.8.3 (09/21/2009):
170 |
171 | * [#27] remove class_eval in Clearance::Authentication. (Anuj Dutta)
172 | * Avoid possible collisions in the remember me token (Joe Ferris)
173 |
174 | New for 0.8.2 (09/01/2009):
175 |
176 | * current_user= accessor method. (Joe Ferris, Josh Clayton)
177 | * set current_user in sign_in. (Jon Yurek)
178 |
179 | New for 0.8.1 (08/31/2009):
180 |
181 | * Removed unnecessary remember_token_expires_at column and the
182 | remember? and forget_me! user instance methods. (Dan Croak)
183 |
184 | New for 0.8.0 (08/31/2009):
185 |
186 | * Always remember me. Replaced session-and-remember-me authentication with
187 | always using a cookie with a long timeout. (Dan Croak)
188 | * Documented Clearance::Authentication with YARD. (Dan Croak)
189 | * Documented Clearance::User with YARD. (Dan Croak)
190 |
191 | New for 0.7.0 (08/04/2009):
192 |
193 | * Redirect signed in user who clicks confirmation link again. (Dan Croak)
194 | * Redirect signed out user who clicks confirmation link again. (Dan Croak)
195 | * Added signed_out? convenience method for controllers, helpers, views. (Dan
196 | Croak)
197 | * Added clearance_views generator. By default, creates formtastic views which
198 | pass all tests and features. (Dan Croak)
199 |
200 | New for 0.6.9 (07/04/2009):
201 |
202 | * Added timestamps to create users migration. (Dan Croak)
203 | * Ready for Ruby 1.9. (Jason Morrison, Nick Quaranto)
204 |
205 | New for 0.6.8 (06/24/2009):
206 |
207 | * Added defined? checks for various Rails constants such as ActionController
208 | for easier unit testing of Clearance extensions... particularly ActiveRecord
209 | extensions... particularly strong_password. (Dan Croak)
210 |
211 | New for 0.6.7 (06/13/2009):
212 |
213 | * [#30] Added sign_up, sign_in, sign_out named routes. (Dan Croak)
214 | * [#22] Minimizing Reek smell: Duplication in redirect_back_or. (Dan Croak)
215 | * Deprecated sign_user_in. Told developers to use sign_in instead. (Dan
216 | Croak)
217 | * [#16] flash_success_after_create, flash_notice_after_create, flash_failure_after_create, flash_sucess_after_update, flash_success_after_destroy, etc. (Dan Croak)
218 | * [#17] bug. added #create to forbidden before_filters on confirmations controller. (Dan Croak)
219 | * [#24] should_be_signed_in_as shouldn't look in the session. (Dan Croak)
220 | * README improvements. (Dan Croak)
221 | * Move routes loading to separate file. (Joshua Clayton)
222 |
223 | New for 0.6.6 (05/18/2009):
224 |
225 | * [#14] replaced class_eval in Clearance::User with modules. This was needed
226 | in a thoughtbot client app so we could write our own validations. (Dan Croak)
227 |
228 | New for 0.6.5 (05/17/2009):
229 |
230 | * [#6] Make Clearance i18n aware. (Timur Vafin, Marcel Goerner, Eugene Bolshakov, Dan Croak)
231 |
232 | New for 0.6.4 (05/12/2009):
233 |
234 | * Moved issue tracking to Github from Lighthouse. (Dan Croak)
235 | * [#7] asking higher-level questions of controllers in webrat steps, such as signed_in? instead of what's in the session. same for accessors. (Dan Croak)
236 | * [#11] replacing sign_in_as & sign_out shoulda macros with a stubbing (requires no dependency) approach. this will avoid dealing with the internals of current_user, such as session & cookies. added sign_in macro which signs in an email confirmed user from clearance's factories. (Dan Croak)
237 | * [#13] move private methods on sessions controller into Clearance::Authentication module (Dan Croak)
238 | * [#9] audited flash keys. (Dan Croak)
239 |
240 | New for 0.6.3 (04/23/2009):
241 |
242 | * Scoping ClearanceMailer properly within controllers so it works in production environments. (Nick Quaranto)
243 |
244 | New for 0.6.2 (04/22/2009):
245 |
246 | * Insert Clearance::User into User model if it exists. (Nick Quaranto)
247 | * World(NavigationHelpers) Cucumber 3.0 style. (Shay Arnett & Mark Cornick)
248 |
249 | New for 0.6.1 (04/21/2009):
250 |
251 | * Scope operators are necessary to keep Rails happy. Reverting the original
252 | revert so they're back in the library now for constants referenced inside of
253 | the gem. (Nick Quaranto)
254 |
255 | New for 0.6.0 (04/21/2009):
256 |
257 | * Converted Clearance to a Rails engine. (Dan Croak & Joe Ferris)
258 | * Include Clearance::User in User model in app. (Dan Croak & Joe Ferris)
259 | * Include Clearance::Authentication in ApplicationController. (Dan Croak & Joe Ferris)
260 | * Namespace controllers under Clearance. (Dan Croak & Joe Ferris)
261 | * Routes move to engine, use namespaced controllers but publicly the same. (Dan Croak & Joe Ferris)
262 | * If you want to override a controller, subclass it like SessionsController <
263 | Clearance::SessionsController. This gives you access to usual hooks such as
264 | url_after_create. (Dan Croak & Joe Ferris)
265 | * Controllers, mailer, model, routes all unit tested inside engine. Use
266 | script/generate clearance_features to test integration of Clearance with your
267 | Rails app. No longer including modules in your app's test files. (Dan Croak & Joe Ferris)
268 | * Moved views to engine. (Joe Ferris)
269 | * Converted generated test/factories/clearance.rb to use inheritence for
270 | email_confirmed_user. (Dan Croak)
271 | * Corrected some spelling errors with methods (Nick Quaranto)
272 | * Converted "I should see error messages" to use a regex in the features (Nick
273 | Quaranto)
274 | * Loading clearance routes after rails routes via some monkeypatching (Nick
275 | Quaranto)
276 | * Made the clearance controllers unloadable to stop constant loading errors in
277 | development mode (Nick Quaranto)
278 |
279 | New for 0.5.6 (4/11/2009):
280 |
281 | * [#57] Step definition changed for "User should see error messages" so
282 | features won't fail for certain validations. (Nick Quaranto)
283 |
284 | New for 0.5.5 (3/23/2009):
285 |
286 | * Removing duplicate test to get rid of warning. (Nick Quaranto)
287 |
288 | New for 0.5.4 (3/21/2009):
289 |
290 | * When users fail logging in, redirect them instead of rendering. (Matt
291 | Jankowski)
292 |
293 | New for 0.5.3 (3/5/2009):
294 |
295 | * Clearance now works with (and requires) Shoulda 2.10.0. (Mark Cornick, Joe
296 | Ferris, Dan Croak)
297 | * Prefer flat over nested contexts in sessions_controller_test. (Joe Ferris,
298 | Dan Croak)
299 |
300 | New for 0.5.2 (3/2/2009):
301 |
302 | * Fixed last remaining errors in Rails 2.3 tests. Now fully compatible. (Joe
303 | Ferris, Dan Croak)
304 |
305 | New for 0.5.1 (2/27/2009):
306 |
307 | * [#46] A user with unconfirmed email who resets password now confirms email.
308 | (Marcel Görner)
309 | * Refactored user_from_cookie, user_from_session, User#authenticate to use
310 | more direct return code instead of ugly, harder to read ternary. (Dan Croak)
311 | * Switch order of cookies and sessions to take advantage of Rails 2.3's "Rack-based lazy-loaded sessions":http://is.gd/i23E. (Dan Croak)
312 | * Altered generator to interact with application_controller.rb instead of
313 | application.rb in Rails 2.3 apps. (Dan Croak)
314 | * [#42] Bug fix. Rack-based session change altered how to test remember me
315 | cookie. (Mihai Anca)
316 |
317 | New for 0.5.0 (2/27/2009):
318 |
319 | * Fixed problem with Cucumber features. (Dan Croak)
320 | * Fixed mising HTTP fluency use case. (Dan Croak)
321 | * Refactored User#update_password to take just parameters it needs. (Dan
322 | Croak)
323 | * Refactored User unit tests to be more readable. (Dan Croak)
324 |
325 | New for 0.4.9 (2/20/2009):
326 |
327 | * Protect passwords & confirmations actions with forbidden filters. (Dan Croak)
328 | * Return 403 Forbidden status code in those cases. (Tim Pope)
329 | * Test 403 Forbidden status code in Cucumber feature. (Dan Croak, Joe Ferris)
330 | * Raise custom ActionController::Forbidden error internally. (Joe Ferris, Mike Burns, Jason Morrison)
331 | * Test ActionController::Forbidden error is raised in functional test. (Joe Ferris, Mike Burns, Dan Croak)
332 | * [#45] Fixed bug that allowed anyone to edit another user's password (Marcel Görner)
333 | * Required Factory Girl >= 1.2.0. (Dan Croak)
334 |
335 | New for 0.4.8 (2/16/2009):
336 |
337 | * Added support paths for Cucumber. (Ben Mabey)
338 | * Added documentation for the flash. (Ben Mabey)
339 | * Generators require "test_helper" instead of File.join. for rr compatibility. (Joe Ferris)
340 | * Removed interpolated email address from flash message to make i18n easier. (Bence Nagy)
341 | * Standardized flash messages that refer to email delivery. (Dan Croak)
342 |
343 | New for 0.4.7 (2/12/2009):
344 |
345 | * Removed Clearance::Test::TestHelper so there is one less setup step. (Dan Croak)
346 | * All test helpers now in shoulda_macros. (Dan Croak)
347 |
348 | New for 0.4.6 (2/11/2009):
349 |
350 | * Made the modules behave like mixins again. (hat-tip Eloy Duran)
351 | * Created Actions and PrivateMethods modules on controllers for future RDoc reasons. (Dan Croak, Joe Ferris)
352 |
353 | New for 0.4.5 (2/9/2009):
354 |
355 | * [#43] Removed email downcasing because local-part is case sensitive per RFC5321. (Dan Croak)
356 | * [#42] Removed dependency on Mocha. (Dan Croak)
357 | * Required Shoulda >= 2.9.1. (Dan Croak)
358 | * Added password reset feature to clearance_features generator. (Eugene Bolshakov, Dan Croak)
359 | * Removed unnecessary session[:salt]. (Dan Croak)
360 | * [#41] Only store location for session[:return_to] for GET requests. (Dan Croak)
361 | * Audited "sign up" naming convention. "Register" had slipped in a few places. (Dan Croak)
362 | * Switched to SHA1 encryption. Cypher doesn't matter much for email confirmation, password reset. Better to have shorter hashes in the emails for clients who line break on 72 chars. (Dan Croak)
363 |
364 | New for 0.4.4 (2/2/2009):
365 |
366 | * Added a generator for Cucumber features. (Joe Ferris, Dan Croak)
367 | * Standarized naming for "Sign up," "Sign in," and "Sign out". (Dan Croak)
368 |
--------------------------------------------------------------------------------