├── test ├── rails_app │ ├── public │ │ ├── favicon.ico │ │ ├── 422.html │ │ ├── 404.html │ │ └── 500.html │ ├── app │ │ ├── views │ │ │ ├── home │ │ │ │ ├── join.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ ├── private.html.erb │ │ │ │ ├── user_dashboard.html.erb │ │ │ │ └── admin_dashboard.html.erb │ │ │ ├── admins │ │ │ │ ├── index.html.erb │ │ │ │ └── sessions │ │ │ │ │ └── new.html.erb │ │ │ ├── users │ │ │ │ ├── sessions │ │ │ │ │ └── new.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── mailer │ │ │ │ │ └── confirmation_instructions.erb │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ ├── active_record │ │ │ ├── shim.rb │ │ │ ├── admin.rb │ │ │ └── user.rb │ │ ├── controllers │ │ │ ├── publisher │ │ │ │ ├── sessions_controller.rb │ │ │ │ └── registrations_controller.rb │ │ │ ├── admins │ │ │ │ └── sessions_controller.rb │ │ │ ├── admins_controller.rb │ │ │ ├── application_controller.rb │ │ │ ├── home_controller.rb │ │ │ ├── users │ │ │ │ └── omniauth_callbacks_controller.rb │ │ │ └── users_controller.rb │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── mailers │ │ │ └── users │ │ │ │ └── mailer.rb │ │ └── mongoid │ │ │ ├── shim.rb │ │ │ ├── admin.rb │ │ │ └── user.rb │ ├── config │ │ ├── initializers │ │ │ ├── inflections.rb │ │ │ ├── secret_token.rb │ │ │ └── backtrace_silencers.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── application.rb │ │ └── routes.rb │ ├── config.ru │ ├── Rakefile │ ├── script │ │ └── rails │ ├── lib │ │ ├── shared_admin.rb │ │ └── shared_user.rb │ └── db │ │ ├── migrate │ │ └── 20100401102949_create_tables.rb │ │ └── schema.rb ├── support │ ├── locale │ │ └── en.yml │ ├── webrat │ │ └── integrations │ │ │ └── rails.rb │ ├── assertions.rb │ ├── helpers.rb │ └── integration.rb ├── models │ ├── omniauthable_test.rb │ ├── registerable_test.rb │ ├── trackable_test.rb │ ├── authenticatable_test.rb │ ├── timeoutable_test.rb │ ├── serializable_test.rb │ ├── token_authenticatable_test.rb │ └── validatable_test.rb ├── orm │ ├── mongoid.rb │ └── active_record.rb ├── generators │ ├── install_generator_test.rb │ ├── mongoid_generator_test.rb │ ├── devise_generator_test.rb │ ├── views_generator_test.rb │ └── active_record_generator_test.rb ├── delegator_test.rb ├── test_helper.rb ├── test_models.rb ├── helpers │ └── devise_helper_test.rb ├── omniauth │ ├── url_helpers_test.rb │ └── config_test.rb ├── controllers │ ├── custom_strategy_test.rb │ ├── url_helpers_test.rb │ ├── sessions_controller_test.rb │ └── internal_helpers_test.rb ├── mailers │ ├── unlock_instructions_test.rb │ ├── reset_password_instructions_test.rb │ └── confirmation_instructions_test.rb ├── integration │ ├── trackable_test.rb │ ├── database_authenticatable_test.rb │ ├── timeoutable_test.rb │ └── omniauthable_test.rb ├── devise_test.rb └── test_helpers_test.rb ├── devise.png ├── lib ├── devise │ ├── version.rb │ ├── orm │ │ ├── active_record.rb │ │ └── mongoid.rb │ ├── hooks │ │ ├── rememberable.rb │ │ ├── lockable.rb │ │ ├── forgetable.rb │ │ ├── trackable.rb │ │ ├── activatable.rb │ │ └── timeoutable.rb │ ├── rails │ │ └── warden_compat.rb │ ├── time_inflector.rb │ ├── controllers │ │ ├── scoped_views.rb │ │ ├── rememberable.rb │ │ └── url_helpers.rb │ ├── delegator.rb │ ├── omniauth │ │ ├── url_helpers.rb │ │ └── config.rb │ ├── strategies │ │ ├── base.rb │ │ ├── database_authenticatable.rb │ │ ├── rememberable.rb │ │ └── token_authenticatable.rb │ ├── models │ │ ├── omniauthable.rb │ │ ├── registerable.rb │ │ ├── timeoutable.rb │ │ ├── trackable.rb │ │ ├── validatable.rb │ │ └── token_authenticatable.rb │ ├── omniauth.rb │ ├── param_filter.rb │ ├── modules.rb │ ├── rails.rb │ └── mailers │ │ └── helpers.rb └── generators │ ├── templates │ ├── markerb │ │ ├── confirmation_instructions.markerb │ │ ├── unlock_instructions.markerb │ │ └── reset_password_instructions.markerb │ ├── simple_form_for │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── registrations │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ └── confirmations │ │ │ └── new.html.erb │ └── README │ ├── devise │ ├── install_generator.rb │ ├── devise_generator.rb │ ├── orm_helpers.rb │ └── views_generator.rb │ ├── active_record │ ├── templates │ │ ├── migration.rb │ │ └── migration_existing.rb │ └── devise_generator.rb │ └── mongoid │ └── devise_generator.rb ├── .gitignore ├── app ├── views │ └── devise │ │ ├── _links.erb │ │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── unlock_instructions.html.erb │ │ └── reset_password_instructions.html.erb │ │ ├── unlocks │ │ └── new.html.erb │ │ ├── passwords │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ ├── confirmations │ │ └── new.html.erb │ │ ├── sessions │ │ └── new.html.erb │ │ ├── registrations │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ └── shared │ │ └── _links.erb ├── mailers │ └── devise │ │ └── mailer.rb ├── helpers │ └── devise_helper.rb └── controllers │ └── devise │ ├── omniauth_callbacks_controller.rb │ ├── unlocks_controller.rb │ ├── confirmations_controller.rb │ ├── sessions_controller.rb │ ├── passwords_controller.rb │ └── registrations_controller.rb ├── Gemfile ├── gemfiles ├── Gemfile.rails-3.1.x └── Gemfile.rails-3.1.x.lock ├── .travis.yml ├── devise.gemspec ├── CONTRIBUTING.md ├── Rakefile ├── MIT-LICENSE ├── config └── locales │ └── en.yml └── Gemfile.lock /test/rails_app/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/rails_app/app/views/home/join.html.erb: -------------------------------------------------------------------------------- 1 | Join -------------------------------------------------------------------------------- /test/rails_app/app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 | Home! -------------------------------------------------------------------------------- /test/rails_app/app/active_record/shim.rb: -------------------------------------------------------------------------------- 1 | module Shim 2 | end -------------------------------------------------------------------------------- /test/rails_app/app/views/home/private.html.erb: -------------------------------------------------------------------------------- 1 | Private! 2 | -------------------------------------------------------------------------------- /test/rails_app/app/views/admins/index.html.erb: -------------------------------------------------------------------------------- 1 | Welcome Admin! 2 | -------------------------------------------------------------------------------- /test/rails_app/app/views/home/user_dashboard.html.erb: -------------------------------------------------------------------------------- 1 | User dashboard -------------------------------------------------------------------------------- /devise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snitko/devise/master/devise.png -------------------------------------------------------------------------------- /test/rails_app/app/views/home/admin_dashboard.html.erb: -------------------------------------------------------------------------------- 1 | Admin dashboard -------------------------------------------------------------------------------- /lib/devise/version.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | VERSION = "2.2.3".freeze 3 | end 4 | -------------------------------------------------------------------------------- /test/rails_app/app/views/users/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | Special user view 2 | -------------------------------------------------------------------------------- /test/rails_app/app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 | Welcome User #<%= current_user.id %>! 2 | -------------------------------------------------------------------------------- /test/rails_app/app/views/users/mailer/confirmation_instructions.erb: -------------------------------------------------------------------------------- 1 | <%= @resource.email %> -------------------------------------------------------------------------------- /test/support/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | messages: 4 | taken: "has already been taken" 5 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport::Inflector.inflections do |inflect| 2 | end 3 | -------------------------------------------------------------------------------- /lib/devise/orm/active_record.rb: -------------------------------------------------------------------------------- 1 | require 'orm_adapter/adapters/active_record' 2 | 3 | ActiveRecord::Base.extend Devise::Models -------------------------------------------------------------------------------- /test/rails_app/app/controllers/publisher/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Publisher::SessionsController < ApplicationController 2 | end -------------------------------------------------------------------------------- /test/rails_app/app/views/admins/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | Welcome to "sessions/new" view! 2 | <%= render :file => "devise/sessions/new" %> -------------------------------------------------------------------------------- /lib/devise/orm/mongoid.rb: -------------------------------------------------------------------------------- 1 | require 'orm_adapter/adapters/mongoid' 2 | 3 | Mongoid::Document::ClassMethods.send :include, Devise::Models -------------------------------------------------------------------------------- /test/rails_app/app/controllers/publisher/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class Publisher::RegistrationsController < ApplicationController 2 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/rails_app/log/* 2 | test/rails_app/tmp/* 3 | *~ 4 | coverage/* 5 | *.sqlite3 6 | .bundle 7 | rdoc/* 8 | pkg 9 | log 10 | test/tmp/* 11 | -------------------------------------------------------------------------------- /test/rails_app/app/active_record/admin.rb: -------------------------------------------------------------------------------- 1 | require 'shared_admin' 2 | 3 | class Admin < ActiveRecord::Base 4 | include Shim 5 | include SharedAdmin 6 | end 7 | -------------------------------------------------------------------------------- /test/rails_app/app/active_record/user.rb: -------------------------------------------------------------------------------- 1 | require 'shared_user' 2 | 3 | class User < ActiveRecord::Base 4 | include Shim 5 | include SharedUser 6 | end 7 | -------------------------------------------------------------------------------- /test/rails_app/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Methods added to this helper will be available to all templates in the application. 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /test/rails_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run RailsApp::Application 5 | -------------------------------------------------------------------------------- /test/rails_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | RailsApp::Application.initialize! 6 | -------------------------------------------------------------------------------- /app/views/devise/_links.erb: -------------------------------------------------------------------------------- 1 | <% ActiveSupport::Deprecation.warn "Rendering partials devise/_links.erb is deprecated" \ 2 | "please use devise/shared/_links.erb instead."%> 3 | <%= render "shared/links" %> 4 | -------------------------------------------------------------------------------- /test/rails_app/app/controllers/admins/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Admins::SessionsController < Devise::SessionsController 2 | def new 3 | flash[:special] = "Welcome to #{controller_path.inspect} controller!" 4 | super 5 | end 6 | end -------------------------------------------------------------------------------- /test/rails_app/config/boot.rb: -------------------------------------------------------------------------------- 1 | unless defined?(DEVISE_ORM) 2 | DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym 3 | end 4 | 5 | require 'rubygems' 6 | require 'bundler/setup' 7 | 8 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /lib/generators/templates/markerb/confirmation_instructions.markerb: -------------------------------------------------------------------------------- 1 | Welcome <%= @email %>! 2 | 3 | You can confirm your account through the link below: 4 | 5 | <%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %> 6 | -------------------------------------------------------------------------------- /test/models/omniauthable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OmniauthableTest < ActiveSupport::TestCase 4 | test 'required_fields should contain the fields that Devise uses' do 5 | assert_same_content Devise::Models::Omniauthable.required_fields(User), [] 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/registerable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegisterableTest < ActiveSupport::TestCase 4 | test 'required_fields should contain the fields that Devise uses' do 5 | assert_same_content Devise::Models::Registerable.required_fields(User), [] 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.secret_token = 'ea942c41850d502f2c8283e26bdc57829f471bb18224ddff0a192c4f32cdf6cb5aa0d82b3a7a7adbeb640c4b06f3aa1cd5f098162d8240f669b39d6b49680571' 2 | Rails.application.config.session_store :cookie_store, :key => "_my_app" -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>

6 | -------------------------------------------------------------------------------- /test/rails_app/app/mailers/users/mailer.rb: -------------------------------------------------------------------------------- 1 | class Users::Mailer < Devise::Mailer 2 | default :from => 'custom@example.com' 3 | end 4 | 5 | class Users::ReplyToMailer < Devise::Mailer 6 | default :from => 'custom@example.com' 7 | default :reply_to => 'custom_reply_to@example.com' 8 | end -------------------------------------------------------------------------------- /test/orm/mongoid.rb: -------------------------------------------------------------------------------- 1 | require 'mongoid/version' 2 | 3 | Mongoid.configure do |config| 4 | config.connect_to("devise-test-suite") 5 | config.use_utc = true 6 | config.include_root_in_json = true 7 | end 8 | 9 | class ActiveSupport::TestCase 10 | setup do 11 | Mongoid.purge! 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/devise/hooks/rememberable.rb: -------------------------------------------------------------------------------- 1 | Warden::Manager.after_set_user :except => :fetch do |record, warden, options| 2 | scope = options[:scope] 3 | if record.respond_to?(:remember_me) && record.remember_me && warden.authenticated?(scope) 4 | Devise::Controllers::Rememberable::Proxy.new(warden).remember_me(record) 5 | end 6 | end -------------------------------------------------------------------------------- /test/rails_app/app/controllers/admins_controller.rb: -------------------------------------------------------------------------------- 1 | class AdminsController < ApplicationController 2 | before_filter :authenticate_admin! 3 | 4 | def index 5 | end 6 | 7 | def expire 8 | admin_session['last_request_at'] = 31.minutes.ago.utc 9 | render :text => 'Admin will be expired on next request' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/devise/rails/warden_compat.rb: -------------------------------------------------------------------------------- 1 | module Warden::Mixins::Common 2 | def request 3 | @request ||= ActionDispatch::Request.new(env) 4 | end 5 | 6 | # This is called internally by Warden on logout 7 | def reset_session! 8 | request.reset_session 9 | end 10 | 11 | def cookies 12 | request.cookie_jar 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/templates/markerb/unlock_instructions.markerb: -------------------------------------------------------------------------------- 1 | Hello <%= @resource.email %>! 2 | 3 | Your account has been locked due to an excessive number of unsuccessful sign in attempts. 4 | 5 | Click the link below to unlock your account: 6 | 7 | <%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %> 8 | -------------------------------------------------------------------------------- /lib/devise/time_inflector.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/module/delegation" 2 | 3 | module Devise 4 | class TimeInflector 5 | include ActionView::Helpers::DateHelper 6 | 7 | class << self 8 | attr_reader :instance 9 | delegate :time_ago_in_words, :to => :instance 10 | end 11 | 12 | @instance = new 13 | end 14 | end -------------------------------------------------------------------------------- /test/orm/active_record.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Migration.verbose = false 2 | ActiveRecord::Base.logger = Logger.new(nil) 3 | 4 | ActiveRecord::Migrator.migrate(File.expand_path("../../rails_app/db/migrate/", __FILE__)) 5 | 6 | class ActiveSupport::TestCase 7 | self.use_transactional_fixtures = true 8 | self.use_instantiated_fixtures = false 9 | end 10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>

8 | -------------------------------------------------------------------------------- /test/rails_app/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | require 'rake' 7 | require 'rake/testtask' 8 | require 'rake/rdoctask' 9 | 10 | Rails.application.load_tasks 11 | -------------------------------------------------------------------------------- /lib/devise/hooks/lockable.rb: -------------------------------------------------------------------------------- 1 | # After each sign in, if resource responds to failed_attempts, sets it to 0 2 | # This is only triggered when the user is explicitly set (with set_user) 3 | Warden::Manager.after_set_user :except => :fetch do |record, warden, options| 4 | if record.respond_to?(:failed_attempts) && warden.authenticated?(options[:scope]) 5 | record.update_attribute(:failed_attempts, 0) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/trackable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TrackableTest < ActiveSupport::TestCase 4 | test 'required_fields should contain the fields that Devise uses' do 5 | assert_same_content Devise::Models::Trackable.required_fields(User), [ 6 | :current_sign_in_at, 7 | :current_sign_in_ip, 8 | :last_sign_in_at, 9 | :last_sign_in_ip, 10 | :sign_in_count 11 | ] 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/devise/controllers/scoped_views.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Controllers 3 | module ScopedViews 4 | extend ActiveSupport::Concern 5 | 6 | module ClassMethods 7 | def scoped_views? 8 | defined?(@scoped_views) ? @scoped_views : Devise.scoped_views 9 | end 10 | 11 | def scoped_views=(value) 12 | @scoped_views = value 13 | end 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
<%= f.label :email %>
7 | <%= f.email_field :email, :autofocus => true %>
8 | 9 |
<%= f.submit "Resend unlock instructions" %>
10 | <% end %> 11 | 12 | <%= render "devise/shared/links" %> 13 | -------------------------------------------------------------------------------- /test/rails_app/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /lib/generators/templates/markerb/reset_password_instructions.markerb: -------------------------------------------------------------------------------- 1 | Hello <%= @resource.email %>! 2 | 3 | Someone has requested a link to change your password, and you can do this through the link below. 4 | 5 | <%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %> 6 | 7 | If you didn't request this, please ignore this email. 8 | Your password won't change until you access the link above and create a new one. 9 | -------------------------------------------------------------------------------- /test/generators/install_generator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class InstallGeneratorTest < Rails::Generators::TestCase 4 | tests Devise::Generators::InstallGenerator 5 | destination File.expand_path("../../tmp", __FILE__) 6 | setup :prepare_destination 7 | 8 | test "Assert all files are properly created" do 9 | run_generator 10 | assert_file "config/initializers/devise.rb" 11 | assert_file "config/locales/devise.en.yml" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
<%= f.label :email %>
7 | <%= f.email_field :email, :autofocus => true %>
8 | 9 |
<%= f.submit "Send me reset password instructions" %>
10 | <% end %> 11 | 12 | <%= render "devise/shared/links" %> 13 | -------------------------------------------------------------------------------- /lib/devise/delegator.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | # Checks the scope in the given environment and returns the associated failure app. 3 | class Delegator 4 | def call(env) 5 | failure_app(env).call(env) 6 | end 7 | 8 | def failure_app(env) 9 | app = env["warden.options"] && 10 | (scope = env["warden.options"][:scope]) && 11 | Devise.mappings[scope.to_sym].failure_app 12 | 13 | app || Devise::FailureApp 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/rails_app/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | 4 | class ApplicationController < ActionController::Base 5 | protect_from_forgery 6 | before_filter :current_user, :unless => :devise_controller? 7 | before_filter :authenticate_user!, :if => :devise_controller? 8 | respond_to *Mime::SET.map(&:to_sym) 9 | end 10 | -------------------------------------------------------------------------------- /test/rails_app/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | 5 | def private 6 | end 7 | 8 | def user_dashboard 9 | end 10 | 11 | def admin_dashboard 12 | end 13 | 14 | def join 15 | end 16 | 17 | def set 18 | session["devise.foo_bar"] = "something" 19 | head :ok 20 | end 21 | 22 | def unauthenticated 23 | render :text => "unauthenticated", :status => :unauthorized 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 | def facebook 3 | data = env["omniauth.auth"] 4 | session["devise.facebook_data"] = data["extra"]["user_hash"] 5 | render :json => data 6 | end 7 | 8 | def sign_in_facebook 9 | user = User.find_by_email('user@test.com') 10 | user.remember_me = true 11 | sign_in user 12 | render :text => "" 13 | end 14 | end -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
<%= f.label :email %>
7 | <%= f.email_field :email, :autofocus => true %>
8 | 9 |
<%= f.submit "Resend confirmation instructions" %>
10 | <% end %> 11 | 12 | <%= render "devise/shared/links" %> 13 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /test/rails_app/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | ENV_PATH = File.expand_path('../../config/environment', __FILE__) 5 | BOOT_PATH = File.expand_path('../../config/boot', __FILE__) 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | ROOT_PATH = File.expand_path('../..', __FILE__) 8 | 9 | require BOOT_PATH 10 | require 'rails/commands' 11 | -------------------------------------------------------------------------------- /app/mailers/devise/mailer.rb: -------------------------------------------------------------------------------- 1 | class Devise::Mailer < Devise.parent_mailer.constantize 2 | include Devise::Mailers::Helpers 3 | 4 | def confirmation_instructions(record, opts={}) 5 | devise_mail(record, :confirmation_instructions, opts) 6 | end 7 | 8 | def reset_password_instructions(record, opts={}) 9 | devise_mail(record, :reset_password_instructions, opts) 10 | end 11 | 12 | def unlock_instructions(record, opts={}) 13 | devise_mail(record, :unlock_instructions, opts) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/devise/hooks/forgetable.rb: -------------------------------------------------------------------------------- 1 | # Before logout hook to forget the user in the given scope, if it responds 2 | # to forget_me! Also clear remember token to ensure the user won't be 3 | # remembered again. Notice that we forget the user unless the record is not persisted. 4 | # This avoids forgetting deleted users. 5 | Warden::Manager.before_logout do |record, warden, options| 6 | if record.respond_to?(:forget_me!) 7 | Devise::Controllers::Rememberable::Proxy.new(warden).forget_me(record) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/devise/hooks/trackable.rb: -------------------------------------------------------------------------------- 1 | # After each sign in, update sign in time, sign in count and sign in IP. 2 | # This is only triggered when the user is explicitly set (with set_user) 3 | # and on authentication. Retrieving the user from session (:fetch) does 4 | # not trigger it. 5 | Warden::Manager.after_set_user :except => :fetch do |record, warden, options| 6 | if record.respond_to?(:update_tracked_fields!) && warden.authenticated?(options[:scope]) && !warden.request.env['devise.skip_trackable'] 7 | record.update_tracked_fields!(warden.request) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, :required => true, :autofocus => true %> 8 |
9 | 10 |
11 | <%= f.button :submit, "Send me reset password instructions" %> 12 |
13 | <% end %> 14 | 15 | <%= render "devise/shared/links" %> 16 | -------------------------------------------------------------------------------- /test/rails_app/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: 13 | adapter: sqlite3 14 | database: ":memory:" 15 | 16 | production: 17 | adapter: sqlite3 18 | database: ":memory:" 19 | -------------------------------------------------------------------------------- /test/rails_app/lib/shared_admin.rb: -------------------------------------------------------------------------------- 1 | module SharedAdmin 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | devise :database_authenticatable, :registerable, 6 | :timeoutable, :recoverable, :lockable, :confirmable, 7 | :unlock_strategy => :time, :lock_strategy => :none, 8 | :allow_unconfirmed_access_for => 2.weeks, :reconfirmable => true 9 | 10 | validates_length_of :reset_password_token, :minimum => 3, :allow_blank => true 11 | validates_uniqueness_of :email, :allow_blank => true, :if => :email_changed? 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign in

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> 4 |
5 | <%= f.input :email, :required => false, :autofocus => true %> 6 | <%= f.input :password, :required => false %> 7 | <%= f.input :remember_me, :as => :boolean if devise_mapping.rememberable? %> 8 |
9 | 10 |
11 | <%= f.button :submit, "Sign in" %> 12 |
13 | <% end %> 14 | 15 | <%= render "devise/shared/links" %> 16 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign in

2 | 3 | <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> 4 |
<%= f.label :email %>
5 | <%= f.email_field :email, :autofocus => true %>
6 | 7 |
<%= f.label :password %>
8 | <%= f.password_field :password %>
9 | 10 | <% if devise_mapping.rememberable? -%> 11 |
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
12 | <% end -%> 13 | 14 |
<%= f.submit "Sign in" %>
15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :unlock_token %> 6 | 7 |
8 | <%= f.input :email, :required => true, :autofocus => true %> 9 |
10 | 11 |
12 | <%= f.button :submit, "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /lib/devise/omniauth/url_helpers.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module OmniAuth 3 | module UrlHelpers 4 | def self.define_helpers(mapping) 5 | end 6 | 7 | def omniauth_authorize_path(resource_or_scope, *args) 8 | scope = Devise::Mapping.find_scope!(resource_or_scope) 9 | send("#{scope}_omniauth_authorize_path", *args) 10 | end 11 | 12 | def omniauth_callback_path(resource_or_scope, *args) 13 | scope = Devise::Mapping.find_scope!(resource_or_scope) 14 | send("#{scope}_omniauth_callback_path", *args) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, :required => true, :autofocus => true %> 8 | <%= f.input :password, :required => true %> 9 | <%= f.input :password_confirmation, :required => true %> 10 |
11 | 12 |
13 | <%= f.button :submit, "Sign up" %> 14 |
15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
<%= f.label :email %>
7 | <%= f.email_field :email, :autofocus => true %>
8 | 9 |
<%= f.label :password %>
10 | <%= f.password_field :password %>
11 | 12 |
<%= f.label :password_confirmation %>
13 | <%= f.password_field :password_confirmation %>
14 | 15 |
<%= f.submit "Sign up" %>
16 | <% end %> 17 | 18 | <%= render "devise/shared/links" %> 19 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :confirmation_token %> 6 | 7 |
8 | <%= f.input :email, :required => true, :autofocus => true %> 9 |
10 | 11 |
12 | <%= f.button :submit, "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /test/support/webrat/integrations/rails.rb: -------------------------------------------------------------------------------- 1 | require 'webrat/core/elements/form' 2 | require 'action_dispatch/testing/integration' 3 | 4 | module Webrat 5 | Form.class_eval do 6 | def self.parse_rails_request_params(params) 7 | Rack::Utils.parse_nested_query(params) 8 | end 9 | end 10 | 11 | module Logging 12 | # Avoid RAILS_DEFAULT_LOGGER deprecation warning 13 | def logger # :nodoc: 14 | ::Rails.logger 15 | end 16 | end 17 | end 18 | 19 | module ActionDispatch #:nodoc: 20 | IntegrationTest.class_eval do 21 | include Webrat::Methods 22 | include Webrat::Matchers 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/rails_app/app/mongoid/shim.rb: -------------------------------------------------------------------------------- 1 | module Shim 2 | extend ::ActiveSupport::Concern 3 | 4 | included do 5 | include ::Mongoid::Timestamps 6 | field :created_at, :type => DateTime 7 | end 8 | 9 | module ClassMethods 10 | def last(options = {}) 11 | options.delete(:order) if options[:order] == "id" 12 | where(options).last 13 | end 14 | 15 | def find_by_email(email) 16 | find_by(:email => email) 17 | end 18 | end 19 | 20 | # overwrite equality (because some devise tests use this for asserting model equality) 21 | def ==(other) 22 | other.is_a?(self.class) && _id == other._id 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/models/authenticatable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AuthenticatableTest < ActiveSupport::TestCase 4 | test 'required_fields should be an empty array' do 5 | assert_equal Devise::Models::Validatable.required_fields(User), [] 6 | end 7 | 8 | test 'find_first_by_auth_conditions allows custom filtering parameters' do 9 | user = User.create!(:email => "example@example.com", :password => "123456") 10 | assert_equal User.find_first_by_auth_conditions({ :email => "example@example.com" }), user 11 | assert_nil User.find_first_by_auth_conditions({ :email => "example@example.com" }, :id => user.id.to_s.next) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
<%= f.label :password, "New password" %>
8 | <%= f.password_field :password, :autofocus => true %>
9 | 10 |
<%= f.label :password_confirmation, "Confirm new password" %>
11 | <%= f.password_field :password_confirmation %>
12 | 13 |
<%= f.submit "Change my password" %>
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rails", "~> 3.2.6" 6 | gem "omniauth", "~> 1.0.0" 7 | gem "omniauth-oauth2", "~> 1.0.0" 8 | gem "rdoc" 9 | 10 | group :test do 11 | gem "omniauth-facebook" 12 | gem "omniauth-openid", "~> 1.0.1" 13 | gem "webrat", "0.7.2", :require => false 14 | gem "mocha", "0.10.0", :require => false 15 | end 16 | 17 | platforms :jruby do 18 | gem "activerecord-jdbc-adapter" 19 | gem "activerecord-jdbcsqlite3-adapter" 20 | gem "jruby-openssl" 21 | end 22 | 23 | platforms :ruby do 24 | gem "sqlite3" 25 | end 26 | 27 | 28 | platforms :mri_19 do 29 | group :mongoid do 30 | gem "mongoid", "~> 3.0" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/devise/strategies/base.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Strategies 3 | # Base strategy for Devise. Responsible for verifying correct scope and mapping. 4 | class Base < ::Warden::Strategies::Base 5 | # Whenever CSRF cannot be verified, we turn off any kind of storage 6 | def store? 7 | !env["devise.skip_storage"] 8 | end 9 | 10 | # Checks if a valid scope was given for devise and find mapping based on this scope. 11 | def mapping 12 | @mapping ||= begin 13 | mapping = Devise.mappings[scope] 14 | raise "Could not find mapping for #{scope}" unless mapping 15 | mapping 16 | end 17 | end 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /test/rails_app/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | prepend_before_filter :current_user, :only => :exhibit 3 | before_filter :authenticate_user!, :except => [:accept, :exhibit] 4 | respond_to :html, :xml 5 | 6 | def index 7 | user_session[:cart] = "Cart" 8 | respond_with(current_user) 9 | end 10 | 11 | def accept 12 | @current_user = current_user 13 | end 14 | 15 | def exhibit 16 | render :text => current_user ? "User is authenticated" : "User is not authenticated" 17 | end 18 | 19 | def expire 20 | user_session['last_request_at'] = 31.minutes.ago.utc 21 | render :text => 'User will be expired on next request' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/delegator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DelegatorTest < ActiveSupport::TestCase 4 | def delegator 5 | Devise::Delegator.new 6 | end 7 | 8 | test 'failure_app returns default failure app if no warden options in env' do 9 | assert_equal Devise::FailureApp, delegator.failure_app({}) 10 | end 11 | 12 | test 'failure_app returns default failure app if no scope in warden options' do 13 | assert_equal Devise::FailureApp, delegator.failure_app({"warden.options" => {}}) 14 | end 15 | 16 | test 'failure_app returns associated failure app by scope in the given environment' do 17 | assert_kind_of Proc, delegator.failure_app({"warden.options" => {:scope => "manager"}}) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/generators/devise/install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module Devise 4 | module Generators 5 | class InstallGenerator < Rails::Generators::Base 6 | source_root File.expand_path("../../templates", __FILE__) 7 | 8 | desc "Creates a Devise initializer and copy locale files to your application." 9 | class_option :orm 10 | 11 | def copy_initializer 12 | template "devise.rb", "config/initializers/devise.rb" 13 | end 14 | 15 | def copy_locale 16 | copy_file "../../../config/locales/en.yml", "config/locales/devise.en.yml" 17 | end 18 | 19 | def show_readme 20 | readme "README" if behavior == :invoke 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/devise/hooks/activatable.rb: -------------------------------------------------------------------------------- 1 | # Deny user access whenever his account is not active yet. All strategies that inherits from 2 | # Devise::Strategies::Authenticatable and uses the validate already check if the user is active_for_authentication? 3 | # before actively signing him in. However, we need this as hook to validate the user activity 4 | # in each request and in case the user is using other strategies beside Devise ones. 5 | Warden::Manager.after_set_user do |record, warden, options| 6 | if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication? 7 | scope = options[:scope] 8 | warden.logout(scope) 9 | throw :warden, :scope => scope, :message => record.inactive_message 10 | end 11 | end -------------------------------------------------------------------------------- /test/generators/mongoid_generator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | if DEVISE_ORM == :mongoid 4 | require "generators/mongoid/devise_generator" 5 | 6 | class MongoidGeneratorTest < Rails::Generators::TestCase 7 | tests Mongoid::Generators::DeviseGenerator 8 | destination File.expand_path("../../tmp", __FILE__) 9 | setup :prepare_destination 10 | 11 | test "all files are properly created" do 12 | run_generator %w(monster) 13 | assert_file "app/models/monster.rb", /devise/ 14 | end 15 | 16 | test "all files are properly deleted" do 17 | run_generator %w(monster) 18 | run_generator %w(monster), :behavior => :revoke 19 | assert_no_file "app/models/monster.rb" 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/devise/models/omniauthable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/omniauth' 2 | 3 | module Devise 4 | module Models 5 | # Adds OmniAuth support to your model. 6 | # 7 | # == Options 8 | # 9 | # Oauthable adds the following options to devise_for: 10 | # 11 | # * +omniauth_providers+: Which providers are avaialble to this model. It expects an array: 12 | # 13 | # devise_for :database_authenticatable, :omniauthable, :omniauth_providers => [:twitter] 14 | # 15 | module Omniauthable 16 | extend ActiveSupport::Concern 17 | 18 | def self.required_fields(klass) 19 | [] 20 | end 21 | 22 | module ClassMethods 23 | Devise::Models.config(self, :omniauth_providers) 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /test/rails_app/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Devise Test App 6 | 7 | 8 |
9 | <%- flash.each do |name, msg| -%> 10 | <%= content_tag :div, msg, :id => "flash_#{name}" %> 11 | <%- end -%> 12 | 13 | <% if user_signed_in? -%> 14 |

Hello User <%= current_user.email %>! You are signed in!

15 | <% end -%> 16 | 17 | <% if admin_signed_in? -%> 18 |

Hello Admin <%= current_admin.email %>! You are signed in!

19 | <% end -%> 20 | 21 | <%= yield %> 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/migration.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreate<%= table_name.camelize %> < ActiveRecord::Migration 2 | def change 3 | create_table(:<%= table_name %>) do |t| 4 | <%= migration_data -%> 5 | 6 | <% attributes.each do |attribute| -%> 7 | t.<%= attribute.type %> :<%= attribute.name %> 8 | <% end -%> 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :<%= table_name %>, :email, :unique => true 14 | add_index :<%= table_name %>, :reset_password_token, :unique => true 15 | # add_index :<%= table_name %>, :confirmation_token, :unique => true 16 | # add_index :<%= table_name %>, :unlock_token, :unique => true 17 | # add_index :<%= table_name %>, :authentication_token, :unique => true 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 | <%= f.input :reset_password_token, :as => :hidden %> 7 | <%= f.full_error :reset_password_token %> 8 | 9 |
10 | <%= f.input :password, :label => "New password", :required => true, :autofocus => true %> 11 | <%= f.input :password_confirmation, :label => "Confirm your new password", :required => true %> 12 |
13 | 14 |
15 | <%= f.button :submit, "Change my password" %> 16 |
17 | <% end %> 18 | 19 | <%= render "devise/shared/links" %> 20 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-3.1.x: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "devise", :path => ".." 4 | 5 | gem "rails", "~> 3.1.0" 6 | gem "omniauth", "~> 1.0.0" 7 | gem "omniauth-oauth2", "~> 1.0.0" 8 | gem "rdoc" 9 | 10 | group :test do 11 | gem "omniauth-facebook" 12 | gem "omniauth-openid", "~> 1.0.1" 13 | gem "webrat", "0.7.2", :require => false 14 | gem "mocha", "0.10.0", :require => false 15 | 16 | platforms :mri_18 do 17 | gem "ruby-debug", ">= 0.10.3" 18 | end 19 | end 20 | 21 | platforms :jruby do 22 | gem "activerecord-jdbc-adapter" 23 | gem "activerecord-jdbcsqlite3-adapter" 24 | gem "jruby-openssl" 25 | end 26 | 27 | platforms :ruby do 28 | gem "sqlite3" 29 | end 30 | 31 | platforms :mri_19 do 32 | group :mongoid do 33 | gem "mongoid", "~> 3.0" 34 | end 35 | end -------------------------------------------------------------------------------- /lib/devise/strategies/database_authenticatable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/strategies/authenticatable' 2 | 3 | module Devise 4 | module Strategies 5 | # Default strategy for signing in a user, based on his email and password in the database. 6 | class DatabaseAuthenticatable < Authenticatable 7 | def authenticate! 8 | resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash) 9 | return fail(:not_found_in_database) unless resource 10 | 11 | if validate(resource){ resource.valid_password?(password) } 12 | resource.after_database_authentication 13 | success!(resource) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | 20 | Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable) 21 | -------------------------------------------------------------------------------- /test/rails_app/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | end 19 | -------------------------------------------------------------------------------- /test/rails_app/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/devise/models/registerable.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Models 3 | # Registerable is responsible for everything related to registering a new 4 | # resource (ie user sign up). 5 | module Registerable 6 | extend ActiveSupport::Concern 7 | 8 | def self.required_fields(klass) 9 | [] 10 | end 11 | 12 | module ClassMethods 13 | # A convenience method that receives both parameters and session to 14 | # initialize a user. This can be used by OAuth, for example, to send 15 | # in the user token and be stored on initialization. 16 | # 17 | # By default discards all information sent by the session by calling 18 | # new with params. 19 | def new_with_session(params, session) 20 | new(params) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/rails_app/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/rails_app/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/rails_app/app/mongoid/admin.rb: -------------------------------------------------------------------------------- 1 | require 'shared_admin' 2 | 3 | class Admin 4 | include Mongoid::Document 5 | include Shim 6 | include SharedAdmin 7 | 8 | ## Database authenticatable 9 | field :email, :type => String 10 | field :encrypted_password, :type => String 11 | 12 | ## Recoverable 13 | field :reset_password_token, :type => String 14 | field :reset_password_sent_at, :type => Time 15 | 16 | ## Rememberable 17 | field :remember_created_at, :type => Time 18 | 19 | ## Confirmable 20 | field :confirmation_token, :type => String 21 | field :confirmed_at, :type => Time 22 | field :confirmation_sent_at, :type => Time 23 | field :unconfirmed_email, :type => String # Only if using reconfirmable 24 | 25 | ## Lockable 26 | field :locked_at, :type => Time 27 | 28 | field :active, :type => Boolean, :default => false 29 | end 30 | -------------------------------------------------------------------------------- /test/rails_app/lib/shared_user.rb: -------------------------------------------------------------------------------- 1 | module SharedUser 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | devise :database_authenticatable, :confirmable, :lockable, :recoverable, 6 | :registerable, :rememberable, :timeoutable, :token_authenticatable, 7 | :trackable, :validatable, :omniauthable 8 | 9 | attr_accessor :other_key 10 | attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :confirmation_sent_at 11 | 12 | # They need to be included after Devise is called. 13 | extend ExtendMethods 14 | end 15 | 16 | module ExtendMethods 17 | def new_with_session(params, session) 18 | super.tap do |user| 19 | if data = session["devise.facebook_data"] 20 | user.email = data["email"] 21 | user.confirmed_at = Time.now 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym 3 | 4 | $:.unshift File.dirname(__FILE__) 5 | puts "\n==> Devise.orm = #{DEVISE_ORM.inspect}" 6 | 7 | require "rails_app/config/environment" 8 | require "rails/test_help" 9 | require "orm/#{DEVISE_ORM}" 10 | 11 | I18n.load_path << File.expand_path("../support/locale/en.yml", __FILE__) 12 | 13 | require 'mocha' 14 | require 'webrat' 15 | Webrat.configure do |config| 16 | config.mode = :rails 17 | config.open_error_files = false 18 | end 19 | 20 | # Add support to load paths so we can overwrite broken webrat setup 21 | $:.unshift File.expand_path('../support', __FILE__) 22 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 23 | 24 | # For generators 25 | require "rails/generators/test_case" 26 | require "generators/devise/install_generator" 27 | require "generators/devise/views_generator" 28 | -------------------------------------------------------------------------------- /lib/generators/devise/devise_generator.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Generators 3 | class DeviseGenerator < Rails::Generators::NamedBase 4 | include Rails::Generators::ResourceHelpers 5 | 6 | namespace "devise" 7 | source_root File.expand_path("../templates", __FILE__) 8 | 9 | desc "Generates a model with the given NAME (if one does not exist) with devise " << 10 | "configuration plus a migration file and devise routes." 11 | 12 | hook_for :orm 13 | 14 | class_option :routes, :desc => "Generate routes", :type => :boolean, :default => true 15 | 16 | def add_devise_routes 17 | devise_route = "devise_for :#{plural_name}" 18 | devise_route << %Q(, :class_name => "#{class_name}") if class_name.include?("::") 19 | devise_route << %Q(, :skip => :all) unless options.routes? 20 | route devise_route 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/test_models.rb: -------------------------------------------------------------------------------- 1 | class Configurable < User 2 | devise :database_authenticatable, :confirmable, :rememberable, :timeoutable, :lockable, 3 | :stretches => 15, :pepper => 'abcdef', :allow_unconfirmed_access_for => 5.days, 4 | :remember_for => 7.days, :timeout_in => 15.minutes, :unlock_in => 10.days 5 | end 6 | 7 | class WithValidation < Admin 8 | devise :database_authenticatable, :validatable, :password_length => 2..6 9 | end 10 | 11 | class UserWithValidation < User 12 | validates_presence_of :username 13 | end 14 | 15 | class UserWithVirtualAttributes < User 16 | devise :case_insensitive_keys => [ :email, :email_confirmation ] 17 | validates :email, :presence => true, :confirmation => {:on => :create} 18 | attr_accessible :email, :email_confirmation 19 | end 20 | 21 | class Several < Admin 22 | devise :validatable 23 | devise :lockable 24 | end 25 | 26 | class Inheritable < Admin 27 | end 28 | -------------------------------------------------------------------------------- /app/helpers/devise_helper.rb: -------------------------------------------------------------------------------- 1 | module DeviseHelper 2 | # A simple way to show error messages for the current devise resource. If you need 3 | # to customize this method, you can either overwrite it in your application helpers or 4 | # copy the views to your application. 5 | # 6 | # This method is intended to stay simple and it is unlikely that we are going to change 7 | # it to add more behavior or options. 8 | def devise_error_messages! 9 | return "" if resource.errors.empty? 10 | 11 | messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join 12 | sentence = I18n.t("errors.messages.not_saved", 13 | :count => resource.errors.count, 14 | :resource => resource.class.model_name.human.downcase) 15 | 16 | html = <<-HTML 17 |
18 |

#{sentence}

19 | 20 |
21 | HTML 22 | 23 | html.html_safe 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | script: "bundle exec rake test" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | env: 8 | - DEVISE_ORM=mongoid 9 | - DEVISE_ORM=active_record 10 | matrix: 11 | exclude: 12 | - rvm: 1.8.7 13 | env: DEVISE_ORM=mongoid 14 | gemfile: Gemfile 15 | - rvm: 1.8.7 16 | env: DEVISE_ORM=mongoid 17 | gemfile: gemfiles/Gemfile.rails-3.1.x 18 | - rvm: 1.9.2 19 | env: DEVISE_ORM=mongoid 20 | gemfile: Gemfile 21 | - rvm: 1.9.2 22 | env: DEVISE_ORM=mongoid 23 | gemfile: gemfiles/Gemfile.rails-3.1.x 24 | gemfile: 25 | - gemfiles/Gemfile.rails-3.1.x 26 | - Gemfile 27 | services: 28 | - mongodb 29 | notifications: 30 | email: false 31 | campfire: 32 | on_success: change 33 | on_failure: always 34 | rooms: 35 | - secure: "TRiqvuM4i/QmRDWjUSNitE5/P91BOzDkNl53+bZjjtxcISCswZtmECWBR7n9\n3xwqCOU1o2lfohxZ32OHOj/Nj7o+90zWJfWxcv+if0hIXRiil62M5pg0lZUd\nyJ4M5VQ0lSWo5he1OUrXhSabPJeaK3B8yT/tdh+qO5yzR+vb/jc=" 36 | -------------------------------------------------------------------------------- /devise.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "devise/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "devise" 7 | s.version = Devise::VERSION.dup 8 | s.platform = Gem::Platform::RUBY 9 | s.license = "MIT" 10 | s.summary = "Flexible authentication solution for Rails with Warden" 11 | s.email = "contact@plataformatec.com.br" 12 | s.homepage = "http://github.com/plataformatec/devise" 13 | s.description = "Flexible authentication solution for Rails with Warden" 14 | s.authors = ['José Valim', 'Carlos Antônio'] 15 | 16 | s.rubyforge_project = "devise" 17 | 18 | s.files = `git ls-files`.split("\n") 19 | s.test_files = `git ls-files -- test/*`.split("\n") 20 | s.require_paths = ["lib"] 21 | 22 | s.add_dependency("warden", "~> 1.2.1") 23 | s.add_dependency("orm_adapter", "~> 0.1") 24 | s.add_dependency("bcrypt-ruby", "~> 3.0") 25 | s.add_dependency("railties", "~> 3.1") 26 | end 27 | -------------------------------------------------------------------------------- /lib/generators/devise/orm_helpers.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Generators 3 | module OrmHelpers 4 | def model_contents 5 | <<-CONTENT 6 | # Include default devise modules. Others available are: 7 | # :token_authenticatable, :confirmable, 8 | # :lockable, :timeoutable and :omniauthable 9 | devise :database_authenticatable, :registerable, 10 | :recoverable, :rememberable, :trackable, :validatable 11 | 12 | CONTENT 13 | end 14 | 15 | def model_exists? 16 | File.exists?(File.join(destination_root, model_path)) 17 | end 18 | 19 | def migration_exists?(table_name) 20 | Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_add_devise_to_#{table_name}.rb$/).first 21 | end 22 | 23 | def migration_path 24 | @migration_path ||= File.join("db", "migrate") 25 | end 26 | 27 | def model_path 28 | @model_path ||= File.join("app", "models", "#{file_path}.rb") 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Please read before contributing 2 | 3 | 1) Do not post questions in the issues tracker. If you have any questions about Devise, search the [Wiki](https://github.com/plataformatec/devise/wiki) or use the [Mailing List](https://groups.google.com/group/plataformatec-devise) or [Stack Overflow](http://stackoverflow.com/questions/tagged/devise). 4 | 5 | 2) If you find a security bug, **DO NOT** submit an issue here. Please send an e-mail to [developers@plataformatec.com.br](mailto:developers@plataformatec.com.br) instead. 6 | 7 | 3) Do a small search on the issues tracker before submitting your issue to see if it was already reported / fixed. 8 | 9 | 4) When reporting an issue, include Rails, Devise and Warden versions. If you are getting exceptions, please include the full backtrace. 10 | 11 | That's it! The more information you give, the easier it becomes for us to track it down and fix it. 12 | Ideally, you should provide an application that reproduces the error or a test case to Devise's suite. 13 | 14 | Thanks! 15 | -------------------------------------------------------------------------------- /app/controllers/devise/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::OmniauthCallbacksController < DeviseController 2 | prepend_before_filter { request.env["devise.skip_timeout"] = true } 3 | 4 | def passthru 5 | render :status => 404, :text => "Not found. Authentication passthru." 6 | end 7 | 8 | def failure 9 | set_flash_message :alert, :failure, :kind => OmniAuth::Utils.camelize(failed_strategy.name), :reason => failure_message 10 | redirect_to after_omniauth_failure_path_for(resource_name) 11 | end 12 | 13 | protected 14 | 15 | def failed_strategy 16 | env["omniauth.error.strategy"] 17 | end 18 | 19 | def failure_message 20 | exception = env["omniauth.error"] 21 | error = exception.error_reason if exception.respond_to?(:error_reason) 22 | error ||= exception.error if exception.respond_to?(:error) 23 | error ||= env["omniauth.error.type"].to_s 24 | error.to_s.humanize if error 25 | end 26 | 27 | def after_omniauth_failure_path_for(scope) 28 | new_session_path(scope) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require "bundler/gem_tasks" 3 | require 'rake/testtask' 4 | require 'rdoc/task' 5 | 6 | desc 'Default: run tests for all ORMs.' 7 | task :default => :test 8 | 9 | desc 'Run Devise tests for all ORMs.' 10 | task :pre_commit do 11 | Dir[File.join(File.dirname(__FILE__), 'test', 'orm', '*.rb')].each do |file| 12 | orm = File.basename(file).split(".").first 13 | # "Some day, my son, rake's inner wisdom will reveal itself. Until then, 14 | # take this `system` -- may its brute force protect you well." 15 | exit 1 unless system "rake test DEVISE_ORM=#{orm}" 16 | end 17 | end 18 | 19 | desc 'Run Devise unit tests.' 20 | Rake::TestTask.new(:test) do |t| 21 | t.libs << 'lib' 22 | t.libs << 'test' 23 | t.pattern = 'test/**/*_test.rb' 24 | t.verbose = true 25 | end 26 | 27 | desc 'Generate documentation for Devise.' 28 | Rake::RDocTask.new(:rdoc) do |rdoc| 29 | rdoc.rdoc_dir = 'rdoc' 30 | rdoc.title = 'Devise' 31 | rdoc.options << '--line-numbers' << '--inline-source' 32 | rdoc.rdoc_files.include('README.md') 33 | rdoc.rdoc_files.include('lib/**/*.rb') 34 | end 35 | -------------------------------------------------------------------------------- /lib/devise/omniauth.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "omniauth" 3 | require "omniauth/version" 4 | rescue LoadError 5 | warn "Could not load 'omniauth'. Please ensure you have the omniauth gem >= 1.0.0 installed and listed in your Gemfile." 6 | raise 7 | end 8 | 9 | unless OmniAuth::VERSION =~ /^1\./ 10 | raise "You are using an old OmniAuth version, please ensure you have 1.0.0.pr2 version or later installed." 11 | end 12 | 13 | # Clean up the default path_prefix. It will be automatically set by Devise. 14 | OmniAuth.config.path_prefix = nil 15 | 16 | OmniAuth.config.on_failure = Proc.new do |env| 17 | env['devise.mapping'] = Devise::Mapping.find_by_path!(env['PATH_INFO'], :path) 18 | controller_name = ActiveSupport::Inflector.camelize(env['devise.mapping'].controllers[:omniauth_callbacks]) 19 | controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller") 20 | controller_klass.action(:failure).call(env) 21 | end 22 | 23 | module Devise 24 | module OmniAuth 25 | autoload :Config, "devise/omniauth/config" 26 | autoload :UrlHelpers, "devise/omniauth/url_helpers" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009-2013 Plataformatec. http://plataformatec.com.br 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/migration_existing.rb: -------------------------------------------------------------------------------- 1 | class AddDeviseTo<%= table_name.camelize %> < ActiveRecord::Migration 2 | def self.up 3 | change_table(:<%= table_name %>) do |t| 4 | <%= migration_data -%> 5 | 6 | <% attributes.each do |attribute| -%> 7 | t.<%= attribute.type %> :<%= attribute.name %> 8 | <% end -%> 9 | 10 | # Uncomment below if timestamps were not included in your original model. 11 | # t.timestamps 12 | end 13 | 14 | add_index :<%= table_name %>, :email, :unique => true 15 | add_index :<%= table_name %>, :reset_password_token, :unique => true 16 | # add_index :<%= table_name %>, :confirmation_token, :unique => true 17 | # add_index :<%= table_name %>, :unlock_token, :unique => true 18 | # add_index :<%= table_name %>, :authentication_token, :unique => true 19 | end 20 | 21 | def self.down 22 | # By default, we don't want to make any assumption about how to roll back a migration when your 23 | # model already existed. Please edit below which fields you would like to remove in this migration. 24 | raise ActiveRecord::IrreversibleMigration 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/devise/hooks/timeoutable.rb: -------------------------------------------------------------------------------- 1 | # Each time a record is set we check whether its session has already timed out 2 | # or not, based on last request time. If so, the record is logged out and 3 | # redirected to the sign in page. Also, each time the request comes and the 4 | # record is set, we set the last request time inside its scoped session to 5 | # verify timeout in the following request. 6 | Warden::Manager.after_set_user do |record, warden, options| 7 | scope = options[:scope] 8 | env = warden.request.env 9 | 10 | if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false 11 | last_request_at = warden.session(scope)['last_request_at'] 12 | 13 | if record.timedout?(last_request_at) && !env['devise.skip_timeout'] 14 | warden.logout(scope) 15 | if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout 16 | record.reset_authentication_token! 17 | end 18 | throw :warden, :scope => scope, :message => :timeout 19 | end 20 | 21 | unless env['devise.skip_trackable'] 22 | warden.session(scope)['last_request_at'] = Time.now.utc 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Sign in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> -------------------------------------------------------------------------------- /lib/devise/param_filter.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | class ParamFilter 3 | def initialize(case_insensitive_keys, strip_whitespace_keys) 4 | @case_insensitive_keys = case_insensitive_keys || [] 5 | @strip_whitespace_keys = strip_whitespace_keys || [] 6 | end 7 | 8 | def filter(conditions) 9 | conditions = stringify_params(conditions.dup) 10 | 11 | @case_insensitive_keys.each do |k| 12 | value = conditions[k] 13 | next unless value.respond_to?(:downcase) 14 | conditions[k] = value.downcase 15 | end 16 | 17 | @strip_whitespace_keys.each do |k| 18 | value = conditions[k] 19 | next unless value.respond_to?(:strip) 20 | conditions[k] = value.strip 21 | end 22 | 23 | conditions 24 | end 25 | 26 | # Force keys to be string to avoid injection on mongoid related database. 27 | def stringify_params(conditions) 28 | return conditions unless conditions.is_a?(Hash) 29 | conditions.each do |k, v| 30 | conditions[k] = v.to_s if param_requires_string_conversion?(v) 31 | end 32 | end 33 | 34 | private 35 | 36 | def param_requires_string_conversion?(value) 37 | true 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/generators/templates/simple_form_for/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, :required => true, :autofocus => true %> 8 | 9 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 10 |

Currently waiting confirmation for: <%= resource.unconfirmed_email %>

11 | <% end %> 12 | 13 | <%= f.input :password, :autocomplete => "off", :hint => "leave it blank if you don't want to change it", :required => false %> 14 | <%= f.input :password_confirmation, :required => false %> 15 | <%= f.input :current_password, :hint => "we need your current password to confirm your changes", :required => true %> 16 |
17 | 18 |
19 | <%= f.button :submit, "Update" %> 20 |
21 | <% end %> 22 | 23 |

Cancel my account

24 | 25 |

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %>.

26 | 27 | <%= link_to "Back", :back %> 28 | -------------------------------------------------------------------------------- /test/generators/devise_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | require "generators/devise/devise_generator" 4 | 5 | class DeviseGeneratorTest < Rails::Generators::TestCase 6 | tests Devise::Generators::DeviseGenerator 7 | destination File.expand_path("../../tmp", __FILE__) 8 | 9 | setup do 10 | prepare_destination 11 | copy_routes 12 | end 13 | 14 | test "route generation for simple model names" do 15 | run_generator %w(monster name:string) 16 | assert_file "config/routes.rb", /devise_for :monsters/ 17 | end 18 | 19 | test "route generation for namespaced model names" do 20 | run_generator %w(monster/goblin name:string) 21 | match = /devise_for :goblins, :class_name => "Monster::Goblin"/ 22 | assert_file "config/routes.rb", match 23 | end 24 | 25 | test "route generation with skip routes" do 26 | run_generator %w(monster name:string --skip-routes) 27 | match = /devise_for :monsters, :skip => :all/ 28 | assert_file "config/routes.rb", match 29 | end 30 | 31 | def copy_routes 32 | routes = File.expand_path("../../rails_app/config/routes.rb", __FILE__) 33 | destination = File.join(destination_root, "config") 34 | 35 | FileUtils.mkdir_p(destination) 36 | FileUtils.cp routes, destination 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |
<%= f.label :email %>
7 | <%= f.email_field :email, :autofocus => true %>
8 | 9 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 10 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
11 | <% end %> 12 | 13 |
<%= f.label :password %> (leave blank if you don't want to change it)
14 | <%= f.password_field :password, :autocomplete => "off" %>
15 | 16 |
<%= f.label :password_confirmation %>
17 | <%= f.password_field :password_confirmation %>
18 | 19 |
<%= f.label :current_password %> (we need your current password to confirm your changes)
20 | <%= f.password_field :current_password %>
21 | 22 |
<%= f.submit "Update" %>
23 | <% end %> 24 | 25 |

Cancel my account

26 | 27 |

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %>.

28 | 29 | <%= link_to "Back", :back %> 30 | -------------------------------------------------------------------------------- /test/rails_app/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # See everything in the log (default is :info) 13 | # config.log_level = :debug 14 | 15 | # Use a different logger for distributed setups 16 | # config.logger = SyslogLogger.new 17 | 18 | # Use a different cache store in production 19 | # config.cache_store = :mem_cache_store 20 | 21 | # Disable Rails's static asset server 22 | # In production, Apache or nginx will already do this 23 | config.serve_static_assets = false 24 | 25 | # Enable serving of images, stylesheets, and javascripts from an asset server 26 | # config.action_controller.asset_host = "http://assets.example.com" 27 | 28 | # Disable delivery errors, bad email addresses will be ignored 29 | # config.action_mailer.raise_delivery_errors = false 30 | 31 | # Enable threaded mode 32 | # config.threadsafe! 33 | end 34 | -------------------------------------------------------------------------------- /test/support/assertions.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/test_case' 2 | 3 | class ActiveSupport::TestCase 4 | def assert_not(assertion) 5 | assert !assertion 6 | end 7 | 8 | def assert_blank(assertion) 9 | assert assertion.blank? 10 | end 11 | 12 | def assert_not_blank(assertion) 13 | assert !assertion.blank? 14 | end 15 | alias :assert_present :assert_not_blank 16 | 17 | def assert_email_sent(address = nil, &block) 18 | assert_difference('ActionMailer::Base.deliveries.size', &block) 19 | if address.present? 20 | assert_equal address, ActionMailer::Base.deliveries.last['to'].to_s 21 | end 22 | end 23 | 24 | def assert_email_not_sent(&block) 25 | assert_no_difference('ActionMailer::Base.deliveries.size', &block) 26 | end 27 | 28 | def assert_same_content(result, expected) 29 | assert expected.size == result.size, "the arrays doesn't have the same size" 30 | expected.each do |element| 31 | assert result.include?(element), "The array doesn't include '#{element}'." 32 | end 33 | end 34 | 35 | def assert_raise_with_message(exception_klass, message, &block) 36 | exception = assert_raise exception_klass, &block 37 | assert_equal exception.message, message, 38 | "The expected message was #{message} but your exception throwed #{exception.message}" 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/devise/modules.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/object/with_options' 2 | 3 | Devise.with_options :model => true do |d| 4 | # Strategies first 5 | d.with_options :strategy => true do |s| 6 | routes = [nil, :new, :destroy] 7 | s.add_module :database_authenticatable, :controller => :sessions, :route => { :session => routes } 8 | s.add_module :token_authenticatable, :controller => :sessions, :route => { :session => routes }, :no_input => true 9 | s.add_module :rememberable, :no_input => true 10 | end 11 | 12 | # Other authentications 13 | d.add_module :omniauthable, :controller => :omniauth_callbacks, :route => :omniauth_callback 14 | 15 | # Misc after 16 | routes = [nil, :new, :edit] 17 | d.add_module :recoverable, :controller => :passwords, :route => { :password => routes } 18 | d.add_module :registerable, :controller => :registrations, :route => { :registration => (routes << :cancel) } 19 | d.add_module :validatable 20 | 21 | # The ones which can sign out after 22 | routes = [nil, :new] 23 | d.add_module :confirmable, :controller => :confirmations, :route => { :confirmation => routes } 24 | d.add_module :lockable, :controller => :unlocks, :route => { :unlock => routes } 25 | d.add_module :timeoutable 26 | 27 | # Stats for last, so we make sure the user is really signed in 28 | d.add_module :trackable 29 | end -------------------------------------------------------------------------------- /lib/generators/templates/README: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | 3 | Some setup you must do manually if you haven't yet: 4 | 5 | 1. Ensure you have defined default url options in your environments files. Here 6 | is an example of default_url_options appropriate for a development environment 7 | in config/environments/development.rb: 8 | 9 | config.action_mailer.default_url_options = { :host => 'localhost:3000' } 10 | 11 | In production, :host should be set to the actual host of your application. 12 | 13 | 2. Ensure you have defined root_url to *something* in your config/routes.rb. 14 | For example: 15 | 16 | root :to => "home#index" 17 | 18 | 3. Ensure you have flash messages in app/views/layouts/application.html.erb. 19 | For example: 20 | 21 |

<%= notice %>

22 |

<%= alert %>

23 | 24 | 4. If you are deploying Rails 3.1+ on Heroku, you may want to set: 25 | 26 | config.assets.initialize_on_precompile = false 27 | 28 | On config/application.rb forcing your application to not access the DB 29 | or load models when precompiling your assets. 30 | 31 | 5. You can copy Devise views (for customization) to your app by running: 32 | 33 | rails g devise:views 34 | 35 | =============================================================================== 36 | -------------------------------------------------------------------------------- /app/controllers/devise/unlocks_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::UnlocksController < DeviseController 2 | prepend_before_filter :require_no_authentication 3 | 4 | # GET /resource/unlock/new 5 | def new 6 | build_resource({}) 7 | end 8 | 9 | # POST /resource/unlock 10 | def create 11 | self.resource = resource_class.send_unlock_instructions(resource_params) 12 | 13 | if successfully_sent?(resource) 14 | respond_with({}, :location => after_sending_unlock_instructions_path_for(resource)) 15 | else 16 | respond_with(resource) 17 | end 18 | end 19 | 20 | # GET /resource/unlock?unlock_token=abcdef 21 | def show 22 | self.resource = resource_class.unlock_access_by_token(params[:unlock_token]) 23 | 24 | if resource.errors.empty? 25 | set_flash_message :notice, :unlocked if is_navigational_format? 26 | respond_with_navigational(resource){ redirect_to after_unlock_path_for(resource) } 27 | else 28 | respond_with_navigational(resource.errors, :status => :unprocessable_entity){ render :new } 29 | end 30 | end 31 | 32 | protected 33 | 34 | # The path used after sending unlock password instructions 35 | def after_sending_unlock_instructions_path_for(resource) 36 | new_session_path(resource) 37 | end 38 | 39 | # The path used after unlocking the resource 40 | def after_unlock_path_for(resource) 41 | new_session_path(resource) 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /test/rails_app/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require "action_controller/railtie" 4 | require "action_mailer/railtie" 5 | require "active_resource/railtie" 6 | require "rails/test_unit/railtie" 7 | 8 | Bundler.require :default, DEVISE_ORM 9 | 10 | begin 11 | require "#{DEVISE_ORM}/railtie" 12 | rescue LoadError 13 | end 14 | 15 | require "devise" 16 | 17 | module RailsApp 18 | class Application < Rails::Application 19 | # Add additional load paths for your own custom dirs 20 | config.autoload_paths.reject!{ |p| p =~ /\/app\/(\w+)$/ && !%w(controllers helpers views).include?($1) } 21 | config.autoload_paths += [ "#{config.root}/app/#{DEVISE_ORM}" ] 22 | 23 | # Configure generators values. Many other options are available, be sure to check the documentation. 24 | # config.generators do |g| 25 | # g.orm :active_record 26 | # g.template_engine :erb 27 | # g.test_framework :test_unit, :fixture => true 28 | # end 29 | 30 | # Configure sensitive parameters which will be filtered from the log file. 31 | config.filter_parameters << :password 32 | config.assets.enabled = false 33 | 34 | config.action_mailer.default_url_options = { :host => "localhost:3000" } 35 | 36 | # This was used to break devise in some situations 37 | config.to_prepare do 38 | Devise::SessionsController.layout "application" 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/models/timeoutable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimeoutableTest < ActiveSupport::TestCase 4 | 5 | test 'should be expired' do 6 | assert new_user.timedout?(31.minutes.ago) 7 | end 8 | 9 | test 'should not be expired' do 10 | assert_not new_user.timedout?(29.minutes.ago) 11 | end 12 | 13 | test 'should not be expired when params is nil' do 14 | assert_not new_user.timedout?(nil) 15 | end 16 | 17 | test 'should use timeout_in method' do 18 | user = new_user 19 | user.instance_eval { def timeout_in; 10.minutes end } 20 | 21 | assert user.timedout?(12.minutes.ago) 22 | assert_not user.timedout?(8.minutes.ago) 23 | end 24 | 25 | test 'should not be expired when timeout_in method returns nil' do 26 | user = new_user 27 | user.instance_eval { def timeout_in; nil end } 28 | assert_not user.timedout?(10.hours.ago) 29 | end 30 | 31 | test 'fallback to Devise config option' do 32 | swap Devise, :timeout_in => 1.minute do 33 | user = new_user 34 | assert user.timedout?(2.minutes.ago) 35 | assert_not user.timedout?(30.seconds.ago) 36 | 37 | Devise.timeout_in = 5.minutes 38 | assert_not user.timedout?(2.minutes.ago) 39 | assert user.timedout?(6.minutes.ago) 40 | end 41 | end 42 | 43 | test 'required_fields should contain the fields that Devise uses' do 44 | assert_same_content Devise::Models::Timeoutable.required_fields(User), [] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/rails_app/app/mongoid/user.rb: -------------------------------------------------------------------------------- 1 | require 'shared_user' 2 | 3 | class User 4 | include Mongoid::Document 5 | include Shim 6 | include SharedUser 7 | 8 | field :username, :type => String 9 | field :facebook_token, :type => String 10 | 11 | ## Database authenticatable 12 | field :email, :type => String, :default => "" 13 | field :encrypted_password, :type => String, :default => "" 14 | 15 | ## Recoverable 16 | field :reset_password_token, :type => String 17 | field :reset_password_sent_at, :type => Time 18 | 19 | ## Rememberable 20 | field :remember_created_at, :type => Time 21 | 22 | ## Trackable 23 | field :sign_in_count, :type => Integer, :default => 0 24 | field :current_sign_in_at, :type => Time 25 | field :last_sign_in_at, :type => Time 26 | field :current_sign_in_ip, :type => String 27 | field :last_sign_in_ip, :type => String 28 | 29 | ## Confirmable 30 | field :confirmation_token, :type => String 31 | field :confirmed_at, :type => Time 32 | field :confirmation_sent_at, :type => Time 33 | # field :unconfirmed_email, :type => String # Only if using reconfirmable 34 | 35 | ## Lockable 36 | field :failed_attempts, :type => Integer, :default => 0 # Only if lock strategy is :failed_attempts 37 | field :unlock_token, :type => String # Only if unlock strategy is :email or :both 38 | field :locked_at, :type => Time 39 | 40 | ## Token authenticatable 41 | field :authentication_token, :type => String 42 | end 43 | -------------------------------------------------------------------------------- /lib/devise/models/timeoutable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/hooks/timeoutable' 2 | 3 | module Devise 4 | module Models 5 | # Timeoutable takes care of veryfing whether a user session has already 6 | # expired or not. When a session expires after the configured time, the user 7 | # will be asked for credentials again, it means, he/she will be redirected 8 | # to the sign in page. 9 | # 10 | # == Options 11 | # 12 | # Timeoutable adds the following options to devise_for: 13 | # 14 | # * +timeout_in+: the interval to timeout the user session without activity. 15 | # 16 | # == Examples 17 | # 18 | # user.timedout?(30.minutes.ago) 19 | # 20 | module Timeoutable 21 | extend ActiveSupport::Concern 22 | 23 | def self.required_fields(klass) 24 | [] 25 | end 26 | 27 | # Checks whether the user session has expired based on configured time. 28 | def timedout?(last_access) 29 | return false if remember_exists_and_not_expired? 30 | !timeout_in.nil? && last_access && last_access <= timeout_in.ago 31 | end 32 | 33 | def timeout_in 34 | self.class.timeout_in 35 | end 36 | 37 | private 38 | 39 | def remember_exists_and_not_expired? 40 | return false unless respond_to?(:remember_created_at) 41 | remember_created_at && !remember_expired? 42 | end 43 | 44 | module ClassMethods 45 | Devise::Models.config(self, :timeout_in) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/controllers/devise/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::ConfirmationsController < DeviseController 2 | # GET /resource/confirmation/new 3 | def new 4 | build_resource({}) 5 | end 6 | 7 | # POST /resource/confirmation 8 | def create 9 | self.resource = resource_class.send_confirmation_instructions(resource_params) 10 | 11 | if successfully_sent?(resource) 12 | respond_with({}, :location => after_resending_confirmation_instructions_path_for(resource_name)) 13 | else 14 | respond_with(resource) 15 | end 16 | end 17 | 18 | # GET /resource/confirmation?confirmation_token=abcdef 19 | def show 20 | self.resource = resource_class.confirm_by_token(params[:confirmation_token]) 21 | 22 | if resource.errors.empty? 23 | set_flash_message(:notice, :confirmed) if is_navigational_format? 24 | sign_in(resource_name, resource) 25 | respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) } 26 | else 27 | respond_with_navigational(resource.errors, :status => :unprocessable_entity){ render :new } 28 | end 29 | end 30 | 31 | protected 32 | 33 | # The path used after resending confirmation instructions. 34 | def after_resending_confirmation_instructions_path_for(resource_name) 35 | new_session_path(resource_name) 36 | end 37 | 38 | # The path used after confirmation. 39 | def after_confirmation_path_for(resource_name, resource) 40 | after_sign_in_path_for(resource) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /lib/devise/models/trackable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/hooks/trackable' 2 | 3 | module Devise 4 | module Models 5 | # Track information about your user sign in. It tracks the following columns: 6 | # 7 | # * sign_in_count - Increased every time a sign in is made (by form, openid, oauth) 8 | # * current_sign_in_at - A timestamp updated when the user signs in 9 | # * last_sign_in_at - Holds the timestamp of the previous sign in 10 | # * current_sign_in_ip - The remote ip updated when the user sign in 11 | # * last_sign_in_ip - Holds the remote ip of the previous sign in 12 | # 13 | module Trackable 14 | def self.required_fields(klass) 15 | [:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count] 16 | end 17 | 18 | def update_tracked_fields!(request) 19 | old_current, new_current = self.current_sign_in_at, Time.now.utc 20 | self.last_sign_in_at = old_current || new_current 21 | self.current_sign_in_at = new_current 22 | 23 | old_current, new_current = self.current_sign_in_ip, request.remote_ip 24 | self.last_sign_in_ip = old_current || new_current 25 | self.current_sign_in_ip = new_current 26 | 27 | self.sign_in_count ||= 0 28 | self.sign_in_count += 1 29 | 30 | save(:validate => false) or raise "Devise trackable could not save #{inspect}." \ 31 | "Please make sure a model using trackable can be saved at sign in." 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/rails_app/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | RailsApp::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql 29 | 30 | config.action_dispatch.show_exceptions = false 31 | 32 | config.active_support.deprecation = :stderr 33 | end 34 | -------------------------------------------------------------------------------- /lib/devise/omniauth/config.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module OmniAuth 3 | class StrategyNotFound < NameError 4 | def initialize(strategy) 5 | @strategy = strategy 6 | super("Could not find a strategy with name `#{strategy}'. " \ 7 | "Please ensure it is required or explicitly set it using the :strategy_class option.") 8 | end 9 | end 10 | 11 | class Config 12 | attr_accessor :strategy 13 | attr_reader :args, :options, :provider, :strategy_name 14 | 15 | def initialize(provider, args) 16 | @provider = provider 17 | @args = args 18 | @options = @args.last.is_a?(Hash) ? @args.last : {} 19 | @strategy = nil 20 | @strategy_name = options[:name] || @provider 21 | @strategy_class = options.delete(:strategy_class) 22 | end 23 | 24 | def strategy_class 25 | @strategy_class ||= find_strategy || autoload_strategy 26 | end 27 | 28 | def find_strategy 29 | ::OmniAuth.strategies.find do |strategy_class| 30 | strategy_class.to_s =~ /#{::OmniAuth::Utils.camelize(strategy_name)}$/ || 31 | strategy_class.default_options[:name] == strategy_name 32 | end 33 | end 34 | 35 | def autoload_strategy 36 | name = ::OmniAuth::Utils.camelize(provider.to_s) 37 | if ::OmniAuth::Strategies.const_defined?(name) 38 | ::OmniAuth::Strategies.const_get(name) 39 | else 40 | raise StrategyNotFound, name 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/devise/strategies/rememberable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/strategies/authenticatable' 2 | 3 | module Devise 4 | module Strategies 5 | # Remember the user through the remember token. This strategy is responsible 6 | # to verify whether there is a cookie with the remember token, and to 7 | # recreate the user from this cookie if it exists. Must be called *before* 8 | # authenticatable. 9 | class Rememberable < Authenticatable 10 | # A valid strategy for rememberable needs a remember token in the cookies. 11 | def valid? 12 | @remember_cookie = nil 13 | remember_cookie.present? 14 | end 15 | 16 | # To authenticate a user we deserialize the cookie and attempt finding 17 | # the record in the database. If the attempt fails, we pass to another 18 | # strategy handle the authentication. 19 | def authenticate! 20 | resource = mapping.to.serialize_from_cookie(*remember_cookie) 21 | 22 | unless resource 23 | cookies.delete(remember_key) 24 | return pass 25 | end 26 | 27 | if validate(resource) 28 | success!(resource) 29 | end 30 | end 31 | 32 | private 33 | 34 | def decorate(resource) 35 | super 36 | resource.extend_remember_period = mapping.to.extend_remember_period if resource.respond_to?(:extend_remember_period=) 37 | end 38 | 39 | def remember_me? 40 | true 41 | end 42 | 43 | def remember_key 44 | mapping.to.rememberable_options.fetch(:key, "remember_#{scope}_token") 45 | end 46 | 47 | def remember_cookie 48 | @remember_cookie ||= cookies.signed[remember_key] 49 | end 50 | 51 | end 52 | end 53 | end 54 | 55 | Warden::Strategies.add(:rememberable, Devise::Strategies::Rememberable) 56 | -------------------------------------------------------------------------------- /test/helpers/devise_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DeviseHelperTest < ActionDispatch::IntegrationTest 4 | setup do 5 | model_labels = { :models => { :user => "utilisateur" } } 6 | 7 | I18n.backend.store_translations :fr, 8 | { 9 | :errors => { :messages => { :not_saved => { 10 | :one => "Erreur lors de l'enregistrement de '%{resource}': 1 erreur.", 11 | :other => "Erreur lors de l'enregistrement de '%{resource}': %{count} erreurs." 12 | } } }, 13 | :activerecord => model_labels, 14 | :mongoid => model_labels 15 | } 16 | 17 | I18n.locale = 'fr' 18 | end 19 | 20 | teardown do 21 | I18n.locale = 'en' 22 | end 23 | 24 | test 'test errors.messages.not_saved with single error from i18n' do 25 | get new_user_registration_path 26 | 27 | fill_in 'password', :with => 'new_user123' 28 | fill_in 'password confirmation', :with => 'new_user123' 29 | click_button 'Sign up' 30 | 31 | assert_have_selector '#error_explanation' 32 | assert_contain "Erreur lors de l'enregistrement de 'utilisateur': 1 erreur" 33 | end 34 | 35 | test 'test errors.messages.not_saved with multiple errors from i18n' do 36 | # Dirty tracking behavior prevents email validations from being applied: 37 | # https://github.com/mongoid/mongoid/issues/756 38 | (pending "Fails on Mongoid < 2.1"; break) if defined?(Mongoid) && Mongoid::VERSION.to_f < 2.1 39 | 40 | get new_user_registration_path 41 | 42 | fill_in 'email', :with => 'invalid_email' 43 | fill_in 'password', :with => 'new_user123' 44 | fill_in 'password confirmation', :with => 'new_user321' 45 | click_button 'Sign up' 46 | 47 | assert_have_selector '#error_explanation' 48 | assert_contain "Erreur lors de l'enregistrement de 'utilisateur': 2 erreurs" 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- /app/controllers/devise/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::SessionsController < DeviseController 2 | prepend_before_filter :require_no_authentication, :only => [ :new, :create ] 3 | prepend_before_filter :allow_params_authentication!, :only => :create 4 | prepend_before_filter { request.env["devise.skip_timeout"] = true } 5 | 6 | # GET /resource/sign_in 7 | def new 8 | self.resource = build_resource(nil, :unsafe => true) 9 | clean_up_passwords(resource) 10 | respond_with(resource, serialize_options(resource)) 11 | end 12 | 13 | # POST /resource/sign_in 14 | def create 15 | self.resource = warden.authenticate!(auth_options) 16 | set_flash_message(:notice, :signed_in) if is_navigational_format? 17 | sign_in(resource_name, resource) 18 | respond_with resource, :location => after_sign_in_path_for(resource) 19 | end 20 | 21 | # DELETE /resource/sign_out 22 | def destroy 23 | redirect_path = after_sign_out_path_for(resource_name) 24 | signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) 25 | set_flash_message :notice, :signed_out if signed_out && is_navigational_format? 26 | 27 | # We actually need to hardcode this as Rails default responder doesn't 28 | # support returning empty response on GET request 29 | respond_to do |format| 30 | format.all { head :no_content } 31 | format.any(*navigational_formats) { redirect_to redirect_path } 32 | end 33 | end 34 | 35 | protected 36 | 37 | def serialize_options(resource) 38 | methods = resource_class.authentication_keys.dup 39 | methods = methods.keys if methods.is_a?(Hash) 40 | methods << :password if resource.respond_to?(:password) 41 | { :methods => methods, :only => [:password] } 42 | end 43 | 44 | def auth_options 45 | { :scope => resource_name, :recall => "#{controller_path}#new" } 46 | end 47 | end 48 | 49 | -------------------------------------------------------------------------------- /test/models/serializable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SerializableTest < ActiveSupport::TestCase 4 | setup do 5 | @user = create_user 6 | end 7 | 8 | test 'should not include unsafe keys on XML' do 9 | assert_match /email/, @user.to_xml 10 | assert_no_match /confirmation-token/, @user.to_xml 11 | end 12 | 13 | test 'should not include unsafe keys on XML even if a new except is provided' do 14 | assert_no_match /email/, @user.to_xml(:except => :email) 15 | assert_no_match /confirmation-token/, @user.to_xml(:except => :email) 16 | end 17 | 18 | test 'should include unsafe keys on XML if a force_except is provided' do 19 | assert_no_match / :email) 20 | assert_match /confirmation-token/, @user.to_xml(:force_except => :email) 21 | end 22 | 23 | test 'should not include unsafe keys on JSON' do 24 | keys = from_json().keys.select{ |key| !key.include?("id") } 25 | assert_equal %w(created_at email facebook_token updated_at username), keys.sort 26 | end 27 | 28 | test 'should not include unsafe keys on JSON even if a new except is provided' do 29 | assert_no_key "email", from_json(:except => :email) 30 | assert_no_key "confirmation_token", from_json(:except => :email) 31 | end 32 | 33 | test 'should include unsafe keys on JSON if a force_except is provided' do 34 | assert_no_key "email", from_json(:force_except => :email) 35 | assert_key "confirmation_token", from_json(:force_except => :email) 36 | end 37 | 38 | def assert_key(key, subject) 39 | assert subject.key?(key), "Expected #{subject.inspect} to have key #{key.inspect}" 40 | end 41 | 42 | def assert_no_key(key, subject) 43 | assert !subject.key?(key), "Expected #{subject.inspect} to not have key #{key.inspect}" 44 | end 45 | 46 | def from_json(options=nil) 47 | ActiveSupport::JSON.decode(@user.to_json(options))["user"] 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/devise/strategies/token_authenticatable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/strategies/base' 2 | 3 | module Devise 4 | module Strategies 5 | # Strategy for signing in a user, based on a authenticatable token. This works for both params 6 | # and http. For the former, all you need to do is to pass the params in the URL: 7 | # 8 | # http://myapp.example.com/?user_token=SECRET 9 | # 10 | # For HTTP, you can pass the token as username and blank password. Since some clients may require 11 | # a password, you can pass "X" as password and it will simply be ignored. 12 | class TokenAuthenticatable < Authenticatable 13 | def store? 14 | super && !mapping.to.skip_session_storage.include?(:token_auth) 15 | end 16 | 17 | def authenticate! 18 | resource = mapping.to.find_for_token_authentication(authentication_hash) 19 | return fail(:invalid_token) unless resource 20 | 21 | if validate(resource) 22 | resource.after_token_authentication 23 | success!(resource) 24 | end 25 | end 26 | 27 | private 28 | 29 | # Token Authenticatable can be authenticated with params in any controller and any verb. 30 | def valid_params_request? 31 | true 32 | end 33 | 34 | # Do not use remember_me behavior with token. 35 | def remember_me? 36 | false 37 | end 38 | 39 | # Try both scoped and non scoped keys. 40 | def params_auth_hash 41 | if params[scope].kind_of?(Hash) && params[scope].has_key?(authentication_keys.first) 42 | params[scope] 43 | else 44 | params 45 | end 46 | end 47 | 48 | # Overwrite authentication keys to use token_authentication_key. 49 | def authentication_keys 50 | @authentication_keys ||= [mapping.to.token_authentication_key] 51 | end 52 | end 53 | end 54 | end 55 | 56 | Warden::Strategies.add(:token_authenticatable, Devise::Strategies::TokenAuthenticatable) 57 | -------------------------------------------------------------------------------- /test/models/token_authenticatable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TokenAuthenticatableTest < ActiveSupport::TestCase 4 | 5 | test 'should reset authentication token' do 6 | user = new_user 7 | user.reset_authentication_token 8 | previous_token = user.authentication_token 9 | user.reset_authentication_token 10 | assert_not_equal previous_token, user.authentication_token 11 | end 12 | 13 | test 'should ensure authentication token' do 14 | user = new_user 15 | user.ensure_authentication_token 16 | previous_token = user.authentication_token 17 | user.ensure_authentication_token 18 | assert_equal previous_token, user.authentication_token 19 | end 20 | 21 | test 'should authenticate a valid user with authentication token and return it' do 22 | user = create_user 23 | user.ensure_authentication_token! 24 | user.confirm! 25 | authenticated_user = User.find_for_token_authentication(:auth_token => user.authentication_token) 26 | assert_equal authenticated_user, user 27 | end 28 | 29 | test 'should return nil when authenticating an invalid user by authentication token' do 30 | user = create_user 31 | user.ensure_authentication_token! 32 | user.confirm! 33 | authenticated_user = User.find_for_token_authentication(:auth_token => user.authentication_token.reverse) 34 | assert_nil authenticated_user 35 | end 36 | 37 | test 'should not be subject to injection' do 38 | user1 = create_user 39 | user1.ensure_authentication_token! 40 | user1.confirm! 41 | 42 | user2 = create_user 43 | user2.ensure_authentication_token! 44 | user2.confirm! 45 | 46 | user = User.find_for_token_authentication(:auth_token => {'$ne' => user1.authentication_token}) 47 | assert_nil user 48 | end 49 | 50 | test 'required_fields should contain the fields that Devise uses' do 51 | assert_same_content Devise::Models::TokenAuthenticatable.required_fields(User), [ 52 | :authentication_token 53 | ] 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/omniauth/url_helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OmniAuthRoutesTest < ActionController::TestCase 4 | tests ApplicationController 5 | 6 | def assert_path(action, provider, with_param=true) 7 | # Resource param 8 | assert_equal @controller.send(action, :user, provider), 9 | @controller.send("user_#{action}", provider) 10 | 11 | # With an object 12 | assert_equal @controller.send(action, User.new, provider), 13 | @controller.send("user_#{action}", provider) 14 | 15 | if with_param 16 | # Default url params 17 | assert_equal @controller.send(action, :user, provider, :param => 123), 18 | @controller.send("user_#{action}", provider, :param => 123) 19 | end 20 | end 21 | 22 | test 'should alias omniauth_callback to mapped user auth_callback' do 23 | assert_path :omniauth_callback_path, :facebook 24 | end 25 | 26 | test 'should alias omniauth_authorize to mapped user auth_authorize' do 27 | assert_path :omniauth_authorize_path, :facebook, false 28 | end 29 | 30 | test 'should generate authorization path' do 31 | assert_match "/users/auth/facebook", @controller.omniauth_authorize_path(:user, :facebook) 32 | 33 | assert_raise ActionController::RoutingError do 34 | @controller.omniauth_authorize_path(:user, :github) 35 | end 36 | end 37 | 38 | test 'should generate authorization path for named open_id omniauth' do 39 | assert_match "/users/auth/google", @controller.omniauth_authorize_path(:user, :google) 40 | end 41 | 42 | test 'should generate authorization path with params' do 43 | assert_match "/users/auth/openid?openid_url=http%3A%2F%2Fyahoo.com", 44 | @controller.omniauth_authorize_path(:user, :openid, :openid_url => "http://yahoo.com") 45 | end 46 | 47 | test 'should not add a "?" if no param was sent' do 48 | assert_equal "/users/auth/openid", 49 | @controller.omniauth_authorize_path(:user, :openid) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/devise/controllers/rememberable.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Controllers 3 | # A module that may be optionally included in a controller in order 4 | # to provide remember me behavior. 5 | module Rememberable 6 | # Return default cookie values retrieved from session options. 7 | def self.cookie_values 8 | Rails.configuration.session_options.slice(:path, :domain, :secure) 9 | end 10 | 11 | # A small warden proxy so we can remember and forget uses from hooks. 12 | class Proxy #:nodoc: 13 | include Devise::Controllers::Rememberable 14 | 15 | delegate :cookies, :env, :to => :@warden 16 | 17 | def initialize(warden) 18 | @warden = warden 19 | end 20 | end 21 | 22 | # Remembers the given resource by setting up a cookie 23 | def remember_me(resource) 24 | scope = Devise::Mapping.find_scope!(resource) 25 | resource.remember_me!(resource.extend_remember_period) 26 | cookies.signed[remember_key(resource, scope)] = remember_cookie_values(resource) 27 | end 28 | 29 | # Forgets the given resource by deleting a cookie 30 | def forget_me(resource) 31 | scope = Devise::Mapping.find_scope!(resource) 32 | resource.forget_me! 33 | cookies.delete(remember_key(resource, scope), forget_cookie_values(resource)) 34 | end 35 | 36 | protected 37 | 38 | def forget_cookie_values(resource) 39 | Devise::Controllers::Rememberable.cookie_values.merge!(resource.rememberable_options) 40 | end 41 | 42 | def remember_cookie_values(resource) 43 | options = { :httponly => true } 44 | options.merge!(forget_cookie_values(resource)) 45 | options.merge!( 46 | :value => resource.class.serialize_into_cookie(resource), 47 | :expires => resource.remember_expires_at 48 | ) 49 | end 50 | 51 | def remember_key(resource, scope) 52 | resource.rememberable_options.fetch(:key, "remember_#{scope}_token") 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/generators/mongoid/devise_generator.rb: -------------------------------------------------------------------------------- 1 | require 'generators/devise/orm_helpers' 2 | 3 | module Mongoid 4 | module Generators 5 | class DeviseGenerator < Rails::Generators::NamedBase 6 | include Devise::Generators::OrmHelpers 7 | 8 | def generate_model 9 | invoke "mongoid:model", [name] unless model_exists? && behavior == :invoke 10 | end 11 | 12 | def inject_field_types 13 | inject_into_file model_path, migration_data, :after => "include Mongoid::Document\n" if model_exists? 14 | end 15 | 16 | def inject_devise_content 17 | inject_into_file model_path, model_contents, :after => "include Mongoid::Document\n" if model_exists? 18 | end 19 | 20 | def migration_data 21 | < String, :default => "" 24 | field :encrypted_password, :type => String, :default => "" 25 | 26 | ## Recoverable 27 | field :reset_password_token, :type => String 28 | field :reset_password_sent_at, :type => Time 29 | 30 | ## Rememberable 31 | field :remember_created_at, :type => Time 32 | 33 | ## Trackable 34 | field :sign_in_count, :type => Integer, :default => 0 35 | field :current_sign_in_at, :type => Time 36 | field :last_sign_in_at, :type => Time 37 | field :current_sign_in_ip, :type => String 38 | field :last_sign_in_ip, :type => String 39 | 40 | ## Confirmable 41 | # field :confirmation_token, :type => String 42 | # field :confirmed_at, :type => Time 43 | # field :confirmation_sent_at, :type => Time 44 | # field :unconfirmed_email, :type => String # Only if using reconfirmable 45 | 46 | ## Lockable 47 | # field :failed_attempts, :type => Integer, :default => 0 # Only if lock strategy is :failed_attempts 48 | # field :unlock_token, :type => String # Only if unlock strategy is :email or :both 49 | # field :locked_at, :type => Time 50 | 51 | ## Token authenticatable 52 | # field :authentication_token, :type => String 53 | RUBY 54 | end 55 | end 56 | end 57 | end -------------------------------------------------------------------------------- /lib/devise/rails.rb: -------------------------------------------------------------------------------- 1 | require 'devise/rails/routes' 2 | require 'devise/rails/warden_compat' 3 | 4 | module Devise 5 | class Engine < ::Rails::Engine 6 | config.devise = Devise 7 | 8 | # Initialize Warden and copy its configurations. 9 | config.app_middleware.use Warden::Manager do |config| 10 | Devise.warden_config = config 11 | end 12 | 13 | # Force routes to be loaded if we are doing any eager load. 14 | config.before_eager_load { |app| app.reload_routes! } 15 | 16 | initializer "devise.url_helpers" do 17 | Devise.include_helpers(Devise::Controllers) 18 | end 19 | 20 | initializer "devise.omniauth" do |app| 21 | Devise.omniauth_configs.each do |provider, config| 22 | app.middleware.use config.strategy_class, *config.args do |strategy| 23 | config.strategy = strategy 24 | end 25 | end 26 | 27 | if Devise.omniauth_configs.any? 28 | Devise.include_helpers(Devise::OmniAuth) 29 | end 30 | end 31 | 32 | initializer "devise.mongoid_version_warning" do 33 | if defined?(Mongoid) 34 | require 'mongoid/version' 35 | if Mongoid::VERSION.to_f < 2.1 36 | puts "\n[DEVISE] Please note that Mongoid versions prior to 2.1 handle dirty model " \ 37 | "object attributes in such a way that the Devise `validatable` module will not apply " \ 38 | "its usual uniqueness and format validations for the email field. It is recommended " \ 39 | "that you upgrade to Mongoid 2.1+ for this and other fixes, but if for some reason you " \ 40 | "are unable to do so, you should add these validations manually.\n" 41 | end 42 | end 43 | end 44 | 45 | initializer "devise.fix_routes_proxy_missing_respond_to_bug" do 46 | # We can get rid of this once we support only Rails > 3.2 47 | ActionDispatch::Routing::RoutesProxy.class_eval do 48 | def respond_to?(method, include_private = false) 49 | super || routes.url_helpers.respond_to?(method) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/controllers/custom_strategy_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'ostruct' 3 | require 'warden/strategies/base' 4 | require 'devise/test_helpers' 5 | 6 | class CustomStrategyController < ActionController::Base 7 | def new 8 | warden.authenticate!(:custom_strategy) 9 | end 10 | end 11 | 12 | # These tests are to prove that a warden strategy can successfully 13 | # return a custom response, including a specific status code and 14 | # custom http response headers. This does work in production, 15 | # however, at the time of writing this, the Devise test helpers do 16 | # not recognise the custom response and proceed to calling the 17 | # Failure App. This makes it impossible to write tests for a 18 | # strategy that return a custom response with Devise. 19 | class CustomStrategy < Warden::Strategies::Base 20 | def authenticate! 21 | custom_headers = { "X-FOO" => "BAR" } 22 | response = Rack::Response.new("BAD REQUEST", 400, custom_headers) 23 | custom! response.finish 24 | end 25 | end 26 | 27 | class CustomStrategyTest < ActionController::TestCase 28 | tests CustomStrategyController 29 | 30 | include Devise::TestHelpers 31 | 32 | setup do 33 | Warden::Strategies.add(:custom_strategy, CustomStrategy) 34 | end 35 | 36 | teardown do 37 | Warden::Strategies._strategies.delete(:custom_strategy) 38 | end 39 | 40 | test "custom strategy can return its own status code" do 41 | ret = get :new 42 | 43 | # check the returned rack array 44 | assert ret.is_a?(Array) 45 | assert_equal 400, ret.first 46 | 47 | # check the saved response as well. This is purely so that the response is available to the testing framework 48 | # for verification. In production, the above array would be delivered directly to Rack. 49 | assert_response 400 50 | end 51 | 52 | test "custom strategy can return custom headers" do 53 | ret = get :new 54 | 55 | # check the returned rack array 56 | assert ret.is_a?(Array) 57 | assert_equal ret.third['X-FOO'], 'BAR' 58 | 59 | # check the saved response headers as well. 60 | assert_equal response.headers['X-FOO'], 'BAR' 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/omniauth/config_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OmniAuthConfigTest < ActiveSupport::TestCase 4 | class MyStrategy 5 | include OmniAuth::Strategy 6 | end 7 | 8 | test 'strategy_name returns provider if no options given' do 9 | config = Devise::OmniAuth::Config.new :facebook, [{}] 10 | assert_equal :facebook, config.strategy_name 11 | end 12 | 13 | test 'strategy_name returns provider if no name option are given' do 14 | config = Devise::OmniAuth::Config.new :facebook, [{ :other => :option }] 15 | assert_equal :facebook, config.strategy_name 16 | end 17 | 18 | test 'returns name option when have a name' do 19 | config = Devise::OmniAuth::Config.new :facebook, [{ :name => :github }] 20 | assert_equal :github, config.strategy_name 21 | end 22 | 23 | test "finds contrib strategies" do 24 | config = Devise::OmniAuth::Config.new :facebook, [{}] 25 | assert_equal OmniAuth::Strategies::Facebook, config.strategy_class 26 | end 27 | 28 | test "finds the strategy in OmniAuth's list by name" do 29 | NamedTestStrategy = Class.new 30 | NamedTestStrategy.send :include, OmniAuth::Strategy 31 | NamedTestStrategy.option :name, :the_one 32 | 33 | config = Devise::OmniAuth::Config.new :the_one, [{}] 34 | assert_equal NamedTestStrategy, config.strategy_class 35 | end 36 | 37 | test "finds the strategy in OmniAuth's list by class name" do 38 | UnNamedTestStrategy = Class.new 39 | UnNamedTestStrategy.send :include, OmniAuth::Strategy 40 | 41 | config = Devise::OmniAuth::Config.new :un_named_test_strategy, [{}] 42 | assert_equal UnNamedTestStrategy, config.strategy_class 43 | end 44 | 45 | test 'raises an error if strategy cannot be found' do 46 | config = Devise::OmniAuth::Config.new :my_other_strategy, [{}] 47 | assert_raise Devise::OmniAuth::StrategyNotFound do 48 | config.strategy_class 49 | end 50 | end 51 | 52 | test 'allows the user to define a custom require path' do 53 | config = Devise::OmniAuth::Config.new :my_strategy, [{:strategy_class => MyStrategy}] 54 | config_class = config.strategy_class 55 | assert_equal MyStrategy, config_class 56 | end 57 | end -------------------------------------------------------------------------------- /test/rails_app/db/migrate/20100401102949_create_tables.rb: -------------------------------------------------------------------------------- 1 | class CreateTables < ActiveRecord::Migration 2 | def self.up 3 | create_table :users do |t| 4 | t.string :username 5 | t.string :facebook_token 6 | 7 | ## Database authenticatable 8 | t.string :email, :null => false, :default => "" 9 | t.string :encrypted_password, :null => false, :default => "" 10 | 11 | ## Recoverable 12 | t.string :reset_password_token 13 | t.datetime :reset_password_sent_at 14 | 15 | ## Rememberable 16 | t.datetime :remember_created_at 17 | 18 | ## Trackable 19 | t.integer :sign_in_count, :default => 0 20 | t.datetime :current_sign_in_at 21 | t.datetime :last_sign_in_at 22 | t.string :current_sign_in_ip 23 | t.string :last_sign_in_ip 24 | 25 | ## Confirmable 26 | t.string :confirmation_token 27 | t.datetime :confirmed_at 28 | t.datetime :confirmation_sent_at 29 | # t.string :unconfirmed_email # Only if using reconfirmable 30 | 31 | ## Lockable 32 | t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts 33 | t.string :unlock_token # Only if unlock strategy is :email or :both 34 | t.datetime :locked_at 35 | 36 | ## Token authenticatable 37 | t.string :authentication_token 38 | 39 | t.timestamps 40 | end 41 | 42 | create_table :admins do |t| 43 | ## Database authenticatable 44 | t.string :email, :null => true 45 | t.string :encrypted_password, :null => true 46 | 47 | ## Recoverable 48 | t.string :reset_password_token 49 | t.datetime :reset_password_sent_at 50 | 51 | ## Rememberable 52 | t.datetime :remember_created_at 53 | 54 | ## Confirmable 55 | t.string :confirmation_token 56 | t.datetime :confirmed_at 57 | t.datetime :confirmation_sent_at 58 | t.string :unconfirmed_email # Only if using reconfirmable 59 | 60 | ## Lockable 61 | t.datetime :locked_at 62 | 63 | ## Attribute for testing route blocks 64 | t.boolean :active, :default => false 65 | 66 | t.timestamps 67 | end 68 | end 69 | 70 | def self.down 71 | drop_table :users 72 | drop_table :admins 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/rails_app/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20100401102949) do 14 | 15 | create_table "admins", :force => true do |t| 16 | t.string "email" 17 | t.string "encrypted_password", :limit => 128 18 | t.string "password_salt" 19 | t.string "remember_token" 20 | t.datetime "remember_created_at" 21 | t.string "reset_password_token" 22 | t.integer "failed_attempts", :default => 0 23 | t.string "unlock_token" 24 | t.datetime "locked_at" 25 | t.datetime "created_at" 26 | t.datetime "updated_at" 27 | end 28 | 29 | create_table "users", :force => true do |t| 30 | t.string "username" 31 | t.string "facebook_token" 32 | t.string "email", :default => "", :null => false 33 | t.string "encrypted_password", :limit => 128, :default => "", :null => false 34 | t.string "confirmation_token" 35 | t.datetime "confirmed_at" 36 | t.datetime "confirmation_sent_at" 37 | t.string "reset_password_token" 38 | t.datetime "remember_created_at" 39 | t.integer "sign_in_count", :default => 0 40 | t.datetime "current_sign_in_at" 41 | t.datetime "last_sign_in_at" 42 | t.string "current_sign_in_ip" 43 | t.string "last_sign_in_ip" 44 | t.integer "failed_attempts", :default => 0 45 | t.string "unlock_token" 46 | t.datetime "locked_at" 47 | t.string "authentication_token" 48 | t.datetime "created_at" 49 | t.datetime "updated_at" 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /test/controllers/url_helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RoutesTest < ActionController::TestCase 4 | tests ApplicationController 5 | 6 | def assert_path_and_url(name, prepend_path=nil) 7 | @request.path = '/users/session' 8 | prepend_path = "#{prepend_path}_" if prepend_path 9 | 10 | # Resource param 11 | assert_equal @controller.send(:"#{prepend_path}#{name}_path", :user), 12 | send(:"#{prepend_path}user_#{name}_path") 13 | assert_equal @controller.send(:"#{prepend_path}#{name}_url", :user), 14 | send(:"#{prepend_path}user_#{name}_url") 15 | 16 | # Default url params 17 | assert_equal @controller.send(:"#{prepend_path}#{name}_path", :user, :param => 123), 18 | send(:"#{prepend_path}user_#{name}_path", :param => 123) 19 | assert_equal @controller.send(:"#{prepend_path}#{name}_url", :user, :param => 123), 20 | send(:"#{prepend_path}user_#{name}_url", :param => 123) 21 | 22 | @request.path = nil 23 | # With an object 24 | assert_equal @controller.send(:"#{prepend_path}#{name}_path", User.new), 25 | send(:"#{prepend_path}user_#{name}_path") 26 | assert_equal @controller.send(:"#{prepend_path}#{name}_url", User.new), 27 | send(:"#{prepend_path}user_#{name}_url") 28 | end 29 | 30 | 31 | test 'should alias session to mapped user session' do 32 | assert_path_and_url :session 33 | assert_path_and_url :session, :new 34 | assert_path_and_url :session, :destroy 35 | end 36 | 37 | test 'should alias password to mapped user password' do 38 | assert_path_and_url :password 39 | assert_path_and_url :password, :new 40 | assert_path_and_url :password, :edit 41 | end 42 | 43 | test 'should alias confirmation to mapped user confirmation' do 44 | assert_path_and_url :confirmation 45 | assert_path_and_url :confirmation, :new 46 | end 47 | 48 | test 'should alias unlock to mapped user unlock' do 49 | assert_path_and_url :unlock 50 | assert_path_and_url :unlock, :new 51 | end 52 | 53 | test 'should alias registration to mapped user registration' do 54 | assert_path_and_url :registration 55 | assert_path_and_url :registration, :new 56 | assert_path_and_url :registration, :edit 57 | assert_path_and_url :registration, :cancel 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /app/controllers/devise/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::PasswordsController < DeviseController 2 | prepend_before_filter :require_no_authentication 3 | # Render the #edit only if coming from a reset password email link 4 | append_before_filter :assert_reset_token_passed, :only => :edit 5 | 6 | # GET /resource/password/new 7 | def new 8 | build_resource({}) 9 | end 10 | 11 | # POST /resource/password 12 | def create 13 | self.resource = resource_class.send_reset_password_instructions(resource_params) 14 | 15 | if successfully_sent?(resource) 16 | respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) 17 | else 18 | respond_with(resource) 19 | end 20 | end 21 | 22 | # GET /resource/password/edit?reset_password_token=abcdef 23 | def edit 24 | self.resource = resource_class.new 25 | resource.reset_password_token = params[:reset_password_token] 26 | end 27 | 28 | # PUT /resource/password 29 | def update 30 | self.resource = resource_class.reset_password_by_token(resource_params) 31 | 32 | if resource.errors.empty? 33 | resource.unlock_access! if unlockable?(resource) 34 | flash_message = resource.active_for_authentication? ? :updated : :updated_not_active 35 | set_flash_message(:notice, flash_message) if is_navigational_format? 36 | sign_in(resource_name, resource) 37 | respond_with resource, :location => after_sign_in_path_for(resource) 38 | else 39 | respond_with resource 40 | end 41 | end 42 | 43 | protected 44 | 45 | # The path used after sending reset password instructions 46 | def after_sending_reset_password_instructions_path_for(resource_name) 47 | new_session_path(resource_name) 48 | end 49 | 50 | # Check if a reset_password_token is provided in the request 51 | def assert_reset_token_passed 52 | if params[:reset_password_token].blank? 53 | set_flash_message(:error, :no_token) 54 | redirect_to new_session_path(resource_name) 55 | end 56 | end 57 | 58 | # Check if proper Lockable module methods are present & unlock strategy 59 | # allows to unlock resource on password reset 60 | def unlockable?(resource) 61 | resource.respond_to?(:unlock_access!) && 62 | resource.respond_to?(:unlock_strategy_enabled?) && 63 | resource.unlock_strategy_enabled?(:email) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/mailers/unlock_instructions_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UnlockInstructionsTest < ActionMailer::TestCase 4 | 5 | def setup 6 | setup_mailer 7 | Devise.mailer = 'Devise::Mailer' 8 | Devise.mailer_sender = 'test@example.com' 9 | end 10 | 11 | def teardown 12 | Devise.mailer = 'Devise::Mailer' 13 | Devise.mailer_sender = 'please-change-me@config-initializers-devise.com' 14 | end 15 | 16 | def user 17 | @user ||= begin 18 | user = create_user 19 | user.lock_access! 20 | user 21 | end 22 | end 23 | 24 | def mail 25 | @mail ||= begin 26 | user 27 | ActionMailer::Base.deliveries.last 28 | end 29 | end 30 | 31 | test 'email sent after locking the user' do 32 | assert_not_nil mail 33 | end 34 | 35 | test 'content type should be set to html' do 36 | assert mail.content_type.include?('text/html') 37 | end 38 | 39 | test 'send unlock instructions to the user email' do 40 | assert_equal [user.email], mail.to 41 | end 42 | 43 | test 'setup sender from configuration' do 44 | assert_equal ['test@example.com'], mail.from 45 | end 46 | 47 | test 'setup sender from custom mailer defaults' do 48 | Devise.mailer = 'Users::Mailer' 49 | assert_equal ['custom@example.com'], mail.from 50 | end 51 | 52 | test 'custom mailer renders parent mailer template' do 53 | Devise.mailer = 'Users::Mailer' 54 | assert_not_blank mail.body.encoded 55 | end 56 | 57 | test 'setup reply to as copy from sender' do 58 | assert_equal ['test@example.com'], mail.reply_to 59 | end 60 | 61 | test 'setup subject from I18n' do 62 | store_translations :en, :devise => { :mailer => { :unlock_instructions => { :subject => 'Yo unlock instructions' } } } do 63 | assert_equal 'Yo unlock instructions', mail.subject 64 | end 65 | end 66 | 67 | test 'subject namespaced by model' do 68 | store_translations :en, :devise => { :mailer => { :unlock_instructions => { :user_subject => 'User Unlock Instructions' } } } do 69 | assert_equal 'User Unlock Instructions', mail.subject 70 | end 71 | end 72 | 73 | test 'body should have user info' do 74 | assert_match user.email, mail.body.encoded 75 | end 76 | 77 | test 'body should have link to unlock the account' do 78 | host = ActionMailer::Base.default_url_options[:host] 79 | unlock_url_regexp = %r{} 80 | assert_match unlock_url_regexp, mail.body.encoded 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/support/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/test_case' 2 | 3 | class ActiveSupport::TestCase 4 | VALID_AUTHENTICATION_TOKEN = 'AbCdEfGhIjKlMnOpQrSt'.freeze 5 | 6 | def setup_mailer 7 | ActionMailer::Base.deliveries = [] 8 | end 9 | 10 | def store_translations(locale, translations, &block) 11 | begin 12 | I18n.backend.store_translations(locale, translations) 13 | yield 14 | ensure 15 | I18n.reload! 16 | end 17 | end 18 | 19 | def generate_unique_email 20 | @@email_count ||= 0 21 | @@email_count += 1 22 | "test#{@@email_count}@example.com" 23 | end 24 | 25 | def valid_attributes(attributes={}) 26 | { :username => "usertest", 27 | :email => generate_unique_email, 28 | :password => '12345678', 29 | :password_confirmation => '12345678' }.update(attributes) 30 | end 31 | 32 | def new_user(attributes={}) 33 | User.new(valid_attributes(attributes)) 34 | end 35 | 36 | def create_user(attributes={}) 37 | User.create!(valid_attributes(attributes)) 38 | end 39 | 40 | def create_admin(attributes={}) 41 | valid_attributes = valid_attributes(attributes) 42 | valid_attributes.delete(:username) 43 | Admin.create!(valid_attributes) 44 | end 45 | 46 | # Execute the block setting the given values and restoring old values after 47 | # the block is executed. 48 | def swap(object, new_values) 49 | old_values = {} 50 | new_values.each do |key, value| 51 | old_values[key] = object.send key 52 | object.send :"#{key}=", value 53 | end 54 | clear_cached_variables(new_values) 55 | yield 56 | ensure 57 | clear_cached_variables(new_values) 58 | old_values.each do |key, value| 59 | object.send :"#{key}=", value 60 | end 61 | end 62 | 63 | def clear_cached_variables(options) 64 | if options.key?(:case_insensitive_keys) || options.key?(:strip_whitespace_keys) 65 | Devise.mappings.each do |_, mapping| 66 | mapping.to.instance_variable_set(:@devise_param_filter, nil) 67 | end 68 | end 69 | end 70 | 71 | def swap_module_method_existence(klass, method) 72 | klass.module_eval %Q[ 73 | class << self 74 | alias #{method}_referenced #{method} 75 | undef #{method} 76 | end 77 | ] 78 | 79 | begin 80 | yield if block_given? 81 | ensure 82 | 83 | klass.module_eval %Q[ 84 | class << self 85 | alias #{method} #{method}_referenced 86 | undef #{method}_referenced 87 | end 88 | ] 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/devise/controllers/url_helpers.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Controllers 3 | # Create url helpers to be used with resource/scope configuration. Acts as 4 | # proxies to the generated routes created by devise. 5 | # Resource param can be a string or symbol, a class, or an instance object. 6 | # Example using a :user resource: 7 | # 8 | # new_session_path(:user) => new_user_session_path 9 | # session_path(:user) => user_session_path 10 | # destroy_session_path(:user) => destroy_user_session_path 11 | # 12 | # new_password_path(:user) => new_user_password_path 13 | # password_path(:user) => user_password_path 14 | # edit_password_path(:user) => edit_user_password_path 15 | # 16 | # new_confirmation_path(:user) => new_user_confirmation_path 17 | # confirmation_path(:user) => user_confirmation_path 18 | # 19 | # Those helpers are included by default to ActionController::Base. 20 | # 21 | # In case you want to add such helpers to another class, you can do 22 | # that as long as this new class includes both url_helpers and 23 | # mounted_helpers. Example: 24 | # 25 | # include Rails.application.routes.url_helpers 26 | # include Rails.application.routes.mounted_helpers 27 | # 28 | module UrlHelpers 29 | def self.remove_helpers! 30 | self.instance_methods.map(&:to_s).grep(/_(url|path)$/).each do |method| 31 | remove_method method 32 | end 33 | end 34 | 35 | def self.generate_helpers!(routes=nil) 36 | routes ||= begin 37 | mappings = Devise.mappings.values.map(&:used_helpers).flatten.uniq 38 | Devise::URL_HELPERS.slice(*mappings) 39 | end 40 | 41 | routes.each do |module_name, actions| 42 | [:path, :url].each do |path_or_url| 43 | actions.each do |action| 44 | action = action ? "#{action}_" : "" 45 | method = "#{action}#{module_name}_#{path_or_url}" 46 | 47 | class_eval <<-URL_HELPERS, __FILE__, __LINE__ + 1 48 | def #{method}(resource_or_scope, *args) 49 | scope = Devise::Mapping.find_scope!(resource_or_scope) 50 | _devise_route_context.send("#{action}\#{scope}_#{module_name}_#{path_or_url}", *args) 51 | end 52 | URL_HELPERS 53 | end 54 | end 55 | end 56 | end 57 | 58 | generate_helpers!(Devise::URL_HELPERS) 59 | 60 | private 61 | 62 | def _devise_route_context 63 | @_devise_route_context ||= send(Devise.available_router_name) 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/devise/models/validatable.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Models 3 | # Validatable creates all needed validations for a user email and password. 4 | # It's optional, given you may want to create the validations by yourself. 5 | # Automatically validate if the email is present, unique and its format is 6 | # valid. Also tests presence of password, confirmation and length. 7 | # 8 | # == Options 9 | # 10 | # Validatable adds the following options to devise_for: 11 | # 12 | # * +email_regexp+: the regular expression used to validate e-mails; 13 | # * +password_length+: a range expressing password length. Defaults to 8..128. 14 | # 15 | module Validatable 16 | # All validations used by this module. 17 | VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of, 18 | :validates_confirmation_of, :validates_length_of ].freeze 19 | 20 | def self.required_fields(klass) 21 | [] 22 | end 23 | 24 | def self.included(base) 25 | base.extend ClassMethods 26 | assert_validations_api!(base) 27 | 28 | base.class_eval do 29 | validates_presence_of :email, :if => :email_required? 30 | validates_uniqueness_of :email, :allow_blank => true, :if => :email_changed? 31 | validates_format_of :email, :with => email_regexp, :allow_blank => true, :if => :email_changed? 32 | 33 | validates_presence_of :password, :if => :password_required? 34 | validates_confirmation_of :password, :if => :password_required? 35 | validates_length_of :password, :within => password_length, :allow_blank => true 36 | end 37 | end 38 | 39 | def self.assert_validations_api!(base) #:nodoc: 40 | unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) } 41 | 42 | unless unavailable_validations.empty? 43 | raise "Could not use :validatable module since #{base} does not respond " << 44 | "to the following methods: #{unavailable_validations.to_sentence}." 45 | end 46 | end 47 | 48 | protected 49 | 50 | # Checks whether a password is needed or not. For validations only. 51 | # Passwords are always required if it's a new record, or if the password 52 | # or confirmation are being set somewhere. 53 | def password_required? 54 | !persisted? || !password.nil? || !password_confirmation.nil? 55 | end 56 | 57 | def email_required? 58 | true 59 | end 60 | 61 | module ClassMethods 62 | Devise::Models.config(self, :email_regexp, :password_length) 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/integration/trackable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TrackableHooksTest < ActionDispatch::IntegrationTest 4 | 5 | test "current and last sign in timestamps are updated on each sign in" do 6 | user = create_user 7 | assert_nil user.current_sign_in_at 8 | assert_nil user.last_sign_in_at 9 | 10 | sign_in_as_user 11 | user.reload 12 | 13 | assert_kind_of Time, user.current_sign_in_at 14 | assert_kind_of Time, user.last_sign_in_at 15 | 16 | assert_equal user.current_sign_in_at, user.last_sign_in_at 17 | assert user.current_sign_in_at >= user.created_at 18 | 19 | visit destroy_user_session_path 20 | new_time = 2.seconds.from_now 21 | Time.stubs(:now).returns(new_time) 22 | 23 | sign_in_as_user 24 | user.reload 25 | assert user.current_sign_in_at > user.last_sign_in_at 26 | end 27 | 28 | test "current and last sign in remote ip are updated on each sign in" do 29 | user = create_user 30 | assert_nil user.current_sign_in_ip 31 | assert_nil user.last_sign_in_ip 32 | 33 | sign_in_as_user 34 | user.reload 35 | 36 | assert_equal "127.0.0.1", user.current_sign_in_ip 37 | assert_equal "127.0.0.1", user.last_sign_in_ip 38 | end 39 | 40 | test "current remote ip returns original ip behind a non transparent proxy" do 41 | user = create_user 42 | 43 | arbitrary_ip = '200.121.1.69' 44 | sign_in_as_user do 45 | header 'HTTP_X_FORWARDED_FOR', arbitrary_ip 46 | end 47 | user.reload 48 | assert_equal arbitrary_ip, user.current_sign_in_ip 49 | end 50 | 51 | test "increase sign in count" do 52 | user = create_user 53 | assert_equal 0, user.sign_in_count 54 | 55 | sign_in_as_user 56 | user.reload 57 | assert_equal 1, user.sign_in_count 58 | 59 | visit destroy_user_session_path 60 | sign_in_as_user 61 | user.reload 62 | assert_equal 2, user.sign_in_count 63 | end 64 | 65 | test "does not update anything if user has signed out along the way" do 66 | swap Devise, :allow_unconfirmed_access_for => 0 do 67 | user = create_user(:confirm => false) 68 | sign_in_as_user 69 | 70 | user.reload 71 | assert_nil user.current_sign_in_at 72 | assert_nil user.last_sign_in_at 73 | end 74 | end 75 | 76 | test "do not track if devise.skip_trackable is set" do 77 | user = create_user 78 | sign_in_as_user do 79 | header 'devise.skip_trackable', '1' 80 | end 81 | user.reload 82 | assert_equal 0, user.sign_in_count 83 | visit destroy_user_session_path 84 | 85 | sign_in_as_user do 86 | header 'devise.skip_trackable', false 87 | end 88 | user.reload 89 | assert_equal 1, user.sign_in_count 90 | end 91 | 92 | end 93 | -------------------------------------------------------------------------------- /test/generators/views_generator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ViewsGeneratorTest < Rails::Generators::TestCase 4 | tests Devise::Generators::ViewsGenerator 5 | destination File.expand_path("../../tmp", __FILE__) 6 | setup :prepare_destination 7 | 8 | test "Assert all views are properly created with no params" do 9 | run_generator 10 | assert_files 11 | assert_shared_links 12 | end 13 | 14 | test "Assert all views are properly created with scope param" do 15 | run_generator %w(users) 16 | assert_files "users" 17 | assert_shared_links "users" 18 | 19 | run_generator %w(admins) 20 | assert_files "admins" 21 | assert_shared_links "admins" 22 | end 23 | 24 | test "Assert views with simple form" do 25 | run_generator %w(-b simple_form_for) 26 | assert_files 27 | assert_file "app/views/devise/confirmations/new.html.erb", /simple_form_for/ 28 | 29 | run_generator %w(users -b simple_form_for) 30 | assert_files "users" 31 | assert_file "app/views/users/confirmations/new.html.erb", /simple_form_for/ 32 | end 33 | 34 | test "Assert views with markerb" do 35 | run_generator %w(--markerb) 36 | assert_files nil, :mail_template_engine => "markerb" 37 | end 38 | 39 | def assert_files(scope = nil, options={}) 40 | scope = "devise" if scope.nil? 41 | mail_template_engine = options[:mail_template_engine] || "html.erb" 42 | 43 | assert_file "app/views/#{scope}/confirmations/new.html.erb" 44 | assert_file "app/views/#{scope}/mailer/confirmation_instructions.#{mail_template_engine}" 45 | assert_file "app/views/#{scope}/mailer/reset_password_instructions.#{mail_template_engine}" 46 | assert_file "app/views/#{scope}/mailer/unlock_instructions.#{mail_template_engine}" 47 | assert_file "app/views/#{scope}/passwords/edit.html.erb" 48 | assert_file "app/views/#{scope}/passwords/new.html.erb" 49 | assert_file "app/views/#{scope}/registrations/new.html.erb" 50 | assert_file "app/views/#{scope}/registrations/edit.html.erb" 51 | assert_file "app/views/#{scope}/sessions/new.html.erb" 52 | assert_file "app/views/#{scope}/shared/_links.erb" 53 | assert_file "app/views/#{scope}/unlocks/new.html.erb" 54 | end 55 | 56 | def assert_shared_links(scope = nil) 57 | scope = "devise" if scope.nil? 58 | link = /<%= render \"#{scope}\/shared\/links\" %>/ 59 | 60 | assert_file "app/views/#{scope}/passwords/edit.html.erb", link 61 | assert_file "app/views/#{scope}/passwords/new.html.erb", link 62 | assert_file "app/views/#{scope}/confirmations/new.html.erb", link 63 | assert_file "app/views/#{scope}/registrations/new.html.erb", link 64 | assert_file "app/views/#{scope}/sessions/new.html.erb", link 65 | assert_file "app/views/#{scope}/unlocks/new.html.erb", link 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/mailers/reset_password_instructions_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ResetPasswordInstructionsTest < ActionMailer::TestCase 4 | def setup 5 | setup_mailer 6 | Devise.mailer = 'Devise::Mailer' 7 | Devise.mailer_sender = 'test@example.com' 8 | end 9 | 10 | def teardown 11 | Devise.mailer = 'Devise::Mailer' 12 | Devise.mailer_sender = 'please-change-me@config-initializers-devise.com' 13 | end 14 | 15 | def user 16 | @user ||= begin 17 | user = create_user 18 | user.send_reset_password_instructions 19 | user 20 | end 21 | end 22 | 23 | def mail 24 | @mail ||= begin 25 | user 26 | ActionMailer::Base.deliveries.last 27 | end 28 | end 29 | 30 | test 'email sent after reseting the user password' do 31 | assert_not_nil mail 32 | end 33 | 34 | test 'content type should be set to html' do 35 | assert mail.content_type.include?('text/html') 36 | end 37 | 38 | test 'send confirmation instructions to the user email' do 39 | assert_equal [user.email], mail.to 40 | end 41 | 42 | test 'setup sender from configuration' do 43 | assert_equal ['test@example.com'], mail.from 44 | end 45 | 46 | test 'setup sender from custom mailer defaults' do 47 | Devise.mailer = 'Users::Mailer' 48 | assert_equal ['custom@example.com'], mail.from 49 | end 50 | 51 | test 'custom mailer renders parent mailer template' do 52 | Devise.mailer = 'Users::Mailer' 53 | assert_not_blank mail.body.encoded 54 | end 55 | 56 | test 'setup reply to as copy from sender' do 57 | assert_equal ['test@example.com'], mail.reply_to 58 | end 59 | 60 | test 'setup subject from I18n' do 61 | store_translations :en, :devise => { :mailer => { :reset_password_instructions => { :subject => 'Reset instructions' } } } do 62 | assert_equal 'Reset instructions', mail.subject 63 | end 64 | end 65 | 66 | test 'subject namespaced by model' do 67 | store_translations :en, :devise => { :mailer => { :reset_password_instructions => { :user_subject => 'User Reset Instructions' } } } do 68 | assert_equal 'User Reset Instructions', mail.subject 69 | end 70 | end 71 | 72 | test 'body should have user info' do 73 | assert_match user.email, mail.body.encoded 74 | end 75 | 76 | test 'body should have link to confirm the account' do 77 | host = ActionMailer::Base.default_url_options[:host] 78 | reset_url_regexp = %r{} 79 | assert_match reset_url_regexp, mail.body.encoded 80 | end 81 | 82 | test 'mailer sender accepts a proc' do 83 | swap Devise, :mailer_sender => proc { "another@example.com" } do 84 | assert_equal ['another@example.com'], mail.from 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/generators/active_record_generator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | if DEVISE_ORM == :active_record 4 | require "generators/active_record/devise_generator" 5 | 6 | class ActiveRecordGeneratorTest < Rails::Generators::TestCase 7 | tests ActiveRecord::Generators::DeviseGenerator 8 | destination File.expand_path("../../tmp", __FILE__) 9 | setup :prepare_destination 10 | 11 | test "all files are properly created with rails31 migration syntax" do 12 | run_generator %w(monster) 13 | assert_file "app/models/monster.rb", /devise/, /attr_accessible (:[a-z_]+(, )?)+/ 14 | assert_migration "db/migrate/devise_create_monsters.rb", /def change/ 15 | end 16 | 17 | test "all files for namespaced model are properly created" do 18 | run_generator %w(admin/monster) 19 | assert_file "app/models/admin/monster.rb", /devise/, /attr_accessible (:[a-z_]+(, )?)+/ 20 | assert_migration "db/migrate/devise_create_admin_monsters.rb", /def change/ 21 | end 22 | 23 | test "update model migration when model exists" do 24 | run_generator %w(monster) 25 | assert_file "app/models/monster.rb" 26 | run_generator %w(monster) 27 | assert_migration "db/migrate/add_devise_to_monsters.rb" 28 | end 29 | 30 | test "all files are properly deleted" do 31 | run_generator %w(monster) 32 | run_generator %w(monster) 33 | assert_migration "db/migrate/devise_create_monsters.rb" 34 | assert_migration "db/migrate/add_devise_to_monsters.rb" 35 | run_generator %w(monster), :behavior => :revoke 36 | assert_no_migration "db/migrate/add_devise_to_monsters.rb" 37 | assert_migration "db/migrate/devise_create_monsters.rb" 38 | run_generator %w(monster), :behavior => :revoke 39 | assert_no_file "app/models/monster.rb" 40 | assert_no_migration "db/migrate/devise_create_monsters.rb" 41 | end 42 | end 43 | 44 | module RailsEngine 45 | class Engine < Rails::Engine 46 | isolate_namespace RailsEngine 47 | end 48 | end 49 | 50 | def simulate_inside_engine(engine, namespace) 51 | if Rails::Generators.respond_to?(:namespace=) 52 | swap Rails::Generators, :namespace => namespace do 53 | yield 54 | end 55 | else 56 | swap Rails, :application => engine.instance do 57 | yield 58 | end 59 | end 60 | end 61 | 62 | class ActiveRecordEngineGeneratorTest < Rails::Generators::TestCase 63 | tests ActiveRecord::Generators::DeviseGenerator 64 | destination File.expand_path("../../tmp", __FILE__) 65 | setup :prepare_destination 66 | 67 | test "all files are properly created" do 68 | simulate_inside_engine(RailsEngine::Engine, RailsEngine) do 69 | run_generator ["monster"] 70 | 71 | assert_file "app/models/rails_engine/monster.rb", /devise/,/attr_accessible (:[a-z_]+(, )?)+/ 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/generators/active_record/devise_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/active_record' 2 | require 'generators/devise/orm_helpers' 3 | 4 | module ActiveRecord 5 | module Generators 6 | class DeviseGenerator < ActiveRecord::Generators::Base 7 | argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" 8 | 9 | include Devise::Generators::OrmHelpers 10 | source_root File.expand_path("../templates", __FILE__) 11 | 12 | def copy_devise_migration 13 | if (behavior == :invoke && model_exists?) || (behavior == :revoke && migration_exists?(table_name)) 14 | migration_template "migration_existing.rb", "db/migrate/add_devise_to_#{table_name}" 15 | else 16 | migration_template "migration.rb", "db/migrate/devise_create_#{table_name}" 17 | end 18 | end 19 | 20 | def generate_model 21 | invoke "active_record:model", [name], :migration => false unless model_exists? && behavior == :invoke 22 | end 23 | 24 | def inject_devise_content 25 | content = model_contents + < false, :default => "" 46 | t.string :encrypted_password, :null => false, :default => "" 47 | 48 | ## Recoverable 49 | t.string :reset_password_token 50 | t.datetime :reset_password_sent_at 51 | 52 | ## Rememberable 53 | t.datetime :remember_created_at 54 | 55 | ## Trackable 56 | t.integer :sign_in_count, :default => 0 57 | t.datetime :current_sign_in_at 58 | t.datetime :last_sign_in_at 59 | t.string :current_sign_in_ip 60 | t.string :last_sign_in_ip 61 | 62 | ## Confirmable 63 | # t.string :confirmation_token 64 | # t.datetime :confirmed_at 65 | # t.datetime :confirmation_sent_at 66 | # t.string :unconfirmed_email # Only if using reconfirmable 67 | 68 | ## Lockable 69 | # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts 70 | # t.string :unlock_token # Only if unlock strategy is :email or :both 71 | # t.datetime :locked_at 72 | 73 | ## Token authenticatable 74 | # t.string :authentication_token 75 | RUBY 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /test/integration/database_authenticatable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DatabaseAuthenticationTest < ActionDispatch::IntegrationTest 4 | test 'sign in with email of different case should succeed when email is in the list of case insensitive keys' do 5 | create_user(:email => 'Foo@Bar.com') 6 | 7 | sign_in_as_user do 8 | fill_in 'email', :with => 'foo@bar.com' 9 | end 10 | 11 | assert warden.authenticated?(:user) 12 | end 13 | 14 | test 'sign in with email of different case should fail when email is NOT the list of case insensitive keys' do 15 | swap Devise, :case_insensitive_keys => [] do 16 | create_user(:email => 'Foo@Bar.com') 17 | 18 | sign_in_as_user do 19 | fill_in 'email', :with => 'foo@bar.com' 20 | end 21 | 22 | assert_not warden.authenticated?(:user) 23 | end 24 | end 25 | 26 | test 'sign in with email including extra spaces should succeed when email is in the list of strip whitespace keys' do 27 | create_user(:email => ' foo@bar.com ') 28 | 29 | sign_in_as_user do 30 | fill_in 'email', :with => 'foo@bar.com' 31 | end 32 | 33 | assert warden.authenticated?(:user) 34 | end 35 | 36 | test 'sign in with email including extra spaces should fail when email is NOT the list of strip whitespace keys' do 37 | swap Devise, :strip_whitespace_keys => [] do 38 | create_user(:email => 'foo@bar.com') 39 | 40 | sign_in_as_user do 41 | fill_in 'email', :with => ' foo@bar.com ' 42 | end 43 | 44 | assert_not warden.authenticated?(:user) 45 | end 46 | end 47 | 48 | test 'sign in should not authenticate if not using proper authentication keys' do 49 | swap Devise, :authentication_keys => [:username] do 50 | sign_in_as_user 51 | assert_not warden.authenticated?(:user) 52 | end 53 | end 54 | 55 | test 'sign in with invalid email should return to sign in form with error message' do 56 | store_translations :en, :devise => { :failure => { :admin => { :not_found_in_database => 'Invalid email address' } } } do 57 | sign_in_as_admin do 58 | fill_in 'email', :with => 'wrongemail@test.com' 59 | end 60 | 61 | assert_contain 'Invalid email address' 62 | assert_not warden.authenticated?(:admin) 63 | end 64 | end 65 | 66 | test 'sign in with invalid pasword should return to sign in form with error message' do 67 | sign_in_as_admin do 68 | fill_in 'password', :with => 'abcdef' 69 | end 70 | 71 | assert_contain 'Invalid email or password' 72 | assert_not warden.authenticated?(:admin) 73 | end 74 | 75 | test 'error message is configurable by resource name' do 76 | store_translations :en, :devise => { :failure => { :admin => { :invalid => "Invalid credentials" } } } do 77 | sign_in_as_admin do 78 | fill_in 'password', :with => 'abcdef' 79 | end 80 | 81 | assert_contain 'Invalid credentials' 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/support/integration.rb: -------------------------------------------------------------------------------- 1 | require 'action_dispatch/testing/integration' 2 | 3 | class ActionDispatch::IntegrationTest 4 | def warden 5 | request.env['warden'] 6 | end 7 | 8 | def create_user(options={}) 9 | @user ||= begin 10 | user = User.create!( 11 | :username => 'usertest', 12 | :email => options[:email] || 'user@test.com', 13 | :password => options[:password] || '12345678', 14 | :password_confirmation => options[:password] || '12345678', 15 | :created_at => Time.now.utc 16 | ) 17 | user.update_attribute(:confirmation_sent_at, options[:confirmation_sent_at]) if options[:confirmation_sent_at] 18 | user.confirm! unless options[:confirm] == false 19 | user.lock_access! if options[:locked] == true 20 | user 21 | end 22 | end 23 | 24 | def create_admin(options={}) 25 | @admin ||= begin 26 | admin = Admin.create!( 27 | :email => options[:email] || 'admin@test.com', 28 | :password => '123456', :password_confirmation => '123456', 29 | :active => options[:active] 30 | ) 31 | admin.confirm! unless options[:confirm] == false 32 | admin 33 | end 34 | end 35 | 36 | def sign_in_as_user(options={}, &block) 37 | user = create_user(options) 38 | visit_with_option options[:visit], new_user_session_path 39 | fill_in 'email', :with => options[:email] || 'user@test.com' 40 | fill_in 'password', :with => options[:password] || '12345678' 41 | check 'remember me' if options[:remember_me] == true 42 | yield if block_given? 43 | click_button 'Sign In' 44 | user 45 | end 46 | 47 | def sign_in_as_admin(options={}, &block) 48 | admin = create_admin(options) 49 | visit_with_option options[:visit], new_admin_session_path 50 | fill_in 'email', :with => 'admin@test.com' 51 | fill_in 'password', :with => '123456' 52 | yield if block_given? 53 | click_button 'Sign In' 54 | admin 55 | end 56 | 57 | # Fix assert_redirect_to in integration sessions because they don't take into 58 | # account Middleware redirects. 59 | # 60 | def assert_redirected_to(url) 61 | assert [301, 302].include?(@integration_session.status), 62 | "Expected status to be 301 or 302, got #{@integration_session.status}" 63 | 64 | assert_url url, @integration_session.headers["Location"] 65 | end 66 | 67 | def assert_current_url(expected) 68 | assert_url expected, current_url 69 | end 70 | 71 | def assert_url(expected, actual) 72 | assert_equal prepend_host(expected), prepend_host(actual) 73 | end 74 | 75 | protected 76 | 77 | def visit_with_option(given, default) 78 | case given 79 | when String 80 | visit given 81 | when FalseClass 82 | # Do nothing 83 | else 84 | visit default 85 | end 86 | end 87 | 88 | def prepend_host(url) 89 | url = "http://#{request.host}#{url}" if url[0] == ?/ 90 | url 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /test/devise_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Devise 4 | def self.yield_and_restore 5 | @@warden_configured = nil 6 | c, b = @@warden_config, @@warden_config_block 7 | yield 8 | ensure 9 | @@warden_config, @@warden_config_block = c, b 10 | end 11 | end 12 | 13 | class DeviseTest < ActiveSupport::TestCase 14 | test 'model options can be configured through Devise' do 15 | swap Devise, :allow_unconfirmed_access_for => 113, :pepper => "foo" do 16 | assert_equal 113, Devise.allow_unconfirmed_access_for 17 | assert_equal "foo", Devise.pepper 18 | end 19 | end 20 | 21 | test 'setup block yields self' do 22 | Devise.setup do |config| 23 | assert_equal Devise, config 24 | end 25 | end 26 | 27 | test 'stores warden configuration' do 28 | assert_kind_of Devise::Delegator, Devise.warden_config.failure_app 29 | assert_equal :user, Devise.warden_config.default_scope 30 | end 31 | 32 | test 'warden manager user configuration through a block' do 33 | Devise.yield_and_restore do 34 | @executed = false 35 | Devise.warden do |config| 36 | @executed = true 37 | assert_kind_of Warden::Config, config 38 | end 39 | 40 | Devise.configure_warden! 41 | assert @executed 42 | end 43 | end 44 | 45 | test 'add new module using the helper method' do 46 | assert_nothing_raised(Exception) { Devise.add_module(:coconut) } 47 | assert_equal 1, Devise::ALL.select { |v| v == :coconut }.size 48 | assert_not Devise::STRATEGIES.include?(:coconut) 49 | assert_not defined?(Devise::Models::Coconut) 50 | Devise::ALL.delete(:coconut) 51 | 52 | assert_nothing_raised(Exception) { Devise.add_module(:banana, :strategy => :fruits) } 53 | assert_equal :fruits, Devise::STRATEGIES[:banana] 54 | Devise::ALL.delete(:banana) 55 | Devise::STRATEGIES.delete(:banana) 56 | 57 | assert_nothing_raised(Exception) { Devise.add_module(:kivi, :controller => :fruits) } 58 | assert_equal :fruits, Devise::CONTROLLERS[:kivi] 59 | Devise::ALL.delete(:kivi) 60 | Devise::CONTROLLERS.delete(:kivi) 61 | end 62 | 63 | test 'should complain when comparing empty or different sized passes' do 64 | [nil, ""].each do |empty| 65 | assert_not Devise.secure_compare(empty, "something") 66 | assert_not Devise.secure_compare("something", empty) 67 | assert_not Devise.secure_compare(empty, empty) 68 | end 69 | assert_not Devise.secure_compare("size_1", "size_four") 70 | end 71 | 72 | test 'Devise.email_regexp should match valid email addresses' do 73 | valid_emails = ["test@example.com", "jo@jo.co", "f4$_m@you.com", "testing.example@example.com.ua"] 74 | non_valid_emails = ["rex", "test@go,com", "test user@example.com", "test_user@example server.com"] 75 | 76 | valid_emails.each do |email| 77 | assert_match Devise.email_regexp, email 78 | end 79 | non_valid_emails.each do |email| 80 | assert_no_match Devise.email_regexp, email 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/devise/mailers/helpers.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Mailers 3 | module Helpers 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | include Devise::Controllers::ScopedViews 8 | attr_reader :scope_name, :resource 9 | end 10 | 11 | protected 12 | 13 | # Configure default email options 14 | def devise_mail(record, action, opts={}) 15 | initialize_from_record(record) 16 | mail headers_for(action, opts) 17 | end 18 | 19 | def initialize_from_record(record) 20 | @scope_name = Devise::Mapping.find_scope!(record) 21 | @resource = instance_variable_set("@#{devise_mapping.name}", record) 22 | end 23 | 24 | def devise_mapping 25 | @devise_mapping ||= Devise.mappings[scope_name] 26 | end 27 | 28 | def headers_for(action, opts) 29 | headers = { 30 | :subject => subject_for(action), 31 | :to => resource.email, 32 | :from => mailer_sender(devise_mapping), 33 | :reply_to => mailer_reply_to(devise_mapping), 34 | :template_path => template_paths, 35 | :template_name => action 36 | }.merge(opts) 37 | 38 | if resource.respond_to?(:headers_for) 39 | ActiveSupport::Deprecation.warn "Calling headers_for in the model is no longer supported. " << 40 | "Please customize your mailer instead." 41 | headers.merge!(resource.headers_for(action)) 42 | end 43 | 44 | @email = headers[:to] 45 | headers 46 | end 47 | 48 | def mailer_reply_to(mapping) 49 | mailer_sender(mapping, :reply_to) 50 | end 51 | 52 | def mailer_from(mapping) 53 | mailer_sender(mapping, :from) 54 | end 55 | 56 | def mailer_sender(mapping, sender = :from) 57 | if default_params[sender].present? 58 | default_params[sender] 59 | elsif Devise.mailer_sender.is_a?(Proc) 60 | Devise.mailer_sender.call(mapping.name) 61 | else 62 | Devise.mailer_sender 63 | end 64 | end 65 | 66 | def template_paths 67 | template_path = _prefixes.dup 68 | template_path.unshift "#{@devise_mapping.scoped_path}/mailer" if self.class.scoped_views? 69 | template_path 70 | end 71 | 72 | # Setup a subject doing an I18n lookup. At first, it attemps to set a subject 73 | # based on the current mapping: 74 | # 75 | # en: 76 | # devise: 77 | # mailer: 78 | # confirmation_instructions: 79 | # user_subject: '...' 80 | # 81 | # If one does not exist, it fallbacks to ActionMailer default: 82 | # 83 | # en: 84 | # devise: 85 | # mailer: 86 | # confirmation_instructions: 87 | # subject: '...' 88 | # 89 | def subject_for(key) 90 | I18n.t(:"#{devise_mapping.name}_subject", :scope => [:devise, :mailer, key], 91 | :default => [:subject, key.to_s.humanize]) 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsControllerTest < ActionController::TestCase 4 | tests Devise::SessionsController 5 | include Devise::TestHelpers 6 | 7 | test "#create works even with scoped views" do 8 | swap Devise, :scoped_views => true do 9 | request.env["devise.mapping"] = Devise.mappings[:user] 10 | post :create 11 | assert_equal 200, @response.status 12 | assert_template "users/sessions/new" 13 | end 14 | end 15 | 16 | test "#create delete the url stored in the session if the requested format is navigational" do 17 | request.env["devise.mapping"] = Devise.mappings[:user] 18 | request.session["user_return_to"] = 'foo.bar' 19 | 20 | user = create_user 21 | user.confirm! 22 | post :create, :user => { 23 | :email => user.email, 24 | :password => user.password 25 | } 26 | 27 | assert_nil request.session["user_return_to"] 28 | end 29 | 30 | test "#create doesn't delete the url stored in the session if the requested format is not navigational" do 31 | request.env["devise.mapping"] = Devise.mappings[:user] 32 | request.session["user_return_to"] = 'foo.bar' 33 | 34 | user = create_user 35 | user.confirm! 36 | post :create, :format => 'json', :user => { 37 | :email => user.email, 38 | :password => user.password 39 | } 40 | 41 | assert_equal 'foo.bar', request.session["user_return_to"] 42 | end 43 | 44 | test "#create doesn't raise exception after Warden authentication fails when TestHelpers included" do 45 | request.env["devise.mapping"] = Devise.mappings[:user] 46 | post :create, :user => { 47 | :email => "nosuchuser@example.com", 48 | :password => "wevdude" 49 | } 50 | assert_equal 200, @response.status 51 | assert_template "devise/sessions/new" 52 | end 53 | 54 | test "#destroy doesn't set the flash if the requested format is not navigational" do 55 | request.env["devise.mapping"] = Devise.mappings[:user] 56 | user = create_user 57 | user.confirm! 58 | post :create, :format => 'json', :user => { 59 | :email => user.email, 60 | :password => user.password 61 | } 62 | 63 | delete :destroy, :format => 'json' 64 | assert flash[:notice].blank?, "flash[:notice] should be blank, not #{flash[:notice].inspect}" 65 | assert_equal 204, @response.status 66 | end 67 | 68 | if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:mass_assignment_sanitizer) 69 | test "#new doesn't raise mass-assignment exception even if sign-in key is attr_protected" do 70 | request.env["devise.mapping"] = Devise.mappings[:user] 71 | 72 | ActiveRecord::Base.mass_assignment_sanitizer = :strict 73 | User.class_eval { attr_protected :email } 74 | 75 | begin 76 | assert_nothing_raised ActiveModel::MassAssignmentSecurity::Error do 77 | get :new, :user => { :email => "allez viens!" } 78 | end 79 | ensure 80 | ActiveRecord::Base.mass_assignment_sanitizer = :logger 81 | User.class_eval { attr_accessible :email } 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/devise/models/token_authenticatable.rb: -------------------------------------------------------------------------------- 1 | require 'devise/strategies/token_authenticatable' 2 | 3 | module Devise 4 | module Models 5 | # The TokenAuthenticatable module is responsible for generating an authentication token and 6 | # validating the authenticity of the same while signing in. 7 | # 8 | # This module only provides a few helpers to help you manage the token, but it is up to you 9 | # to choose how to use it. For example, if you want to have a new token every time the user 10 | # saves his account, you can do the following: 11 | # 12 | # before_save :reset_authentication_token 13 | # 14 | # On the other hand, if you want to generate token unless one exists, you should use instead: 15 | # 16 | # before_save :ensure_authentication_token 17 | # 18 | # If you want to delete the token after it is used, you can do so in the 19 | # after_token_authentication callback. 20 | # 21 | # == APIs 22 | # 23 | # If you are using token authentication with APIs and using trackable. Every 24 | # request will be considered as a new sign in (since there is no session in 25 | # APIs). You can disable this by creating a before filter as follow: 26 | # 27 | # before_filter :skip_trackable 28 | # 29 | # def skip_trackable 30 | # request.env['devise.skip_trackable'] = true 31 | # end 32 | # 33 | # == Options 34 | # 35 | # TokenAuthenticatable adds the following options to devise_for: 36 | # 37 | # * +token_authentication_key+: Defines name of the authentication token params key. E.g. /users/sign_in?some_key=... 38 | # 39 | module TokenAuthenticatable 40 | extend ActiveSupport::Concern 41 | 42 | def self.required_fields(klass) 43 | [:authentication_token] 44 | end 45 | 46 | # Generate new authentication token (a.k.a. "single access token"). 47 | def reset_authentication_token 48 | self.authentication_token = self.class.authentication_token 49 | end 50 | 51 | # Generate new authentication token and save the record. 52 | def reset_authentication_token! 53 | reset_authentication_token 54 | save(:validate => false) 55 | end 56 | 57 | # Generate authentication token unless already exists. 58 | def ensure_authentication_token 59 | reset_authentication_token if authentication_token.blank? 60 | end 61 | 62 | # Generate authentication token unless already exists and save the record. 63 | def ensure_authentication_token! 64 | reset_authentication_token! if authentication_token.blank? 65 | end 66 | 67 | # Hook called after token authentication. 68 | def after_token_authentication 69 | end 70 | 71 | def expire_auth_token_on_timeout 72 | self.class.expire_auth_token_on_timeout 73 | end 74 | 75 | module ClassMethods 76 | def find_for_token_authentication(conditions) 77 | find_for_authentication(:authentication_token => conditions[token_authentication_key]) 78 | end 79 | 80 | # Generate a token checking if one does not already exist in the database. 81 | def authentication_token 82 | generate_token(:authentication_token) 83 | end 84 | 85 | Devise::Models.config(self, :token_authentication_key, :expire_auth_token_on_timeout) 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /test/mailers/confirmation_instructions_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConfirmationInstructionsTest < ActionMailer::TestCase 4 | 5 | def setup 6 | setup_mailer 7 | Devise.mailer = 'Devise::Mailer' 8 | Devise.mailer_sender = 'test@example.com' 9 | end 10 | 11 | def teardown 12 | Devise.mailer = 'Devise::Mailer' 13 | Devise.mailer_sender = 'please-change-me@config-initializers-devise.com' 14 | end 15 | 16 | def user 17 | @user ||= create_user 18 | end 19 | 20 | def mail 21 | @mail ||= begin 22 | user 23 | ActionMailer::Base.deliveries.first 24 | end 25 | end 26 | 27 | test 'email sent after creating the user' do 28 | assert_not_nil mail 29 | end 30 | 31 | test 'content type should be set to html' do 32 | assert mail.content_type.include?('text/html') 33 | end 34 | 35 | test 'send confirmation instructions to the user email' do 36 | mail 37 | assert_equal [user.email], mail.to 38 | end 39 | 40 | test 'setup sender from configuration' do 41 | assert_equal ['test@example.com'], mail.from 42 | end 43 | 44 | test 'setup sender from custom mailer defaults' do 45 | Devise.mailer = 'Users::Mailer' 46 | assert_equal ['custom@example.com'], mail.from 47 | end 48 | 49 | test 'custom mailer renders parent mailer template' do 50 | Devise.mailer = 'Users::Mailer' 51 | assert_not_blank mail.body.encoded 52 | end 53 | 54 | test 'setup reply to as copy from sender' do 55 | assert_equal ['test@example.com'], mail.reply_to 56 | end 57 | 58 | test 'setup reply to as different if set in defaults' do 59 | Devise.mailer = 'Users::ReplyToMailer' 60 | assert_equal ['custom@example.com'], mail.from 61 | assert_equal ['custom_reply_to@example.com'], mail.reply_to 62 | end 63 | 64 | test 'setup subject from I18n' do 65 | store_translations :en, :devise => { :mailer => { :confirmation_instructions => { :subject => 'Account Confirmation' } } } do 66 | assert_equal 'Account Confirmation', mail.subject 67 | end 68 | end 69 | 70 | test 'subject namespaced by model' do 71 | store_translations :en, :devise => { :mailer => { :confirmation_instructions => { :user_subject => 'User Account Confirmation' } } } do 72 | assert_equal 'User Account Confirmation', mail.subject 73 | end 74 | end 75 | 76 | test 'body should have user info' do 77 | assert_match user.email, mail.body.encoded 78 | end 79 | 80 | test 'body should have link to confirm the account' do 81 | host = ActionMailer::Base.default_url_options[:host] 82 | confirmation_url_regexp = %r{} 83 | assert_match confirmation_url_regexp, mail.body.encoded 84 | end 85 | 86 | test 'renders a scoped if scoped_views is set to true' do 87 | swap Devise, :scoped_views => true do 88 | assert_equal user.email, mail.body.decoded 89 | end 90 | end 91 | 92 | test 'renders a scoped if scoped_views is set in the mailer class' do 93 | begin 94 | Devise::Mailer.scoped_views = true 95 | assert_equal user.email, mail.body.decoded 96 | ensure 97 | Devise::Mailer.send :remove_instance_variable, :@scoped_views 98 | end 99 | end 100 | 101 | test 'mailer sender accepts a proc' do 102 | swap Devise, :mailer_sender => proc { "another@example.com" } do 103 | assert_equal ['another@example.com'], mail.from 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /test/rails_app/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Resources for testing 3 | resources :users, :only => [:index] do 4 | get :expire, :on => :member 5 | get :accept, :on => :member 6 | 7 | authenticate do 8 | post :exhibit, :on => :member 9 | end 10 | end 11 | 12 | resources :admins, :only => [:index] do 13 | get :expire, :on => :member 14 | end 15 | 16 | # Users scope 17 | devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" } 18 | 19 | as :user do 20 | get "/as/sign_in", :to => "devise/sessions#new" 21 | end 22 | 23 | get "/sign_in", :to => "devise/sessions#new" 24 | 25 | # Admin scope 26 | devise_for :admin, :path => "admin_area", :controllers => { :sessions => :"admins/sessions" }, :skip => :passwords 27 | 28 | get "/admin_area/home", :to => "admins#index", :as => :admin_root 29 | get "/anywhere", :to => "foo#bar", :as => :new_admin_password 30 | 31 | authenticate(:admin) do 32 | get "/private", :to => "home#private", :as => :private 33 | end 34 | 35 | authenticate(:admin, lambda { |admin| admin.active? }) do 36 | get "/private/active", :to => "home#private", :as => :private_active 37 | end 38 | 39 | authenticated :admin do 40 | get "/dashboard", :to => "home#admin_dashboard" 41 | end 42 | 43 | authenticated :admin, lambda { |admin| admin.active? } do 44 | get "/dashboard/active", :to => "home#admin_dashboard" 45 | end 46 | 47 | authenticated do 48 | get "/dashboard", :to => "home#user_dashboard" 49 | end 50 | 51 | unauthenticated do 52 | get "/join", :to => "home#join" 53 | end 54 | 55 | # Routes for constraints testing 56 | devise_for :headquarters_admin, :class_name => "Admin", :path => "headquarters", :constraints => {:host => /192\.168\.1\.\d\d\d/} 57 | 58 | constraints(:host => /192\.168\.1\.\d\d\d/) do 59 | devise_for :homebase_admin, :class_name => "Admin", :path => "homebase" 60 | end 61 | 62 | devise_for :skip_admin, :class_name => "Admin", :skip => :all 63 | 64 | # Routes for format=false testing 65 | devise_for :htmlonly_admin, :class_name => "Admin", :skip => [:confirmations, :unlocks], :path => "htmlonly_admin", :format => false, :skip_helpers => [:confirmations, :unlocks] 66 | devise_for :htmlonly_users, :class_name => "User", :only => [:confirmations, :unlocks], :path => "htmlonly_users", :format => false, :skip_helpers => true 67 | 68 | # Other routes for routing_test.rb 69 | devise_for :reader, :class_name => "User", :only => :passwords 70 | 71 | scope :host => "sub.example.com" do 72 | devise_for :sub_admin, :class_name => "Admin" 73 | end 74 | 75 | namespace :publisher, :path_names => { :sign_in => "i_dont_care", :sign_out => "get_out" } do 76 | devise_for :accounts, :class_name => "Admin", :path_names => { :sign_in => "get_in" } 77 | end 78 | 79 | scope ":locale", :module => :invalid do 80 | devise_for :accounts, :singular => "manager", :class_name => "Admin", 81 | :path_names => { 82 | :sign_in => "login", :sign_out => "logout", 83 | :password => "secret", :confirmation => "verification", 84 | :unlock => "unblock", :sign_up => "register", 85 | :registration => "management", :cancel => "giveup" 86 | }, :failure_app => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["Oops, not found"]] }, :module => :devise 87 | end 88 | 89 | namespace :sign_out_via, :module => "devise" do 90 | devise_for :deletes, :sign_out_via => :delete, :class_name => "Admin" 91 | devise_for :posts, :sign_out_via => :post, :class_name => "Admin" 92 | devise_for :delete_or_posts, :sign_out_via => [:delete, :post], :class_name => "Admin" 93 | end 94 | 95 | get "/set", :to => "home#set" 96 | get "/unauthenticated", :to => "home#unauthenticated" 97 | get "/custom_strategy/new" 98 | 99 | root :to => "home#index" 100 | end 101 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your account was successfully confirmed. You are now signed in." 7 | send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account was not activated yet." 12 | invalid: "Invalid email or password." 13 | invalid_token: "Invalid authentication token." 14 | locked: "Your account is locked." 15 | not_found_in_database: "Invalid email or password." 16 | timeout: "Your session expired, please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your account before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock Instructions" 26 | omniauth_callbacks: 27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 28 | success: "Successfully authenticated from %{kind} account." 29 | passwords: 30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 31 | send_instructions: "You will receive an email with instructions about how to reset your password in a few minutes." 32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 33 | updated: "Your password was changed successfully. You are now signed in." 34 | updated_not_active: "Your password was changed successfully." 35 | registrations: 36 | destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon." 37 | signed_up: "Welcome! You have signed up successfully." 38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." 41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." 42 | updated: "You updated your account successfully." 43 | sessions: 44 | signed_in: "Signed in successfully." 45 | signed_out: "Signed out successfully." 46 | unlocks: 47 | send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes." 48 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes." 49 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 50 | errors: 51 | messages: 52 | already_confirmed: "was already confirmed, please try signing in" 53 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 54 | expired: "has expired, please request a new one" 55 | not_found: "not found" 56 | not_locked: "was not locked" 57 | not_saved: 58 | one: "1 error prohibited this %{resource} from being saved:" 59 | other: "%{count} errors prohibited this %{resource} from being saved:" 60 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | devise (2.2.3) 5 | bcrypt-ruby (~> 3.0) 6 | orm_adapter (~> 0.1) 7 | railties (~> 3.1) 8 | warden (~> 1.2.1) 9 | 10 | GEM 11 | remote: http://rubygems.org/ 12 | specs: 13 | actionmailer (3.2.11) 14 | actionpack (= 3.2.11) 15 | mail (~> 2.4.4) 16 | actionpack (3.2.11) 17 | activemodel (= 3.2.11) 18 | activesupport (= 3.2.11) 19 | builder (~> 3.0.0) 20 | erubis (~> 2.7.0) 21 | journey (~> 1.0.4) 22 | rack (~> 1.4.0) 23 | rack-cache (~> 1.2) 24 | rack-test (~> 0.6.1) 25 | sprockets (~> 2.2.1) 26 | activemodel (3.2.11) 27 | activesupport (= 3.2.11) 28 | builder (~> 3.0.0) 29 | activerecord (3.2.11) 30 | activemodel (= 3.2.11) 31 | activesupport (= 3.2.11) 32 | arel (~> 3.0.2) 33 | tzinfo (~> 0.3.29) 34 | activeresource (3.2.11) 35 | activemodel (= 3.2.11) 36 | activesupport (= 3.2.11) 37 | activesupport (3.2.11) 38 | i18n (~> 0.6) 39 | multi_json (~> 1.0) 40 | arel (3.0.2) 41 | bcrypt-ruby (3.0.1) 42 | builder (3.0.4) 43 | erubis (2.7.0) 44 | faraday (0.8.4) 45 | multipart-post (~> 1.1) 46 | hashie (1.2.0) 47 | hike (1.2.1) 48 | httpauth (0.2.0) 49 | i18n (0.6.1) 50 | journey (1.0.4) 51 | json (1.7.6) 52 | jwt (0.1.5) 53 | multi_json (>= 1.0) 54 | mail (2.4.4) 55 | i18n (>= 0.4.0) 56 | mime-types (~> 1.16) 57 | treetop (~> 1.4.8) 58 | metaclass (0.0.1) 59 | mime-types (1.19) 60 | mocha (0.10.0) 61 | metaclass (~> 0.0.1) 62 | mongoid (3.0.16) 63 | activemodel (~> 3.1) 64 | moped (~> 1.1) 65 | origin (~> 1.0) 66 | tzinfo (~> 0.3.22) 67 | moped (1.3.2) 68 | multi_json (1.5.0) 69 | multipart-post (1.1.5) 70 | nokogiri (1.5.5) 71 | oauth2 (0.8.0) 72 | faraday (~> 0.8) 73 | httpauth (~> 0.1) 74 | jwt (~> 0.1.4) 75 | multi_json (~> 1.0) 76 | rack (~> 1.2) 77 | omniauth (1.0.3) 78 | hashie (~> 1.2) 79 | rack 80 | omniauth-facebook (1.4.0) 81 | omniauth-oauth2 (~> 1.0.2) 82 | omniauth-oauth2 (1.0.3) 83 | oauth2 (~> 0.8.0) 84 | omniauth (~> 1.0) 85 | omniauth-openid (1.0.1) 86 | omniauth (~> 1.0) 87 | rack-openid (~> 1.3.1) 88 | origin (1.0.11) 89 | orm_adapter (0.4.0) 90 | polyglot (0.3.3) 91 | rack (1.4.3) 92 | rack-cache (1.2) 93 | rack (>= 0.4) 94 | rack-openid (1.3.1) 95 | rack (>= 1.1.0) 96 | ruby-openid (>= 2.1.8) 97 | rack-ssl (1.3.2) 98 | rack 99 | rack-test (0.6.2) 100 | rack (>= 1.0) 101 | rails (3.2.11) 102 | actionmailer (= 3.2.11) 103 | actionpack (= 3.2.11) 104 | activerecord (= 3.2.11) 105 | activeresource (= 3.2.11) 106 | activesupport (= 3.2.11) 107 | bundler (~> 1.0) 108 | railties (= 3.2.11) 109 | railties (3.2.11) 110 | actionpack (= 3.2.11) 111 | activesupport (= 3.2.11) 112 | rack-ssl (~> 1.3.2) 113 | rake (>= 0.8.7) 114 | rdoc (~> 3.4) 115 | thor (>= 0.14.6, < 2.0) 116 | rake (10.0.3) 117 | rdoc (3.12) 118 | json (~> 1.4) 119 | ruby-openid (2.2.2) 120 | sprockets (2.2.2) 121 | hike (~> 1.2) 122 | multi_json (~> 1.0) 123 | rack (~> 1.0) 124 | tilt (~> 1.1, != 1.3.0) 125 | sqlite3 (1.3.6) 126 | thor (0.16.0) 127 | tilt (1.3.3) 128 | treetop (1.4.12) 129 | polyglot 130 | polyglot (>= 0.3.1) 131 | tzinfo (0.3.35) 132 | warden (1.2.1) 133 | rack (>= 1.0) 134 | webrat (0.7.2) 135 | nokogiri (>= 1.2.0) 136 | rack (>= 1.0) 137 | rack-test (>= 0.5.3) 138 | 139 | PLATFORMS 140 | ruby 141 | 142 | DEPENDENCIES 143 | activerecord-jdbc-adapter 144 | activerecord-jdbcsqlite3-adapter 145 | devise! 146 | jruby-openssl 147 | mocha (= 0.10.0) 148 | mongoid (~> 3.0) 149 | omniauth (~> 1.0.0) 150 | omniauth-facebook 151 | omniauth-oauth2 (~> 1.0.0) 152 | omniauth-openid (~> 1.0.1) 153 | rails (~> 3.2.6) 154 | rdoc 155 | sqlite3 156 | webrat (= 0.7.2) 157 | -------------------------------------------------------------------------------- /test/integration/timeoutable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionTimeoutTest < ActionDispatch::IntegrationTest 4 | 5 | def last_request_at 6 | @controller.user_session['last_request_at'] 7 | end 8 | 9 | test 'set last request at in user session after each request' do 10 | sign_in_as_user 11 | old_last_request = last_request_at 12 | assert_not_nil last_request_at 13 | 14 | get users_path 15 | assert_not_nil last_request_at 16 | assert_not_equal old_last_request, last_request_at 17 | end 18 | 19 | test 'set last request at in user session after each request is skipped if tracking is disabled' do 20 | sign_in_as_user 21 | old_last_request = last_request_at 22 | assert_not_nil last_request_at 23 | 24 | get users_path, {}, 'devise.skip_trackable' => true 25 | assert_equal old_last_request, last_request_at 26 | end 27 | 28 | test 'does not time out user session before default limit time' do 29 | sign_in_as_user 30 | assert_response :success 31 | assert warden.authenticated?(:user) 32 | 33 | get users_path 34 | assert_response :success 35 | assert warden.authenticated?(:user) 36 | end 37 | 38 | test 'time out user session after default limit time' do 39 | user = sign_in_as_user 40 | get expire_user_path(user) 41 | assert_not_nil last_request_at 42 | 43 | get users_path 44 | assert_redirected_to users_path 45 | assert_not warden.authenticated?(:user) 46 | end 47 | 48 | test 'time out is not triggered on sign out' do 49 | user = sign_in_as_user 50 | get expire_user_path(user) 51 | 52 | get destroy_user_session_path 53 | 54 | assert_response :redirect 55 | assert_redirected_to root_path 56 | follow_redirect! 57 | assert_contain 'Signed out successfully' 58 | end 59 | 60 | test 'time out is not triggered on sign in' do 61 | user = sign_in_as_user 62 | get expire_user_path(user) 63 | 64 | post "/users/sign_in", :email => user.email, :password => "123456" 65 | 66 | assert_response :redirect 67 | follow_redirect! 68 | assert_contain 'You are signed in' 69 | end 70 | 71 | test 'admin does not explode on time out' do 72 | admin = sign_in_as_admin 73 | get expire_admin_path(admin) 74 | 75 | Admin.send :define_method, :reset_authentication_token! do 76 | nil 77 | end 78 | 79 | begin 80 | get admins_path 81 | assert_redirected_to admins_path 82 | assert_not warden.authenticated?(:admin) 83 | ensure 84 | Admin.send(:remove_method, :reset_authentication_token!) 85 | end 86 | end 87 | 88 | test 'user configured timeout limit' do 89 | swap Devise, :timeout_in => 8.minutes do 90 | user = sign_in_as_user 91 | 92 | get users_path 93 | assert_not_nil last_request_at 94 | assert_response :success 95 | assert warden.authenticated?(:user) 96 | 97 | get expire_user_path(user) 98 | get users_path 99 | assert_redirected_to users_path 100 | assert_not warden.authenticated?(:user) 101 | end 102 | end 103 | 104 | test 'error message with i18n' do 105 | store_translations :en, :devise => { 106 | :failure => { :user => { :timeout => 'Session expired!' } } 107 | } do 108 | user = sign_in_as_user 109 | 110 | get expire_user_path(user) 111 | get root_path 112 | follow_redirect! 113 | assert_contain 'Session expired!' 114 | end 115 | end 116 | 117 | test 'error message with i18n with double redirect' do 118 | store_translations :en, :devise => { 119 | :failure => { :user => { :timeout => 'Session expired!' } } 120 | } do 121 | user = sign_in_as_user 122 | 123 | get expire_user_path(user) 124 | get users_path 125 | follow_redirect! 126 | follow_redirect! 127 | assert_contain 'Session expired!' 128 | end 129 | end 130 | 131 | test 'time out not triggered if remembered' do 132 | user = sign_in_as_user :remember_me => true 133 | get expire_user_path(user) 134 | assert_not_nil last_request_at 135 | 136 | get users_path 137 | assert_response :success 138 | assert warden.authenticated?(:user) 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /test/test_helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TestHelpersTest < ActionController::TestCase 4 | tests UsersController 5 | include Devise::TestHelpers 6 | 7 | class CustomFailureApp < Devise::FailureApp 8 | def redirect 9 | self.status = 306 10 | end 11 | end 12 | 13 | test "redirects if attempting to access a page unauthenticated" do 14 | get :index 15 | assert_redirected_to new_user_session_path 16 | assert_equal "You need to sign in or sign up before continuing.", flash[:alert] 17 | end 18 | 19 | test "redirects if attempting to access a page with an unconfirmed account" do 20 | swap Devise, :allow_unconfirmed_access_for => 0 do 21 | user = create_user 22 | assert !user.active_for_authentication? 23 | 24 | sign_in user 25 | get :index 26 | assert_redirected_to new_user_session_path 27 | end 28 | end 29 | 30 | test "returns nil if accessing current_user with an unconfirmed account" do 31 | swap Devise, :allow_unconfirmed_access_for => 0 do 32 | user = create_user 33 | assert !user.active_for_authentication? 34 | 35 | sign_in user 36 | get :accept, :id => user 37 | assert_nil assigns(:current_user) 38 | end 39 | end 40 | 41 | test "does not redirect with valid user" do 42 | user = create_user 43 | user.confirm! 44 | 45 | sign_in user 46 | get :index 47 | assert_response :success 48 | end 49 | 50 | test "does not redirect with valid user after failed first attempt" do 51 | get :index 52 | assert_response :redirect 53 | 54 | user = create_user 55 | user.confirm! 56 | 57 | sign_in user 58 | get :index 59 | assert_response :success 60 | end 61 | 62 | test "redirects if valid user signed out" do 63 | user = create_user 64 | user.confirm! 65 | 66 | sign_in user 67 | get :index 68 | 69 | sign_out user 70 | get :index 71 | assert_redirected_to new_user_session_path 72 | end 73 | 74 | test "respects custom failure app" do 75 | begin 76 | Devise.warden_config.failure_app = CustomFailureApp 77 | get :index 78 | assert_response 306 79 | ensure 80 | Devise.warden_config.failure_app = Devise::FailureApp 81 | end 82 | end 83 | 84 | test "returns the body of a failure app" do 85 | get :index 86 | assert_equal response.body, "You are being redirected." 87 | end 88 | 89 | test "defined Warden after_authentication callback should not be called when sign_in is called" do 90 | begin 91 | Warden::Manager.after_authentication do |user, auth, opts| 92 | flunk "callback was called while it should not" 93 | end 94 | 95 | user = create_user 96 | user.confirm! 97 | sign_in user 98 | ensure 99 | Warden::Manager._after_set_user.pop 100 | end 101 | end 102 | 103 | test "defined Warden before_logout callback should not be called when sign_out is called" do 104 | begin 105 | Warden::Manager.before_logout do |user, auth, opts| 106 | flunk "callback was called while it should not" 107 | end 108 | user = create_user 109 | user.confirm! 110 | 111 | sign_in user 112 | sign_out user 113 | ensure 114 | Warden::Manager._before_logout.pop 115 | end 116 | end 117 | 118 | test "before_failure call should work" do 119 | begin 120 | executed = false 121 | Warden::Manager.before_failure do |env,opts| 122 | executed = true 123 | end 124 | 125 | user = create_user 126 | sign_in user 127 | 128 | get :index 129 | assert executed 130 | ensure 131 | Warden::Manager._before_failure.pop 132 | end 133 | end 134 | 135 | test "allows to sign in with different users" do 136 | first_user = create_user 137 | first_user.confirm! 138 | 139 | sign_in first_user 140 | get :index 141 | assert_match /User ##{first_user.id}/, @response.body 142 | sign_out first_user 143 | 144 | second_user = create_user 145 | second_user.confirm! 146 | 147 | sign_in second_user 148 | get :index 149 | assert_match /User ##{second_user.id}/, @response.body 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /lib/generators/devise/views_generator.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Generators 3 | # Include this module in your generator to generate Devise views. 4 | # `copy_views` is the main method and by default copies all views 5 | # with forms. 6 | module ViewPathTemplates #:nodoc: 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | argument :scope, :required => false, :default => nil, 11 | :desc => "The scope to copy views to" 12 | 13 | # Le sigh, ensure Thor won't handle opts as args 14 | # It should be fixed in future Rails releases 15 | class_option :form_builder, :aliases => "-b" 16 | class_option :markerb 17 | 18 | public_task :copy_views 19 | end 20 | 21 | # TODO: Add this to Rails itself 22 | module ClassMethods 23 | def hide! 24 | Rails::Generators.hide_namespace self.namespace 25 | end 26 | end 27 | 28 | def copy_views 29 | view_directory :confirmations 30 | view_directory :passwords 31 | view_directory :registrations 32 | view_directory :sessions 33 | view_directory :unlocks 34 | end 35 | 36 | protected 37 | 38 | def view_directory(name, _target_path = nil) 39 | directory name.to_s, _target_path || "#{target_path}/#{name}" do |content| 40 | if scope 41 | content.gsub "devise/shared/links", "#{scope}/shared/links" 42 | else 43 | content 44 | end 45 | end 46 | end 47 | 48 | def target_path 49 | @target_path ||= "app/views/#{scope || :devise}" 50 | end 51 | end 52 | 53 | class SharedViewsGenerator < Rails::Generators::Base #:nodoc: 54 | include ViewPathTemplates 55 | source_root File.expand_path("../../../../app/views/devise", __FILE__) 56 | desc "Copies shared Devise views to your application." 57 | hide! 58 | 59 | # Override copy_views to just copy mailer and shared. 60 | def copy_views 61 | view_directory :shared 62 | end 63 | end 64 | 65 | class FormForGenerator < Rails::Generators::Base #:nodoc: 66 | include ViewPathTemplates 67 | source_root File.expand_path("../../../../app/views/devise", __FILE__) 68 | desc "Copies default Devise views to your application." 69 | hide! 70 | end 71 | 72 | class SimpleFormForGenerator < Rails::Generators::Base #:nodoc: 73 | include ViewPathTemplates 74 | source_root File.expand_path("../../templates/simple_form_for", __FILE__) 75 | desc "Copies simple form enabled views to your application." 76 | hide! 77 | end 78 | 79 | class ErbGenerator < Rails::Generators::Base #:nodoc: 80 | include ViewPathTemplates 81 | source_root File.expand_path("../../../../app/views/devise", __FILE__) 82 | desc "Copies Devise mail erb views to your application." 83 | hide! 84 | 85 | def copy_views 86 | view_directory :mailer 87 | end 88 | end 89 | 90 | class MarkerbGenerator < Rails::Generators::Base #:nodoc: 91 | include ViewPathTemplates 92 | source_root File.expand_path("../../templates", __FILE__) 93 | desc "Copies Devise mail markerb views to your application." 94 | hide! 95 | 96 | def copy_views 97 | view_directory :markerb, target_path 98 | end 99 | 100 | def target_path 101 | "app/views/#{scope || :devise}/mailer" 102 | end 103 | end 104 | 105 | class ViewsGenerator < Rails::Generators::Base 106 | desc "Copies Devise views to your application." 107 | 108 | argument :scope, :required => false, :default => nil, 109 | :desc => "The scope to copy views to" 110 | 111 | invoke SharedViewsGenerator 112 | 113 | hook_for :form_builder, :aliases => "-b", 114 | :desc => "Form builder to be used", 115 | :default => defined?(SimpleForm) ? "simple_form_for" : "form_for" 116 | 117 | hook_for :markerb, :desc => "Generate markerb instead of erb mail views", 118 | :default => defined?(Markerb) ? :markerb : :erb, 119 | :type => :boolean 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /test/integration/omniauthable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | 4 | class OmniauthableIntegrationTest < ActionDispatch::IntegrationTest 5 | FACEBOOK_INFO = { 6 | "id" => '12345', 7 | "link" => 'http://facebook.com/josevalim', 8 | "email" => 'user@example.com', 9 | "first_name" => 'Jose', 10 | "last_name" => 'Valim', 11 | "website" => 'http://blog.plataformatec.com.br' 12 | } 13 | 14 | setup do 15 | OmniAuth.config.test_mode = true 16 | OmniAuth.config.mock_auth[:facebook] = { 17 | "uid" => '12345', 18 | "provider" => 'facebook', 19 | "user_info" => {"nickname" => 'josevalim'}, 20 | "credentials" => {"token" => 'plataformatec'}, 21 | "extra" => {"user_hash" => FACEBOOK_INFO} 22 | } 23 | end 24 | 25 | teardown do 26 | OmniAuth.config.test_mode = false 27 | end 28 | 29 | def stub_action!(name) 30 | Users::OmniauthCallbacksController.class_eval do 31 | alias_method :__old_facebook, :facebook 32 | alias_method :facebook, name 33 | end 34 | yield 35 | ensure 36 | Users::OmniauthCallbacksController.class_eval do 37 | alias_method :facebook, :__old_facebook 38 | end 39 | end 40 | 41 | test "can access omniauth.auth in the env hash" do 42 | visit "/users/sign_in" 43 | click_link "Sign in with Facebook" 44 | 45 | json = ActiveSupport::JSON.decode(response.body) 46 | 47 | assert_equal "12345", json["uid"] 48 | assert_equal "facebook", json["provider"] 49 | assert_equal "josevalim", json["user_info"]["nickname"] 50 | assert_equal FACEBOOK_INFO, json["extra"]["user_hash"] 51 | assert_equal "plataformatec", json["credentials"]["token"] 52 | end 53 | 54 | test "cleans up session on sign up" do 55 | assert_no_difference "User.count" do 56 | visit "/users/sign_in" 57 | click_link "Sign in with Facebook" 58 | end 59 | 60 | assert session["devise.facebook_data"] 61 | 62 | assert_difference "User.count" do 63 | visit "/users/sign_up" 64 | fill_in "Password", :with => "12345678" 65 | fill_in "Password confirmation", :with => "12345678" 66 | click_button "Sign up" 67 | end 68 | 69 | assert_current_url "/" 70 | assert_contain "You have signed up successfully." 71 | assert_contain "Hello User user@example.com" 72 | assert_not session["devise.facebook_data"] 73 | end 74 | 75 | test "cleans up session on cancel" do 76 | assert_no_difference "User.count" do 77 | visit "/users/sign_in" 78 | click_link "Sign in with Facebook" 79 | end 80 | 81 | assert session["devise.facebook_data"] 82 | visit "/users/cancel" 83 | assert !session["devise.facebook_data"] 84 | end 85 | 86 | test "cleans up session on sign in" do 87 | assert_no_difference "User.count" do 88 | visit "/users/sign_in" 89 | click_link "Sign in with Facebook" 90 | end 91 | 92 | assert session["devise.facebook_data"] 93 | user = sign_in_as_user 94 | assert !session["devise.facebook_data"] 95 | end 96 | 97 | test "sign in and send remember token if configured" do 98 | visit "/users/sign_in" 99 | click_link "Sign in with Facebook" 100 | assert_nil warden.cookies["remember_user_token"] 101 | 102 | stub_action!(:sign_in_facebook) do 103 | create_user 104 | visit "/users/sign_in" 105 | click_link "Sign in with Facebook" 106 | assert warden.authenticated?(:user) 107 | assert warden.cookies["remember_user_token"] 108 | end 109 | end 110 | 111 | test "generates a proper link when SCRIPT_NAME is set" do 112 | header 'SCRIPT_NAME', '/q' 113 | visit "/users/sign_in" 114 | assert_select "a", :href => "/q/users/auth/facebook" 115 | end 116 | 117 | test "handles callback error parameter according to the specification" do 118 | OmniAuth.config.mock_auth[:facebook] = :access_denied 119 | visit "/users/auth/facebook/callback?error=access_denied" 120 | assert_current_url "/users/sign_in" 121 | assert_contain 'Could not authenticate you from Facebook because "Access denied".' 122 | end 123 | 124 | test "handles other exceptions from omniauth" do 125 | OmniAuth.config.mock_auth[:facebook] = :invalid_credentials 126 | 127 | visit "/users/sign_in" 128 | click_link "Sign in with Facebook" 129 | 130 | assert_current_url "/users/sign_in" 131 | assert_contain 'Could not authenticate you from Facebook because "Invalid credentials".' 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/models/validatable_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'test_helper' 3 | 4 | class ValidatableTest < ActiveSupport::TestCase 5 | test 'should require email to be set' do 6 | user = new_user(:email => nil) 7 | assert user.invalid? 8 | assert user.errors[:email] 9 | assert_equal 'can\'t be blank', user.errors[:email].join 10 | end 11 | 12 | test 'should require uniqueness of email if email has changed, allowing blank' do 13 | existing_user = create_user 14 | 15 | user = new_user(:email => '') 16 | assert user.invalid? 17 | assert_no_match(/taken/, user.errors[:email].join) 18 | 19 | user.email = existing_user.email 20 | assert user.invalid? 21 | assert_match(/taken/, user.errors[:email].join) 22 | 23 | user.save(:validate => false) 24 | assert user.valid? 25 | end 26 | 27 | test 'should require correct email format if email has changed, allowing blank' do 28 | user = new_user(:email => '') 29 | assert user.invalid? 30 | assert_not_equal 'is invalid', user.errors[:email].join 31 | 32 | %w{invalid_email_format 123 $$$ () ☃ bla@bla.}.each do |email| 33 | user.email = email 34 | assert user.invalid?, 'should be invalid with email ' << email 35 | assert_equal 'is invalid', user.errors[:email].join 36 | end 37 | 38 | user.save(:validate => false) 39 | assert user.valid? 40 | end 41 | 42 | test 'should accept valid emails' do 43 | %w(a.b.c@example.com test_mail@gmail.com any@any.net email@test.br 123@mail.test 1☃3@mail.test).each do |email| 44 | user = new_user(:email => email) 45 | assert user.valid?, 'should be valid with email ' << email 46 | assert_blank user.errors[:email] 47 | end 48 | end 49 | 50 | test 'should require password to be set when creating a new record' do 51 | user = new_user(:password => '', :password_confirmation => '') 52 | assert user.invalid? 53 | assert_equal 'can\'t be blank', user.errors[:password].join 54 | end 55 | 56 | test 'should require confirmation to be set when creating a new record' do 57 | user = new_user(:password => 'new_password', :password_confirmation => 'blabla') 58 | assert user.invalid? 59 | assert_equal 'doesn\'t match confirmation', user.errors[:password].join 60 | end 61 | 62 | test 'should require password when updating/reseting password' do 63 | user = create_user 64 | 65 | user.password = '' 66 | user.password_confirmation = '' 67 | 68 | assert user.invalid? 69 | assert_equal 'can\'t be blank', user.errors[:password].join 70 | end 71 | 72 | test 'should require confirmation when updating/reseting password' do 73 | user = create_user 74 | user.password_confirmation = 'another_password' 75 | assert user.invalid? 76 | assert_equal 'doesn\'t match confirmation', user.errors[:password].join 77 | end 78 | 79 | test 'should require a password with minimum of 6 characters' do 80 | user = new_user(:password => '12345', :password_confirmation => '12345') 81 | assert user.invalid? 82 | assert_equal 'is too short (minimum is 6 characters)', user.errors[:password].join 83 | end 84 | 85 | test 'should require a password with maximum of 128 characters long' do 86 | user = new_user(:password => 'x'*129, :password_confirmation => 'x'*129) 87 | assert user.invalid? 88 | assert_equal 'is too long (maximum is 128 characters)', user.errors[:password].join 89 | end 90 | 91 | test 'should not require password length when it\'s not changed' do 92 | user = create_user.reload 93 | user.password = user.password_confirmation = nil 94 | assert user.valid? 95 | 96 | user.password_confirmation = 'confirmation' 97 | assert user.invalid? 98 | assert_not (user.errors[:password].join =~ /is too long/) 99 | end 100 | 101 | test 'should complain about length even if possword is not required' do 102 | user = new_user(:password => 'x'*129, :password_confirmation => 'x'*129) 103 | user.stubs(:password_required?).returns(false) 104 | assert user.invalid? 105 | assert_equal 'is too long (maximum is 128 characters)', user.errors[:password].join 106 | end 107 | 108 | test 'should not be included in objects with invalid API' do 109 | assert_raise RuntimeError do 110 | Class.new.send :include, Devise::Models::Validatable 111 | end 112 | end 113 | 114 | test 'required_fields should be an empty array' do 115 | assert_equal Devise::Models::Validatable.required_fields(User), [] 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.rails-3.1.x.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | devise (2.2.0) 5 | bcrypt-ruby (~> 3.0) 6 | orm_adapter (~> 0.1) 7 | railties (~> 3.1) 8 | warden (~> 1.2.1) 9 | 10 | GEM 11 | remote: http://rubygems.org/ 12 | specs: 13 | actionmailer (3.1.10) 14 | actionpack (= 3.1.10) 15 | mail (~> 2.3.3) 16 | actionpack (3.1.10) 17 | activemodel (= 3.1.10) 18 | activesupport (= 3.1.10) 19 | builder (~> 3.0.0) 20 | erubis (~> 2.7.0) 21 | i18n (~> 0.6) 22 | rack (~> 1.3.6) 23 | rack-cache (~> 1.2) 24 | rack-mount (~> 0.8.2) 25 | rack-test (~> 0.6.1) 26 | sprockets (~> 2.0.4) 27 | activemodel (3.1.10) 28 | activesupport (= 3.1.10) 29 | builder (~> 3.0.0) 30 | i18n (~> 0.6) 31 | activerecord (3.1.10) 32 | activemodel (= 3.1.10) 33 | activesupport (= 3.1.10) 34 | arel (~> 2.2.3) 35 | tzinfo (~> 0.3.29) 36 | activeresource (3.1.10) 37 | activemodel (= 3.1.10) 38 | activesupport (= 3.1.10) 39 | activesupport (3.1.10) 40 | multi_json (>= 1.0, < 1.3) 41 | arel (2.2.3) 42 | bcrypt-ruby (3.0.1) 43 | builder (3.0.4) 44 | columnize (0.3.6) 45 | erubis (2.7.0) 46 | faraday (0.8.4) 47 | multipart-post (~> 1.1) 48 | hashie (1.2.0) 49 | hike (1.2.1) 50 | httpauth (0.2.0) 51 | i18n (0.6.1) 52 | json (1.7.6) 53 | jwt (0.1.5) 54 | multi_json (>= 1.0) 55 | linecache (0.46) 56 | rbx-require-relative (> 0.0.4) 57 | mail (2.3.3) 58 | i18n (>= 0.4.0) 59 | mime-types (~> 1.16) 60 | treetop (~> 1.4.8) 61 | metaclass (0.0.1) 62 | mime-types (1.19) 63 | mocha (0.10.0) 64 | metaclass (~> 0.0.1) 65 | mongoid (3.0.16) 66 | activemodel (~> 3.1) 67 | moped (~> 1.1) 68 | origin (~> 1.0) 69 | tzinfo (~> 0.3.22) 70 | moped (1.3.2) 71 | multi_json (1.2.0) 72 | multipart-post (1.1.5) 73 | nokogiri (1.5.6) 74 | oauth2 (0.8.0) 75 | faraday (~> 0.8) 76 | httpauth (~> 0.1) 77 | jwt (~> 0.1.4) 78 | multi_json (~> 1.0) 79 | rack (~> 1.2) 80 | omniauth (1.0.3) 81 | hashie (~> 1.2) 82 | rack 83 | omniauth-facebook (1.4.0) 84 | omniauth-oauth2 (~> 1.0.2) 85 | omniauth-oauth2 (1.0.3) 86 | oauth2 (~> 0.8.0) 87 | omniauth (~> 1.0) 88 | omniauth-openid (1.0.1) 89 | omniauth (~> 1.0) 90 | rack-openid (~> 1.3.1) 91 | origin (1.0.11) 92 | orm_adapter (0.4.0) 93 | polyglot (0.3.3) 94 | rack (1.3.8) 95 | rack-cache (1.2) 96 | rack (>= 0.4) 97 | rack-mount (0.8.3) 98 | rack (>= 1.0.0) 99 | rack-openid (1.3.1) 100 | rack (>= 1.1.0) 101 | ruby-openid (>= 2.1.8) 102 | rack-ssl (1.3.2) 103 | rack 104 | rack-test (0.6.2) 105 | rack (>= 1.0) 106 | rails (3.1.10) 107 | actionmailer (= 3.1.10) 108 | actionpack (= 3.1.10) 109 | activerecord (= 3.1.10) 110 | activeresource (= 3.1.10) 111 | activesupport (= 3.1.10) 112 | bundler (~> 1.0) 113 | railties (= 3.1.10) 114 | railties (3.1.10) 115 | actionpack (= 3.1.10) 116 | activesupport (= 3.1.10) 117 | rack-ssl (~> 1.3.2) 118 | rake (>= 0.8.7) 119 | rdoc (~> 3.4) 120 | thor (~> 0.14.6) 121 | rake (10.0.3) 122 | rbx-require-relative (0.0.9) 123 | rdoc (3.12) 124 | json (~> 1.4) 125 | ruby-debug (0.10.4) 126 | columnize (>= 0.1) 127 | ruby-debug-base (~> 0.10.4.0) 128 | ruby-debug-base (0.10.4) 129 | linecache (>= 0.3) 130 | ruby-openid (2.2.2) 131 | sprockets (2.0.4) 132 | hike (~> 1.2) 133 | rack (~> 1.0) 134 | tilt (~> 1.1, != 1.3.0) 135 | sqlite3 (1.3.6) 136 | thor (0.14.6) 137 | tilt (1.3.3) 138 | treetop (1.4.12) 139 | polyglot 140 | polyglot (>= 0.3.1) 141 | tzinfo (0.3.35) 142 | warden (1.2.1) 143 | rack (>= 1.0) 144 | webrat (0.7.2) 145 | nokogiri (>= 1.2.0) 146 | rack (>= 1.0) 147 | rack-test (>= 0.5.3) 148 | 149 | PLATFORMS 150 | ruby 151 | 152 | DEPENDENCIES 153 | activerecord-jdbc-adapter 154 | activerecord-jdbcsqlite3-adapter 155 | devise! 156 | jruby-openssl 157 | mocha (= 0.10.0) 158 | mongoid (~> 3.0) 159 | omniauth (~> 1.0.0) 160 | omniauth-facebook 161 | omniauth-oauth2 (~> 1.0.0) 162 | omniauth-openid (~> 1.0.1) 163 | rails (~> 3.1.0) 164 | rdoc 165 | ruby-debug (>= 0.10.3) 166 | sqlite3 167 | webrat (= 0.7.2) 168 | -------------------------------------------------------------------------------- /app/controllers/devise/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class Devise::RegistrationsController < DeviseController 2 | prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ] 3 | prepend_before_filter :authenticate_scope!, :only => [:edit, :update, :destroy] 4 | 5 | # GET /resource/sign_up 6 | def new 7 | resource = build_resource({}) 8 | respond_with resource 9 | end 10 | 11 | # POST /resource 12 | def create 13 | build_resource 14 | 15 | if resource.save 16 | if resource.active_for_authentication? 17 | set_flash_message :notice, :signed_up if is_navigational_format? 18 | sign_up(resource_name, resource) 19 | respond_with resource, :location => after_sign_up_path_for(resource) 20 | else 21 | set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format? 22 | expire_session_data_after_sign_in! 23 | respond_with resource, :location => after_inactive_sign_up_path_for(resource) 24 | end 25 | else 26 | clean_up_passwords resource 27 | respond_with resource 28 | end 29 | end 30 | 31 | # GET /resource/edit 32 | def edit 33 | render :edit 34 | end 35 | 36 | # PUT /resource 37 | # We need to use a copy of the resource because we don't want to change 38 | # the current user in place. 39 | def update 40 | self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key) 41 | prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email) 42 | 43 | if resource.update_with_password(resource_params) 44 | if is_navigational_format? 45 | flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ? 46 | :update_needs_confirmation : :updated 47 | set_flash_message :notice, flash_key 48 | end 49 | sign_in resource_name, resource, :bypass => true 50 | respond_with resource, :location => after_update_path_for(resource) 51 | else 52 | clean_up_passwords resource 53 | respond_with resource 54 | end 55 | end 56 | 57 | # DELETE /resource 58 | def destroy 59 | resource.destroy 60 | Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name) 61 | set_flash_message :notice, :destroyed if is_navigational_format? 62 | respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) } 63 | end 64 | 65 | # GET /resource/cancel 66 | # Forces the session data which is usually expired after sign 67 | # in to be expired now. This is useful if the user wants to 68 | # cancel oauth signing in/up in the middle of the process, 69 | # removing all OAuth session data. 70 | def cancel 71 | expire_session_data_after_sign_in! 72 | redirect_to new_registration_path(resource_name) 73 | end 74 | 75 | protected 76 | 77 | def update_needs_confirmation?(resource, previous) 78 | resource.respond_to?(:pending_reconfirmation?) && 79 | resource.pending_reconfirmation? && 80 | previous != resource.unconfirmed_email 81 | end 82 | 83 | # Build a devise resource passing in the session. Useful to move 84 | # temporary session data to the newly created user. 85 | def build_resource(hash=nil) 86 | hash ||= resource_params || {} 87 | self.resource = resource_class.new_with_session(hash, session) 88 | end 89 | 90 | # Signs in a user on sign up. You can overwrite this method in your own 91 | # RegistrationsController. 92 | def sign_up(resource_name, resource) 93 | sign_in(resource_name, resource) 94 | end 95 | 96 | # The path used after sign up. You need to overwrite this method 97 | # in your own RegistrationsController. 98 | def after_sign_up_path_for(resource) 99 | after_sign_in_path_for(resource) 100 | end 101 | 102 | # The path used after sign up for inactive accounts. You need to overwrite 103 | # this method in your own RegistrationsController. 104 | def after_inactive_sign_up_path_for(resource) 105 | respond_to?(:root_path) ? root_path : "/" 106 | end 107 | 108 | # The default url to be used after updating a resource. You need to overwrite 109 | # this method in your own RegistrationsController. 110 | def after_update_path_for(resource) 111 | signed_in_root_path(resource) 112 | end 113 | 114 | # Authenticates the current scope and gets the current resource from the session. 115 | def authenticate_scope! 116 | send(:"authenticate_#{resource_name}!", :force => true) 117 | self.resource = send(:"current_#{resource_name}") 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /test/controllers/internal_helpers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MyController < DeviseController 4 | end 5 | 6 | class HelpersTest < ActionController::TestCase 7 | tests MyController 8 | 9 | def setup 10 | @mock_warden = OpenStruct.new 11 | @controller.request.env['warden'] = @mock_warden 12 | @controller.request.env['devise.mapping'] = Devise.mappings[:user] 13 | end 14 | 15 | test 'get resource name from env' do 16 | assert_equal :user, @controller.resource_name 17 | end 18 | 19 | test 'get resource class from env' do 20 | assert_equal User, @controller.resource_class 21 | end 22 | 23 | test 'get resource instance variable from env' do 24 | @controller.instance_variable_set(:@user, user = User.new) 25 | assert_equal user, @controller.resource 26 | end 27 | 28 | test 'set resource instance variable from env' do 29 | user = @controller.send(:resource_class).new 30 | @controller.send(:resource=, user) 31 | 32 | assert_equal user, @controller.send(:resource) 33 | assert_equal user, @controller.instance_variable_get(:@user) 34 | end 35 | 36 | test 'get resource params from request params using resource name as key' do 37 | user_params = {'name' => 'Shirley Templar'} 38 | @controller.stubs(:params).returns(HashWithIndifferentAccess.new({'user' => user_params})) 39 | 40 | assert_equal user_params, @controller.resource_params 41 | end 42 | 43 | test 'resources methods are not controller actions' do 44 | assert @controller.class.action_methods.empty? 45 | end 46 | 47 | test 'require no authentication tests current mapping' do 48 | @mock_warden.expects(:authenticate?).with(:rememberable, :token_authenticatable, :scope => :user).returns(true) 49 | @mock_warden.expects(:user).with(:user).returns(User.new) 50 | @controller.expects(:redirect_to).with(root_path) 51 | @controller.send :require_no_authentication 52 | end 53 | 54 | test 'require no authentication only checks if already authenticated if no inputs strategies are available' do 55 | Devise.mappings[:user].expects(:no_input_strategies).returns([]) 56 | @mock_warden.expects(:authenticate?).never 57 | @mock_warden.expects(:authenticated?).with(:user).once.returns(true) 58 | @mock_warden.expects(:user).with(:user).returns(User.new) 59 | @controller.expects(:redirect_to).with(root_path) 60 | @controller.send :require_no_authentication 61 | end 62 | 63 | test 'require no authentication sets a flash message' do 64 | @mock_warden.expects(:authenticate?).with(:rememberable, :token_authenticatable, :scope => :user).returns(true) 65 | @mock_warden.expects(:user).with(:user).returns(User.new) 66 | @controller.expects(:redirect_to).with(root_path) 67 | @controller.send :require_no_authentication 68 | assert flash[:alert] == I18n.t("devise.failure.already_authenticated") 69 | end 70 | 71 | test 'signed in resource returns signed in resource for current scope' do 72 | @mock_warden.expects(:authenticate).with(:scope => :user).returns(User.new) 73 | assert_kind_of User, @controller.signed_in_resource 74 | end 75 | 76 | test 'is a devise controller' do 77 | assert @controller.devise_controller? 78 | end 79 | 80 | test 'does not issue blank flash messages' do 81 | I18n.stubs(:t).returns(' ') 82 | @controller.send :set_flash_message, :notice, :send_instructions 83 | assert flash[:notice].nil? 84 | end 85 | 86 | test 'issues non-blank flash messages normally' do 87 | I18n.stubs(:t).returns('non-blank') 88 | @controller.send :set_flash_message, :notice, :send_instructions 89 | assert_equal 'non-blank', flash[:notice] 90 | end 91 | 92 | test 'uses custom i18n options' do 93 | @controller.stubs(:devise_i18n_options).returns(:default => "devise custom options") 94 | @controller.send :set_flash_message, :notice, :invalid_i18n_messagesend_instructions 95 | assert_equal 'devise custom options', flash[:notice] 96 | end 97 | 98 | test 'allows custom i18n options to override resource_name' do 99 | I18n.expects(:t).with("custom_resource_name.confirmed", anything) 100 | @controller.stubs(:devise_i18n_options).returns(:resource_name => "custom_resource_name") 101 | @controller.send :set_flash_message, :notice, :confirmed 102 | end 103 | 104 | test 'navigational_formats not returning a wild card' do 105 | MyController.send(:public, :navigational_formats) 106 | Devise.navigational_formats = [:"*/*", :html] 107 | assert_not @controller.navigational_formats.include?(:"*/*") 108 | MyController.send(:protected, :navigational_formats) 109 | end 110 | end 111 | --------------------------------------------------------------------------------