stn-simple_token_authentication 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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