simple_token_authentication 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -24
  3. data/Rakefile +31 -11
  4. data/doc/README.md +18 -0
  5. data/lib/simple_token_authentication.rb +39 -0
  6. data/lib/simple_token_authentication/acts_as_token_authenticatable.rb +18 -7
  7. data/lib/simple_token_authentication/acts_as_token_authentication_handler.rb +12 -123
  8. data/lib/simple_token_authentication/adapter.rb +7 -0
  9. data/lib/simple_token_authentication/adapters/active_record_adapter.rb +14 -0
  10. data/lib/simple_token_authentication/adapters/rails_adapter.rb +14 -0
  11. data/lib/simple_token_authentication/configuration.rb +25 -0
  12. data/lib/simple_token_authentication/entities_manager.rb +10 -0
  13. data/lib/simple_token_authentication/entity.rb +64 -0
  14. data/lib/simple_token_authentication/fallback_authentication_handler.rb +11 -0
  15. data/lib/simple_token_authentication/sign_in_handler.rb +19 -0
  16. data/lib/simple_token_authentication/token_authentication_handler.rb +138 -0
  17. data/lib/simple_token_authentication/token_comparator.rb +13 -0
  18. data/lib/simple_token_authentication/token_generator.rb +9 -0
  19. data/lib/simple_token_authentication/version.rb +1 -1
  20. data/spec/configuration/action_controller_callbacks_options_spec.rb +53 -0
  21. data/spec/configuration/fallback_to_devise_option_spec.rb +128 -0
  22. data/spec/configuration/header_names_option_spec.rb +454 -0
  23. data/spec/configuration/sign_in_token_option_spec.rb +92 -0
  24. data/spec/lib/simple_token_authentication/acts_as_token_authenticatable_spec.rb +108 -0
  25. data/spec/lib/simple_token_authentication/acts_as_token_authentication_handler_spec.rb +127 -0
  26. data/spec/lib/simple_token_authentication/adapter_spec.rb +21 -0
  27. data/spec/lib/simple_token_authentication/adapters/active_record_adapter_spec.rb +21 -0
  28. data/spec/lib/simple_token_authentication/adapters/rails_adapter_spec.rb +21 -0
  29. data/spec/lib/simple_token_authentication/configuration_spec.rb +121 -0
  30. data/spec/lib/simple_token_authentication/entities_manager_spec.rb +67 -0
  31. data/spec/lib/simple_token_authentication/entity_spec.rb +190 -0
  32. data/spec/lib/simple_token_authentication/fallback_authentication_handler_spec.rb +24 -0
  33. data/spec/lib/simple_token_authentication/sign_in_handler_spec.rb +43 -0
  34. data/spec/lib/simple_token_authentication/token_authentication_handler_spec.rb +250 -0
  35. data/spec/lib/simple_token_authentication/token_comparator_spec.rb +19 -0
  36. data/spec/lib/simple_token_authentication/token_generator_spec.rb +19 -0
  37. data/spec/lib/simple_token_authentication_spec.rb +86 -0
  38. data/spec/spec_helper.rb +13 -0
  39. data/spec/support/dummy_classes_helper.rb +80 -0
  40. data/spec/support/spec_for_adapter.rb +6 -0
  41. data/spec/support/spec_for_authentication_handler_interface.rb +8 -0
  42. data/spec/support/spec_for_configuration_option_interface.rb +28 -0
  43. data/spec/support/spec_for_entities_manager_interface.rb +8 -0
  44. data/spec/support/spec_for_sign_in_handler_interface.rb +8 -0
  45. data/spec/support/spec_for_token_comparator_interface.rb +8 -0
  46. data/spec/support/spec_for_token_generator_interface.rb +8 -0
  47. data/spec/support/specs_for_token_authentication_handler_interface.rb +8 -0
  48. metadata +80 -132
  49. data/lib/tasks/cucumber.rake +0 -65
  50. data/spec/dummy/README.rdoc +0 -28
  51. data/spec/dummy/Rakefile +0 -6
  52. data/spec/dummy/app/assets/javascripts/application.js +0 -13
  53. data/spec/dummy/app/assets/stylesheets/application.css +0 -13
  54. data/spec/dummy/app/controllers/application_controller.rb +0 -5
  55. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  56. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  57. data/spec/dummy/bin/bundle +0 -3
  58. data/spec/dummy/bin/rails +0 -4
  59. data/spec/dummy/bin/rake +0 -4
  60. data/spec/dummy/config.ru +0 -4
  61. data/spec/dummy/config/application.rb +0 -24
  62. data/spec/dummy/config/boot.rb +0 -5
  63. data/spec/dummy/config/database.yml +0 -25
  64. data/spec/dummy/config/environment.rb +0 -5
  65. data/spec/dummy/config/environments/development.rb +0 -29
  66. data/spec/dummy/config/environments/production.rb +0 -80
  67. data/spec/dummy/config/environments/test.rb +0 -36
  68. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  69. data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  70. data/spec/dummy/config/initializers/inflections.rb +0 -16
  71. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  72. data/spec/dummy/config/initializers/secret_token.rb +0 -12
  73. data/spec/dummy/config/initializers/session_store.rb +0 -3
  74. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  75. data/spec/dummy/config/locales/en.yml +0 -23
  76. data/spec/dummy/config/routes.rb +0 -56
  77. data/spec/dummy/public/404.html +0 -58
  78. data/spec/dummy/public/422.html +0 -58
  79. data/spec/dummy/public/500.html +0 -57
  80. data/spec/dummy/public/favicon.ico +0 -0
@@ -1,15 +1,40 @@
1
1
  module SimpleTokenAuthentication
2
2
  module Configuration
3
3
 
4
+ mattr_reader :fallback
4
5
  mattr_accessor :header_names
5
6
  mattr_accessor :sign_in_token
7
+ mattr_accessor :controller_adapters
8
+ mattr_accessor :model_adapters
6
9
 
7
10
  # Default configuration
11
+ @@fallback = :devise
8
12
  @@header_names = {}
9
13
  @@sign_in_token = false
14
+ @@controller_adapters = ['rails']
15
+ @@model_adapters = ['active_record']
10
16
 
17
+ # Allow the default configuration to be overwritten from initializers
11
18
  def configure
12
19
  yield self if block_given?
13
20
  end
21
+
22
+ def parse_options(options)
23
+ unless options[:fallback].presence
24
+ if options[:fallback_to_devise]
25
+ options[:fallback] = :devise
26
+ elsif options[:fallback_to_devise] == false
27
+ if SimpleTokenAuthentication.fallback == :devise
28
+ options[:fallback] = :none
29
+ else
30
+ options[:fallback] = SimpleTokenAuthentication.fallback
31
+ end
32
+ else
33
+ options[:fallback] = SimpleTokenAuthentication.fallback
34
+ end
35
+ end
36
+ options.reject! { |k,v| k == :fallback_to_devise }
37
+ options
38
+ end
14
39
  end
15
40
  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)
6
+ @entities ||= {}
7
+ @entities[model.name] ||= Entity.new(model)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,64 @@
1
+ module SimpleTokenAuthentication
2
+ class Entity
3
+ def initialize model
4
+ @model = model
5
+ @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,138 @@
1
+ require 'action_controller/base'
2
+ require 'active_support/concern'
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_authentication_handler'
8
+ require 'simple_token_authentication/token_comparator'
9
+
10
+ module SimpleTokenAuthentication
11
+ module TokenAuthenticationHandler
12
+ extend ::ActiveSupport::Concern
13
+
14
+ included do
15
+ private_class_method :define_token_authentication_helpers_for
16
+ private_class_method :set_token_authentication_hooks
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
+
27
+ # This is necessary to test which arguments were passed to sign_in
28
+ # from authenticate_entity_from_token!
29
+ # See https://github.com/gonzalo-bulnes/simple_token_authentication/pull/32
30
+ ::ActionController::Base.send :include, Devise::Controllers::SignInOut if Rails.env.test?
31
+ end
32
+
33
+ def authenticate_entity_from_token!(entity)
34
+ record = find_record_from_identifier(entity)
35
+
36
+ if token_correct?(record, entity, token_comparator)
37
+ perform_sign_in!(record, sign_in_handler)
38
+ end
39
+ end
40
+
41
+ def authenticate_entity_from_fallback!(entity, fallback_authentication_handler)
42
+ fallback_authentication_handler.authenticate_entity!(self, entity)
43
+ end
44
+
45
+ def token_correct?(record, entity, token_comparator)
46
+ record && token_comparator.compare(record.authentication_token,
47
+ entity.get_token_from_params_or_headers(self))
48
+ end
49
+
50
+ def perform_sign_in!(record, sign_in_handler)
51
+ # Notice the store option defaults to false, so the record
52
+ # identifier is not actually stored in the session and a token
53
+ # is needed for every request. That behaviour can be configured
54
+ # through the sign_in_token option.
55
+ sign_in_handler.sign_in self, record, store: SimpleTokenAuthentication.sign_in_token
56
+ end
57
+
58
+ def find_record_from_identifier(entity)
59
+ email = entity.get_identifier_from_params_or_headers(self).presence
60
+
61
+ # Rails 3 and 4 finder methods are supported,
62
+ # see https://github.com/ryanb/cancan/blob/1.6.10/lib/cancan/controller_resource.rb#L108-L111
63
+ record = nil
64
+ if entity.model.respond_to? "find_by"
65
+ record = email && entity.model.find_by(email: email)
66
+ elsif entity.model.respond_to? "find_by_email"
67
+ record = email && entity.model.find_by_email(email)
68
+ end
69
+ end
70
+
71
+ def token_comparator
72
+ @@token_comparator ||= TokenComparator.new
73
+ end
74
+
75
+ def sign_in_handler
76
+ @@sign_in_handler ||= SignInHandler.new
77
+ end
78
+
79
+ module ClassMethods
80
+
81
+ # Provide token authentication handling for a token authenticatable class
82
+ #
83
+ # model - the token authenticatable Class
84
+ #
85
+ # Returns nothing.
86
+ def handle_token_authentication_for(model, options = {})
87
+ entity = entities_manager.find_or_create_entity(model)
88
+ options = SimpleTokenAuthentication.parse_options(options)
89
+ define_token_authentication_helpers_for(entity, fallback_authentication_handler)
90
+ set_token_authentication_hooks(entity, options)
91
+ end
92
+
93
+ def entities_manager
94
+ if class_variable_defined?(:@@entities_manager)
95
+ class_variable_get(:@@entities_manager)
96
+ else
97
+ class_variable_set(:@@entities_manager, EntitiesManager.new)
98
+ end
99
+ end
100
+
101
+ def fallback_authentication_handler
102
+ if class_variable_defined?(:@@fallback_authentication_handler)
103
+ class_variable_get(:@@fallback_authentication_handler)
104
+ else
105
+ class_variable_set(:@@fallback_authentication_handler, FallbackAuthenticationHandler.new)
106
+ end
107
+ end
108
+
109
+ def define_token_authentication_helpers_for(entity, fallback_authentication_handler)
110
+
111
+ method_name = "authenticate_#{entity.name_underscore}_from_token"
112
+ method_name_bang = method_name + '!'
113
+
114
+ class_eval do
115
+ define_method method_name.to_sym do
116
+ lambda { |entity| authenticate_entity_from_token!(entity) }.call(entity)
117
+ end
118
+
119
+ define_method method_name_bang.to_sym do
120
+ lambda do |entity|
121
+ authenticate_entity_from_token!(entity)
122
+ authenticate_entity_from_fallback!(entity, fallback_authentication_handler)
123
+ end.call(entity)
124
+ end
125
+ end
126
+ end
127
+
128
+ def set_token_authentication_hooks(entity, options)
129
+ authenticate_method = unless options[:fallback] == :none
130
+ :"authenticate_#{entity.name_underscore}_from_token!"
131
+ else
132
+ :"authenticate_#{entity.name_underscore}_from_token"
133
+ end
134
+ before_filter authenticate_method, options.slice(:only, :except)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,13 @@
1
+ require 'devise'
2
+
3
+ module SimpleTokenAuthentication
4
+ class TokenComparator
5
+ def compare(a, b)
6
+ # Notice how we use Devise.secure_compare to compare tokens
7
+ # while mitigating timing attacks.
8
+ # See http://rubydoc.info/github/plataformatec/\
9
+ # devise/master/Devise#secure_compare-class_method
10
+ Devise.secure_compare(a, b)
11
+ end
12
+ end
13
+ 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
@@ -1,3 +1,3 @@
1
1
  module SimpleTokenAuthentication
2
- VERSION = "1.5.1"
2
+ VERSION = "1.5.2"
3
3
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActionController', action_controller_callbacks_options: true do
4
+
5
+ after(:each) do
6
+ ensure_examples_independence
7
+ end
8
+
9
+ before(:each) do
10
+ double_user_model
11
+ define_test_subjects_for_extension_of(SimpleTokenAuthentication::ActsAsTokenAuthenticationHandler)
12
+ end
13
+
14
+ describe ':only option' do
15
+
16
+ context 'when provided to `acts_as_token_authentication_hanlder_for`' do
17
+
18
+ it 'is applied to the corresponding callback (1)', rspec_3_error: true, private: true do
19
+ some_class = @subjects.first
20
+
21
+ expect(some_class).to receive(:before_filter).with(:authenticate_user_from_token!, { only: ['some_action', :some_other_action] })
22
+ some_class.acts_as_token_authentication_handler_for User, only: ['some_action', :some_other_action]
23
+ end
24
+
25
+ it 'is applied to the corresponding callback (2)', rspec_3_error: true, private: true do
26
+ some_child_class = @subjects.last
27
+
28
+ expect(some_child_class).to receive(:before_filter).with(:authenticate_user_from_token!, { only: ['some_action', :some_other_action] })
29
+ some_child_class.acts_as_token_authentication_handler_for User, only: ['some_action', :some_other_action]
30
+ end
31
+ end
32
+ end
33
+
34
+ describe ':except option' do
35
+
36
+ context 'when provided to `acts_as_token_authentication_hanlder_for`' do
37
+
38
+ it 'is applied to the corresponding callback (1)', rspec_3_error: true, private: true do
39
+ some_class = @subjects.first
40
+
41
+ expect(some_class).to receive(:before_filter).with(:authenticate_user_from_token!, { except: ['some_action', :some_other_action] })
42
+ some_class.acts_as_token_authentication_handler_for User, except: ['some_action', :some_other_action]
43
+ end
44
+
45
+ it 'is applied to the corresponding callback (2)', rspec_3_error: true, private: true do
46
+ some_child_class = @subjects.last
47
+
48
+ expect(some_child_class).to receive(:before_filter).with(:authenticate_user_from_token!, { except: ['some_action', :some_other_action] })
49
+ some_child_class.acts_as_token_authentication_handler_for User, except: ['some_action', :some_other_action]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Simple Token Authentication' do
4
+
5
+ describe ':fallback_to_devise option', fallback_to_devise_option: true, fallback_option: true do
6
+
7
+ describe 'determines what to do if token authentication fails' do
8
+
9
+ before(:each) do
10
+ user = double()
11
+ stub_const('User', user)
12
+ allow(user).to receive(:name).and_return('User')
13
+
14
+ # given a controller class which acts as token authentication handler
15
+ @controller_class = Class.new
16
+ allow(@controller_class).to receive(:before_filter)
17
+ @controller_class.send :extend, SimpleTokenAuthentication::ActsAsTokenAuthenticationHandler
18
+ end
19
+
20
+ context 'when true' do
21
+
22
+ it 'delegates authentication to Devise strategies', protected: true do
23
+ @controller = @controller_class.new
24
+ allow(@controller).to receive(:params)
25
+ allow(@controller).to receive(:find_record_from_identifier)
26
+
27
+ # sets :authenticate_user_from_token! (bang) in the before_filter
28
+ expect(@controller_class).to receive(:before_filter).with(:authenticate_user_from_token!, {})
29
+
30
+ # when falling back to Devise is enabled
31
+ @controller_class.acts_as_token_authentication_handler_for User, fallback_to_devise: true
32
+
33
+ # when the hook is triggered
34
+ # Devise strategies take control of authentication
35
+ expect(@controller).to receive(:authenticate_user!)
36
+ @controller.authenticate_user_from_token! # bang
37
+ end
38
+ end
39
+
40
+ context 'when false' do
41
+
42
+ it 'does nothing after token authentication fails', protected: true do
43
+ @controller = @controller_class.new
44
+ allow(@controller).to receive(:params)
45
+ allow(@controller).to receive(:find_record_from_identifier)
46
+
47
+ # sets :authenticate_user_from_token (non-bang) in the before_filter
48
+ expect(@controller_class).to receive(:before_filter).with(:authenticate_user_from_token, {})
49
+
50
+ # when falling back to Devise is enabled
51
+ @controller_class.acts_as_token_authentication_handler_for User, fallback_to_devise: false
52
+
53
+ # when the hook is triggered
54
+ # Devise strategies do not take control of authentication
55
+ expect(@controller).not_to receive(:authenticate_user!)
56
+ @controller.authenticate_user_from_token # non-bang
57
+ end
58
+ end
59
+
60
+ context 'when omitted' do
61
+
62
+ it 'delegates authentication to Devise strategies', protected: true do
63
+ @controller = @controller_class.new
64
+ allow(@controller).to receive(:params)
65
+ allow(@controller).to receive(:find_record_from_identifier)
66
+
67
+ # sets :authenticate_user_from_token! (bang) in the before_filter
68
+ expect(@controller_class).to receive(:before_filter).with(:authenticate_user_from_token!, {})
69
+
70
+ # when falling back to Devise is enabled
71
+ @controller_class.acts_as_token_authentication_handler_for User
72
+
73
+ # when the hook is triggered
74
+ # Devise strategies take control of authentication
75
+ expect(@controller).to receive(:authenticate_user!)
76
+ @controller.authenticate_user_from_token! # bang
77
+ end
78
+ end
79
+
80
+ describe 'in a per-model (token authenticatable) way' do
81
+
82
+ before(:each) do
83
+ admin = double()
84
+ stub_const('Admin', admin)
85
+ allow(admin).to receive(:name).and_return('Admin')
86
+ end
87
+
88
+ context 'when false for User and true for Admin' do
89
+
90
+ before(:each) do
91
+ @controller = @controller_class.new
92
+ allow(@controller).to receive(:params)
93
+ allow(@controller).to receive(:find_record_from_identifier)
94
+
95
+ # sets :authenticate_user_from_token (non-bang) in the before_filter
96
+ expect(@controller_class).to receive(:before_filter).with(:authenticate_user_from_token, {})
97
+ # sets :authenticate_admin_from_token! (bang) in the before_filter
98
+ expect(@controller_class).to receive(:before_filter).with(:authenticate_admin_from_token!, {})
99
+
100
+ # when falling back to Devise is enabled for Admin but not User
101
+ @controller_class.acts_as_token_authentication_handler_for User, fallback_to_devise: false
102
+ @controller_class.acts_as_token_authentication_handler_for Admin, fallback_to_devise: true
103
+ end
104
+
105
+ context 'after no user suceeds token authentication' do
106
+
107
+ it 'does nothing', protected: true do
108
+ # when the user hook is triggered
109
+ # Devise strategies do not take control of authentication
110
+ expect(@controller).not_to receive(:authenticate_user!)
111
+ @controller.authenticate_user_from_token
112
+ end
113
+ end
114
+
115
+ context 'after no admin succeeds token authentication' do
116
+
117
+ it 'does delegate authentication to Devise', protected: true do
118
+ # when the admin hook is triggered
119
+ # Devise strategies do take control of authentication
120
+ expect(@controller).to receive(:authenticate_admin!)
121
+ @controller.authenticate_admin_from_token!
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end