stn-simple_token_authentication 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +270 -0
  4. data/Rakefile +61 -0
  5. data/doc/README.md +18 -0
  6. data/lib/simple_token_authentication.rb +58 -0
  7. data/lib/simple_token_authentication/acts_as_token_authenticatable.rb +49 -0
  8. data/lib/simple_token_authentication/acts_as_token_authentication_handler.rb +22 -0
  9. data/lib/simple_token_authentication/adapter.rb +7 -0
  10. data/lib/simple_token_authentication/adapters/active_record_adapter.rb +14 -0
  11. data/lib/simple_token_authentication/adapters/mongoid_adapter.rb +14 -0
  12. data/lib/simple_token_authentication/adapters/rails_adapter.rb +14 -0
  13. data/lib/simple_token_authentication/adapters/rails_api_adapter.rb +18 -0
  14. data/lib/simple_token_authentication/configuration.rb +45 -0
  15. data/lib/simple_token_authentication/entities_manager.rb +10 -0
  16. data/lib/simple_token_authentication/entity.rb +64 -0
  17. data/lib/simple_token_authentication/fallback_authentication_handler.rb +11 -0
  18. data/lib/simple_token_authentication/sign_in_handler.rb +19 -0
  19. data/lib/simple_token_authentication/token_authentication_handler.rb +149 -0
  20. data/lib/simple_token_authentication/token_comparator.rb +20 -0
  21. data/lib/simple_token_authentication/token_generator.rb +9 -0
  22. data/lib/simple_token_authentication/version.rb +3 -0
  23. data/lib/tasks/simple_token_authentication_tasks.rake +4 -0
  24. data/spec/configuration/action_controller_callbacks_options_spec.rb +53 -0
  25. data/spec/configuration/fallback_to_devise_option_spec.rb +128 -0
  26. data/spec/configuration/header_names_option_spec.rb +463 -0
  27. data/spec/configuration/sign_in_token_option_spec.rb +92 -0
  28. data/spec/lib/simple_token_authentication/acts_as_token_authenticatable_spec.rb +108 -0
  29. data/spec/lib/simple_token_authentication/acts_as_token_authentication_handler_spec.rb +127 -0
  30. data/spec/lib/simple_token_authentication/adapter_spec.rb +19 -0
  31. data/spec/lib/simple_token_authentication/adapters/active_record_adapter_spec.rb +21 -0
  32. data/spec/lib/simple_token_authentication/adapters/mongoid_adapter_spec.rb +21 -0
  33. data/spec/lib/simple_token_authentication/adapters/rails_adapter_spec.rb +21 -0
  34. data/spec/lib/simple_token_authentication/adapters/rails_api_adapter_spec.rb +43 -0
  35. data/spec/lib/simple_token_authentication/configuration_spec.rb +133 -0
  36. data/spec/lib/simple_token_authentication/entities_manager_spec.rb +67 -0
  37. data/spec/lib/simple_token_authentication/entity_spec.rb +190 -0
  38. data/spec/lib/simple_token_authentication/errors_spec.rb +8 -0
  39. data/spec/lib/simple_token_authentication/fallback_authentication_handler_spec.rb +24 -0
  40. data/spec/lib/simple_token_authentication/sign_in_handler_spec.rb +43 -0
  41. data/spec/lib/simple_token_authentication/token_authentication_handler_spec.rb +351 -0
  42. data/spec/lib/simple_token_authentication/token_comparator_spec.rb +19 -0
  43. data/spec/lib/simple_token_authentication/token_generator_spec.rb +19 -0
  44. data/spec/lib/simple_token_authentication_spec.rb +181 -0
  45. data/spec/spec_helper.rb +15 -0
  46. data/spec/support/dummy_classes_helper.rb +80 -0
  47. data/spec/support/spec_for_adapter.rb +10 -0
  48. data/spec/support/spec_for_authentication_handler_interface.rb +8 -0
  49. data/spec/support/spec_for_configuration_option_interface.rb +28 -0
  50. data/spec/support/spec_for_entities_manager_interface.rb +8 -0
  51. data/spec/support/spec_for_sign_in_handler_interface.rb +8 -0
  52. data/spec/support/spec_for_token_comparator_interface.rb +8 -0
  53. data/spec/support/spec_for_token_generator_interface.rb +8 -0
  54. data/spec/support/specs_for_token_authentication_handler_interface.rb +8 -0
  55. metadata +250 -0
@@ -0,0 +1,22 @@
1
+ require 'active_support/deprecation'
2
+ require 'simple_token_authentication/token_authentication_handler'
3
+
4
+ module SimpleTokenAuthentication
5
+ module ActsAsTokenAuthenticationHandler
6
+
7
+ # This module ensures that no TokenAuthenticationHandler behaviour
8
+ # is added before the class actually `acts_as_token_authentication_handler_for`
9
+ # some token authenticatable model.
10
+ # See https://github.com/gonzalo-bulnes/simple_token_authentication/issues/8#issuecomment-31707201
11
+
12
+ def acts_as_token_authentication_handler_for(model, options = {})
13
+ include SimpleTokenAuthentication::TokenAuthenticationHandler
14
+ handle_token_authentication_for(model, options)
15
+ end
16
+
17
+ def acts_as_token_authentication_handler
18
+ ::ActiveSupport::Deprecation.warn "`acts_as_token_authentication_handler()` is deprecated and may be removed from future releases, use `acts_as_token_authentication_handler_for(User)` instead.", caller
19
+ acts_as_token_authentication_handler_for User
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module SimpleTokenAuthentication
2
+ module Adapter
3
+ def base_class
4
+ raise NotImplementedError
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record'
2
+ require 'simple_token_authentication/adapter'
3
+
4
+ module SimpleTokenAuthentication
5
+ module Adapters
6
+ class ActiveRecordAdapter
7
+ extend SimpleTokenAuthentication::Adapter
8
+
9
+ def self.base_class
10
+ ::ActiveRecord::Base
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'mongoid'
2
+ require 'simple_token_authentication/adapter'
3
+
4
+ module SimpleTokenAuthentication
5
+ module Adapters
6
+ class MongoidAdapter
7
+ extend SimpleTokenAuthentication::Adapter
8
+
9
+ def self.base_class
10
+ ::Mongoid::Document
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'action_controller'
2
+ require 'simple_token_authentication/adapter'
3
+
4
+ module SimpleTokenAuthentication
5
+ module Adapters
6
+ class RailsAdapter
7
+ extend SimpleTokenAuthentication::Adapter
8
+
9
+ def self.base_class
10
+ ::ActionController::Base
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'action_controller'
2
+ require 'simple_token_authentication/adapter'
3
+
4
+ module SimpleTokenAuthentication
5
+ module Adapters
6
+ class RailsAPIAdapter
7
+ extend SimpleTokenAuthentication::Adapter
8
+
9
+ def self.base_class
10
+ ::ActionController::API
11
+ end
12
+ end
13
+
14
+ # make the adpater available even if the 'API' acronym is not defined
15
+ RailsApiAdapter = RailsAPIAdapter
16
+ end
17
+ end
18
+
@@ -0,0 +1,45 @@
1
+ module SimpleTokenAuthentication
2
+ module Configuration
3
+
4
+ mattr_reader :fallback
5
+ mattr_accessor :header_names
6
+ mattr_accessor :sign_in_token
7
+ mattr_accessor :controller_adapters
8
+ mattr_accessor :model_adapters
9
+ mattr_accessor :adapters_dependencies
10
+
11
+ # Default configuration
12
+ @@fallback = :devise
13
+ @@header_names = {}
14
+ @@sign_in_token = false
15
+ @@controller_adapters = ['rails', 'rails_api']
16
+ @@model_adapters = ['active_record', 'mongoid']
17
+ @@adapters_dependencies = { 'active_record' => 'ActiveRecord::Base',
18
+ 'mongoid' => 'Mongoid::Document',
19
+ 'rails' => 'ActionController::Base',
20
+ 'rails_api' => 'ActionController::API' }
21
+
22
+ # Allow the default configuration to be overwritten from initializers
23
+ def configure
24
+ yield self if block_given?
25
+ end
26
+
27
+ def parse_options(options)
28
+ unless options[:fallback].presence
29
+ if options[:fallback_to_devise]
30
+ options[:fallback] = :devise
31
+ elsif options[:fallback_to_devise] == false
32
+ if SimpleTokenAuthentication.fallback == :devise
33
+ options[:fallback] = :none
34
+ else
35
+ options[:fallback] = SimpleTokenAuthentication.fallback
36
+ end
37
+ else
38
+ options[:fallback] = SimpleTokenAuthentication.fallback
39
+ end
40
+ end
41
+ options.reject! { |k,v| k == :fallback_to_devise }
42
+ options
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ require 'simple_token_authentication/entity'
2
+
3
+ module SimpleTokenAuthentication
4
+ class EntitiesManager
5
+ def find_or_create_entity(model, name)
6
+ @entities ||= {}
7
+ @entities[name] ||= Entity.new(model, name)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,64 @@
1
+ module SimpleTokenAuthentication
2
+ class Entity
3
+ def initialize model, name=nil
4
+ @model = model
5
+ @name = name || model.name
6
+ end
7
+
8
+ def model
9
+ @model
10
+ end
11
+
12
+ def name
13
+ @name
14
+ end
15
+
16
+ def name_underscore
17
+ name.underscore
18
+ end
19
+
20
+ # Private: Return the name of the header to watch for the token authentication param
21
+ def token_header_name
22
+ if SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym].presence \
23
+ && token_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][:authentication_token]
24
+ token_header_name
25
+ else
26
+ "X-#{name}-Token"
27
+ end
28
+ end
29
+
30
+ # Private: Return the name of the header to watch for the email param
31
+ def identifier_header_name
32
+ if SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym].presence \
33
+ && identifier_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][:email]
34
+ identifier_header_name
35
+ else
36
+ "X-#{name}-Email"
37
+ end
38
+ end
39
+
40
+ def token_param_name
41
+ "#{name_underscore}_token".to_sym
42
+ end
43
+
44
+ def identifier_param_name
45
+ "#{name_underscore}_email".to_sym
46
+ end
47
+
48
+ def get_token_from_params_or_headers controller
49
+ # if the token is not present among params, get it from headers
50
+ if token = controller.params[token_param_name].blank? && controller.request.headers[token_header_name]
51
+ controller.params[token_param_name] = token
52
+ end
53
+ controller.params[token_param_name]
54
+ end
55
+
56
+ def get_identifier_from_params_or_headers controller
57
+ # if the identifier (email) is not present among params, get it from headers
58
+ if email = controller.params[identifier_param_name].blank? && controller.request.headers[identifier_header_name]
59
+ controller.params[identifier_param_name] = email
60
+ end
61
+ controller.params[identifier_param_name]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,11 @@
1
+ module SimpleTokenAuthentication
2
+ class FallbackAuthenticationHandler
3
+ # Devise authentication is performed through a controller
4
+ # which includes Devise::Controllers::Helpers
5
+ # See http://rdoc.info/github/plataformatec/devise/master/\
6
+ # Devise/Controllers/Helpers#define_helpers-class_method
7
+ def authenticate_entity!(controller, entity)
8
+ controller.send("authenticate_#{entity.name_underscore}!".to_sym)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module SimpleTokenAuthentication
2
+ class SignInHandler
3
+ # Devise sign in is performed through a controller
4
+ # which includes Devise::Controllers::SignInOut
5
+ def sign_in(controller, record, *args)
6
+ integrate_with_devise_trackable!(controller)
7
+
8
+ controller.send(:sign_in, record, *args)
9
+ end
10
+
11
+ private
12
+
13
+ def integrate_with_devise_trackable!(controller)
14
+ # Sign in using token should not be tracked by Devise trackable
15
+ # See https://github.com/plataformatec/devise/issues/953
16
+ controller.env["devise.skip_trackable"] = true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,149 @@
1
+ require 'active_support/concern'
2
+ require 'devise'
3
+
4
+ require 'simple_token_authentication/entities_manager'
5
+ require 'simple_token_authentication/fallback_authentication_handler'
6
+ require 'simple_token_authentication/sign_in_handler'
7
+ require 'simple_token_authentication/token_comparator'
8
+
9
+ module SimpleTokenAuthentication
10
+ module TokenAuthenticationHandler
11
+ extend ::ActiveSupport::Concern
12
+
13
+ included do
14
+ private_class_method :define_token_authentication_helpers_for
15
+ private_class_method :set_token_authentication_hooks
16
+ private_class_method :entities_manager
17
+ private_class_method :fallback_authentication_handler
18
+
19
+ private :authenticate_entity_from_token!
20
+ private :authenticate_entity_from_fallback!
21
+ private :token_correct?
22
+ private :perform_sign_in!
23
+ private :token_comparator
24
+ private :sign_in_handler
25
+ private :find_record_from_identifier
26
+ private :integrate_with_devise_case_insensitive_keys
27
+ end
28
+
29
+ def authenticate_entity_from_token!(entity)
30
+ record = find_record_from_identifier(entity)
31
+
32
+ if token_correct?(record, entity, token_comparator)
33
+ perform_sign_in!(record, sign_in_handler)
34
+ end
35
+ end
36
+
37
+ def authenticate_entity_from_fallback!(entity, fallback_authentication_handler)
38
+ fallback_authentication_handler.authenticate_entity!(self, entity)
39
+ end
40
+
41
+ def token_correct?(record, entity, token_comparator)
42
+ record && token_comparator.compare(record.authentication_token,
43
+ entity.get_token_from_params_or_headers(self))
44
+ end
45
+
46
+ def perform_sign_in!(record, sign_in_handler)
47
+ # Notice the store option defaults to false, so the record
48
+ # identifier is not actually stored in the session and a token
49
+ # is needed for every request. That behaviour can be configured
50
+ # through the sign_in_token option.
51
+ sign_in_handler.sign_in self, record, store: SimpleTokenAuthentication.sign_in_token
52
+ end
53
+
54
+ def find_record_from_identifier(entity)
55
+ email = entity.get_identifier_from_params_or_headers(self).presence
56
+
57
+ email = integrate_with_devise_case_insensitive_keys(email)
58
+
59
+ # The finder method should be compatible with all the model adapters,
60
+ # namely ActiveRecord and Mongoid in all their supported versions.
61
+ record = nil
62
+ record = email && entity.model.where(email: email).first
63
+ end
64
+
65
+ # Private: Take benefit from Devise case-insensitive keys
66
+ #
67
+ # See https://github.com/plataformatec/devise/blob/v3.4.1/lib/generators/templates/devise.rb#L45-L48
68
+ #
69
+ # email - the original email String
70
+ #
71
+ # Returns an email String which case follows the Devise case-insensitive keys policy
72
+ def integrate_with_devise_case_insensitive_keys(email)
73
+ email.downcase! if email && Devise.case_insensitive_keys.include?(:email)
74
+ email
75
+ end
76
+
77
+ # Private: Get one (always the same) object which behaves as a token comprator
78
+ def token_comparator
79
+ @@token_comparator ||= TokenComparator.new
80
+ end
81
+
82
+ # Private: Get one (always the same) object which behaves as a sign in handler
83
+ def sign_in_handler
84
+ @@sign_in_handler ||= SignInHandler.new
85
+ end
86
+
87
+ module ClassMethods
88
+
89
+ # Provide token authentication handling for a token authenticatable class
90
+ #
91
+ # model - the token authenticatable Class
92
+ #
93
+ # Returns nothing.
94
+ def handle_token_authentication_for(model, options = {})
95
+ name = options[:name] || model.name
96
+ entity = entities_manager.find_or_create_entity(model, name)
97
+ options = SimpleTokenAuthentication.parse_options(options)
98
+ define_token_authentication_helpers_for(entity, fallback_authentication_handler)
99
+ set_token_authentication_hooks(entity, options)
100
+ end
101
+
102
+ # Private: Get one (always the same) object which behaves as an entities manager
103
+ def entities_manager
104
+ if class_variable_defined?(:@@entities_manager)
105
+ class_variable_get(:@@entities_manager)
106
+ else
107
+ class_variable_set(:@@entities_manager, EntitiesManager.new)
108
+ end
109
+ end
110
+
111
+ # Private: Get one (always the same) object which behaves as a fallback authentication handler
112
+ def fallback_authentication_handler
113
+ if class_variable_defined?(:@@fallback_authentication_handler)
114
+ class_variable_get(:@@fallback_authentication_handler)
115
+ else
116
+ class_variable_set(:@@fallback_authentication_handler, FallbackAuthenticationHandler.new)
117
+ end
118
+ end
119
+
120
+ def define_token_authentication_helpers_for(entity, fallback_authentication_handler)
121
+
122
+ method_name = "authenticate_#{entity.name_underscore}_from_token"
123
+ method_name_bang = method_name + '!'
124
+
125
+ class_eval do
126
+ define_method method_name.to_sym do
127
+ lambda { |_entity| authenticate_entity_from_token!(_entity) }.call(entity)
128
+ end
129
+
130
+ define_method method_name_bang.to_sym do
131
+ lambda do |_entity|
132
+ authenticate_entity_from_token!(_entity)
133
+ authenticate_entity_from_fallback!(_entity, fallback_authentication_handler)
134
+ end.call(entity)
135
+ end
136
+ end
137
+ end
138
+
139
+ def set_token_authentication_hooks(entity, options)
140
+ authenticate_method = unless options[:fallback] == :none
141
+ :"authenticate_#{entity.name_underscore}_from_token!"
142
+ else
143
+ :"authenticate_#{entity.name_underscore}_from_token"
144
+ end
145
+ before_filter authenticate_method, options.slice(:only, :except)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,20 @@
1
+ require 'devise'
2
+
3
+ module SimpleTokenAuthentication
4
+ class TokenComparator
5
+
6
+ # Compare two String instances
7
+ #
8
+ # Important: this method is cryptographically critical and
9
+ # must be implemented with care when defining new token comparators.
10
+ #
11
+ # Returns true if String instances do match, false otherwise
12
+ def compare(a, b)
13
+ # Notice how we use Devise.secure_compare to compare tokens
14
+ # while mitigating timing attacks.
15
+ # See http://rubydoc.info/github/plataformatec/\
16
+ # devise/master/Devise#secure_compare-class_method
17
+ Devise.secure_compare(a, b)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require 'devise'
2
+
3
+ module SimpleTokenAuthentication
4
+ class TokenGenerator
5
+ def generate_token
6
+ Devise.friendly_token
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleTokenAuthentication
2
+ VERSION = "1.7.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :simple_token_authentication do
3
+ # # Task goes here
4
+ # end