diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index fc1463d2..6617891f 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -7,6 +7,7 @@ * sign_out_via is available in the router to configure the method used for sign out (by github.com/martinrehfeld) * Improved Ajax requests handling in failure app (by github.com/spastorino) * Add reply-to to e-mail headers by default + * Add request_keys to easily use request specific values (like subdomain) in authentication * bugfix * after_sign_in_path_for always receives a resource diff --git a/lib/devise.rb b/lib/devise.rb index 93c8a60c..b88b0166 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -68,6 +68,10 @@ module Devise mattr_accessor :authentication_keys @@authentication_keys = [ :email ] + # Request keys used when authenticating an user. + mattr_accessor :request_keys + @@request_keys = [] + # If http authentication is enabled by default. mattr_accessor :http_authenticatable @@http_authenticatable = false diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index 6c6fb227..c39834a3 100644 --- a/lib/devise/models/authenticatable.rb +++ b/lib/devise/models/authenticatable.rb @@ -10,6 +10,14 @@ module Devise # # * +authentication_keys+: parameters used for authentication. By default [:email]. # + # * +request_keys+: parameters from the request object used for authentication. + # By specifying a symbol (which should be a request method), it will automatically be + # passed to find_for_authentication method and considered in your model lookup. + # + # For instance, if you set :request_keys to [:subdomain], :subdomain will be considered + # as key on authentication. This can also be a hash where the value is a boolean expliciting + # if the value is required or not. + # # * +http_authenticatable+: if this model allows http authentication. By default true. # It also accepts an array specifying the strategies that should allow http. # @@ -66,7 +74,7 @@ module Devise end module ClassMethods - Devise::Models.config(self, :authentication_keys, :http_authenticatable, :params_authenticatable) + Devise::Models.config(self, :authentication_keys, :request_keys, :http_authenticatable, :params_authenticatable) def params_authenticatable?(strategy) params_authenticatable.is_a?(Array) ? diff --git a/lib/devise/strategies/authenticatable.rb b/lib/devise/strategies/authenticatable.rb index a826bbd7..23c70664 100644 --- a/lib/devise/strategies/authenticatable.rb +++ b/lib/devise/strategies/authenticatable.rb @@ -101,10 +101,12 @@ module Devise end # Sets the authentication hash and the password from params_auth_hash or http_auth_hash. - def with_authentication_hash(hash) - self.authentication_hash = hash.slice(*authentication_keys) - self.password = hash[:password] - authentication_keys.all?{ |k| authentication_hash[k].present? } + def with_authentication_hash(auth_values) + self.authentication_hash = {} + self.password = auth_values[:password] + + parse_authentication_key_values(auth_values, authentication_keys) && + parse_authentication_key_values(request_values, request_keys) end # Holds the authentication keys. @@ -112,6 +114,33 @@ module Devise @authentication_keys ||= mapping.to.authentication_keys end + # Holds request keys. + def request_keys + @request_keys ||= mapping.to.request_keys + end + + # Returns values from the request object. + def request_values + keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys + keys.inject({}) do |hash, key| + hash[key] = self.request.send(key) + hash + end + end + + # Parse authentication keys considering if they should be enforced or not. + def parse_authentication_key_values(hash, keys) + keys.each do |key, enforce| + value = hash[key].presence + if value + self.authentication_hash[key] = value + else + return false unless enforce == false + end + end + true + end + # Holds the authenticatable name for this class. Devise::Strategies::DatabaseAuthenticatable # becomes simply :database. def authenticatable_name diff --git a/lib/generators/templates/devise.rb b/lib/generators/templates/devise.rb index 56fda0a6..2e0288bf 100644 --- a/lib/generators/templates/devise.rb +++ b/lib/generators/templates/devise.rb @@ -20,8 +20,17 @@ Devise.setup do |config| # authenticating an user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. + # You can also supply hash where the value is a boolean expliciting if authentication + # should be aborted or not if the value is not present. By default is empty. # config.authentication_keys = [ :email ] + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + # Tell if authentication through request.params is enabled. True by default. # config.params_authenticatable = true diff --git a/test/integration/authenticatable_test.rb b/test/integration/authenticatable_test.rb index 8e4cba64..b94a0c30 100644 --- a/test/integration/authenticatable_test.rb +++ b/test/integration/authenticatable_test.rb @@ -333,6 +333,47 @@ class AuthenticationOthersTest < ActionController::IntegrationTest end end +class AuthenticationRequestKeysTest < ActionController::IntegrationTest + test 'request keys are used on authentication' do + host! 'foo.bar.baz' + + swap Devise, :request_keys => [:subdomain] do + User.expects(:find_for_authentication).with(:subdomain => 'foo', :email => 'user@test.com').returns(create_user) + sign_in_as_user + assert warden.authenticated?(:user) + end + end + + test 'invalid request keys raises NoMethodError' do + swap Devise, :request_keys => [:unknown_method] do + assert_raise NoMethodError do + sign_in_as_user + end + + assert_not warden.authenticated?(:user) + end + end + + test 'blank request keys cause authentication to abort' do + host! 'test.com' + + swap Devise, :request_keys => [:subdomain] do + sign_in_as_user + assert_contain "Invalid email or password." + assert_not warden.authenticated?(:user) + end + end + + test 'blank request keys cause authentication to abort unless if marked as not required' do + host! 'test.com' + + swap Devise, :request_keys => { :subdomain => false } do + sign_in_as_user + assert warden.authenticated?(:user) + end + end +end + class AuthenticationSignOutViaTest < ActionController::IntegrationTest def sign_in!(scope) sign_in_as_user(:visit => send("new_#{scope}_session_path"))