Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

TokenAuthenticatable now works with HTTP Basic Auth by default (take …

…a look at Highrise API for a good example). This basically allows you to pass the authentication token as HTTP Basic Auth username.
  • Loading branch information...
commit f5d01c217d16f11cc21d8a984e25a1551fcc720c 1 parent 2b5a068
José Valim josevalim authored
2  CHANGELOG.rdoc
View
@@ -3,7 +3,6 @@
* enhancements
* Rails 3 compatibility.
* All controllers and views are namespaced, for example: Devise::SessionsController and "devise/sessions".
- * You can specify the controller in routes and have specific controllers for each role.
* Devise.orm is deprecated. This reduces the required API to hook your ORM with devise.
* Use metal for failure app.
* HTML e-mails now have proper formatting.
@@ -15,6 +14,7 @@
* Allow to specify haml in devise_views.
* Compatibility with Datamapper and Mongoid.
* Make config.devise available on config/application.rb.
+ * TokenAuthenticatable now works with HTTP Basic Auth.
* Allow :unlock_strategy to be :none and add :lock_strategy which can be :failed_attempts or
none. Setting those values to :none means that you want to handle lock and unlocking by
yourself.
4 lib/devise.rb
View
@@ -64,6 +64,10 @@ module Strategies
mattr_accessor :http_authenticatable
@@http_authenticatable = true
+ # If params authenticatable is enabled by default.
+ mattr_accessor :params_authenticatable
+ @@params_authenticatable = true
+
# The realm used in Http Basic Authentication.
mattr_accessor :http_authentication_realm
@@http_authentication_realm = "Application"
16 lib/devise/models/authenticatable.rb
View
@@ -10,6 +10,10 @@ module Models
# authentication_keys: parameters used for authentication. By default [:email].
#
# http_authenticatable: if this model allows http authentication. By default true.
+ # It also accepts an array specifying the strategies that should allow http.
+ #
+ # params_authenticatable: if this model allows authentication through request params. By default true.
+ # It also accepts an array specifying the strategies that should allow params authentication.
#
module Authenticatable
extend ActiveSupport::Concern
@@ -21,9 +25,17 @@ def valid_for_authentication?
end
module ClassMethods
- Devise::Models.config(self, :authentication_keys, :http_authenticatable)
+ Devise::Models.config(self, :authentication_keys, :http_authenticatable, :params_authenticatable)
+
+ def params_authenticatable?(strategy)
+ params_authenticatable.is_a?(Array) ?
+ params_authenticatable.include?(strategy) : params_authenticatable
+ end
- alias :http_authenticatable? :http_authenticatable
+ def http_authenticatable?(strategy)
+ http_authenticatable.is_a?(Array) ?
+ http_authenticatable.include?(strategy) : http_authenticatable
+ end
# Find first record based on conditions given (ie by the sign in form).
# Overwrite to add customized conditions, create a join, or maybe use a
3  lib/devise/models/token_authenticatable.rb
View
@@ -18,7 +18,8 @@ module Models
# User.find(1).valid_authentication_token?('rI1t6PKQ8yP7VetgwdybB') # returns true/false
#
module TokenAuthenticatable
- extend ActiveSupport::Concern
+ extend ActiveSupport::Concern
+ include Devise::Models::Authenticatable
included do
before_save :ensure_authentication_token
53 lib/devise/strategies/authenticatable.rb
View
@@ -26,45 +26,72 @@ def validate(resource, &block)
end
end
+ # Check if this is strategy is valid for http authentication.
def valid_for_http_auth?
- mapping.to.http_authenticatable? && request.authorization && set_http_auth_hash
+ http_authenticatable? && request.authorization && with_authentication_hash(http_auth_hash)
end
+ # Check if this is strategy is valid for params authentication.
def valid_for_params_auth?
- valid_controller? && valid_params? && set_params_auth_hash
+ params_authenticatable? && valid_controller? &&
+ valid_params? && with_authentication_hash(params_auth_hash)
end
- def valid_controller?
- mapping.controllers[:sessions] == params[:controller]
+ # Check if the model accepts this strategy as http authenticatable.
+ def http_authenticatable?
+ mapping.to.http_authenticatable?(authenticatable_name)
end
- def valid_params?
- params[scope].is_a?(Hash)
+ # Check if the model accepts this strategy as params authenticatable.
+ def params_authenticatable?
+ mapping.to.params_authenticatable?(authenticatable_name)
end
- def set_http_auth_hash
+ # Extract the appropriate subhash for authentication from params.
+ def params_auth_hash
+ params[scope]
+ end
+
+ # Extract a hash with attributes:values from the http params.
+ def http_auth_hash
keys = [authentication_keys.first, :password]
- with_authentication_hash Hash[*keys.zip(decode_credentials).flatten]
+ Hash[*keys.zip(decode_credentials).flatten]
+ end
+
+ # Check if the controller is valid for params authentication.
+ def valid_controller?
+ mapping.controllers[:sessions] == params[:controller]
end
+ # Check if the params_auth_hash is valid for params authentication.
+ def valid_params?
+ params_auth_hash.is_a?(Hash)
+ end
+
+ # Helper to decode credentials from HTTP.
def decode_credentials
username_and_password = request.authorization.split(' ', 2).last || ''
ActiveSupport::Base64.decode64(username_and_password).split(/:/, 2)
end
- def set_params_auth_hash
- with_authentication_hash params[scope]
- 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? } && password.present?
+ authentication_keys.all?{ |k| authentication_hash[k].present? }
end
+ # Holds the authentication keys.
def authentication_keys
@authentication_keys ||= mapping.to.authentication_keys
end
+
+ # Holds the authenticatable name for this class. Devise::Strategies::DatabaseAuthenticatable
+ # becomes simply :database.
+ def authenticatable_name
+ @authenticatable_name ||=
+ self.class.name.split("::").last.underscore.sub("_authenticatable", "").to_sym
+ end
end
end
end
47 lib/devise/strategies/token_authenticatable.rb
View
@@ -2,33 +2,42 @@
module Devise
module Strategies
- # Strategy for signing in a user, based on a authenticatable token.
- # Redirects to sign_in page if it's not authenticated.
- class TokenAuthenticatable < Base
- def valid?
- authentication_token(scope).present?
- end
-
- # Authenticate a user based on authenticatable token params, returning to warden
- # success and the authenticated user if everything is okay. Otherwise redirect
- # to sign in page.
+ # Strategy for signing in a user, based on a authenticatable token. This works for both params
+ # and http. For the former, all you need to do is to pass the params in the URL:
+ #
+ # http://myapp.example.com/?user_token=SECRET
+ #
+ # For HTTP, you can pass the token as username. Since some clients may require a password,
+ # you can pass anything and it will simply be ignored.
+ class TokenAuthenticatable < Authenticatable
def authenticate!
- if resource = mapping.to.authenticate_with_token(params[scope] || params)
+ if resource = mapping.to.authenticate_with_token(authentication_hash)
success!(resource)
else
- fail!(:invalid_token)
+ fail(:invalid_token)
end
end
private
- # Detect authentication token in params: scoped or not.
- def authentication_token(scope)
- if params[scope]
- params[scope][mapping.to.token_authentication_key]
- else
- params[mapping.to.token_authentication_key]
- end
+ # TokenAuthenticatable params can be given to any controller.
+ def valid_controller?
+ true
+ end
+
+ # Do not use remember_me behavir with token.
+ def remember_me?
+ false
+ end
+
+ # Try both scoped and non scoped keys.
+ def params_auth_hash
+ params[scope] || params
+ end
+
+ # Overwrite authentication keys to use token_authentication_key.
+ def authentication_keys
+ @authentication_keys ||= [mapping.to.token_authentication_key]
end
end
end
48 test/integration/token_authenticatable_test.rb
View
@@ -2,9 +2,20 @@
class TokenAuthenticationTest < ActionController::IntegrationTest
- test 'sign in should authenticate with valid authentication token and proper authentication token key' do
+ test 'authenticate with valid authentication token key and value through params' do
swap Devise, :token_authentication_key => :secret_token do
- sign_in_as_new_user_with_token(:auth_token_key => :secret_token)
+ sign_in_as_new_user_with_token
+
+ assert_response :success
+ assert_template 'users/index'
+ assert_contain 'Welcome'
+ assert warden.authenticated?(:user)
+ end
+ end
+
+ test 'authenticate with valid authentication token key and value through http' do
+ swap Devise, :token_authentication_key => :secret_token do
+ sign_in_as_new_user_with_token(:http_auth => true)
assert_response :success
assert_template 'users/index'
@@ -13,7 +24,27 @@ class TokenAuthenticationTest < ActionController::IntegrationTest
end
end
- test 'signing in with valid authentication token - but improper authentication token key - return to sign in form with error message' do
+ test 'does authenticate with valid authentication token key and value through params if not configured' do
+ swap Devise, :token_authentication_key => :secret_token, :params_authenticatable => [:database] do
+ sign_in_as_new_user_with_token
+
+ assert_contain 'You need to sign in or sign up before continuing'
+ assert_contain 'Sign in'
+ assert_not warden.authenticated?(:user)
+ end
+ end
+
+ test 'does authenticate with valid authentication token key and value through http if not configured' do
+ swap Devise, :token_authentication_key => :secret_token, :http_authenticatable => [:database] do
+ sign_in_as_new_user_with_token(:http_auth => true)
+
+ assert_response 401
+ assert_contain 'Invalid email or password.'
+ assert_not warden.authenticated?(:user)
+ end
+ end
+
+ test 'does not authenticate with improper authentication token key' do
swap Devise, :token_authentication_key => :donald_duck_token do
sign_in_as_new_user_with_token(:auth_token_key => :secret_token)
assert_current_path new_user_session_path(:unauthenticated => true)
@@ -24,12 +55,11 @@ class TokenAuthenticationTest < ActionController::IntegrationTest
end
end
- test 'signing in with invalid authentication token should return to sign in form with error message' do
+ test 'does not authenticate with improper authentication token value' do
store_translations :en, :devise => {:sessions => {:invalid_token => 'LOL, that was not a single character correct.'}} do
sign_in_as_new_user_with_token(:auth_token => '*** INVALID TOKEN ***')
assert_current_path new_user_session_path(:invalid_token => true)
- assert_response :success
assert_contain 'LOL, that was not a single character correct.'
assert_contain 'Sign in'
assert_not warden.authenticated?(:user)
@@ -46,7 +76,13 @@ def sign_in_as_new_user_with_token(options = {})
user.authentication_token = VALID_AUTHENTICATION_TOKEN
user.save
- visit users_path(options[:auth_token_key].to_sym => options[:auth_token])
+ if options[:http_auth]
+ header = "Basic #{ActiveSupport::Base64.encode64("#{VALID_AUTHENTICATION_TOKEN}:X")}"
+ get users_path, {}, "HTTP_AUTHORIZATION" => header
+ else
+ visit users_path(options[:auth_token_key].to_sym => options[:auth_token])
+ end
+
user
end
Please sign in to comment.
Something went wrong with that request. Please try again.