cbsorcery 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.document +5 -0
  2. data/.gitignore +56 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +40 -0
  5. data/CHANGELOG.md +263 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +360 -0
  9. data/Rakefile +6 -0
  10. data/gemfiles/active_record-rails40.gemfile +7 -0
  11. data/gemfiles/active_record-rails41.gemfile +7 -0
  12. data/lib/generators/sorcery/USAGE +22 -0
  13. data/lib/generators/sorcery/helpers.rb +40 -0
  14. data/lib/generators/sorcery/install_generator.rb +95 -0
  15. data/lib/generators/sorcery/templates/initializer.rb +451 -0
  16. data/lib/generators/sorcery/templates/migration/activity_logging.rb +10 -0
  17. data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +9 -0
  18. data/lib/generators/sorcery/templates/migration/core.rb +13 -0
  19. data/lib/generators/sorcery/templates/migration/external.rb +12 -0
  20. data/lib/generators/sorcery/templates/migration/remember_me.rb +8 -0
  21. data/lib/generators/sorcery/templates/migration/reset_password.rb +9 -0
  22. data/lib/generators/sorcery/templates/migration/user_activation.rb +9 -0
  23. data/lib/sorcery.rb +85 -0
  24. data/lib/sorcery/adapters/active_record_adapter.rb +120 -0
  25. data/lib/sorcery/adapters/base_adapter.rb +30 -0
  26. data/lib/sorcery/controller.rb +157 -0
  27. data/lib/sorcery/controller/config.rb +65 -0
  28. data/lib/sorcery/controller/submodules/activity_logging.rb +82 -0
  29. data/lib/sorcery/controller/submodules/brute_force_protection.rb +38 -0
  30. data/lib/sorcery/controller/submodules/external.rb +199 -0
  31. data/lib/sorcery/controller/submodules/http_basic_auth.rb +74 -0
  32. data/lib/sorcery/controller/submodules/remember_me.rb +81 -0
  33. data/lib/sorcery/controller/submodules/session_timeout.rb +56 -0
  34. data/lib/sorcery/crypto_providers/aes256.rb +51 -0
  35. data/lib/sorcery/crypto_providers/bcrypt.rb +97 -0
  36. data/lib/sorcery/crypto_providers/common.rb +35 -0
  37. data/lib/sorcery/crypto_providers/md5.rb +19 -0
  38. data/lib/sorcery/crypto_providers/sha1.rb +28 -0
  39. data/lib/sorcery/crypto_providers/sha256.rb +36 -0
  40. data/lib/sorcery/crypto_providers/sha512.rb +36 -0
  41. data/lib/sorcery/engine.rb +21 -0
  42. data/lib/sorcery/model.rb +183 -0
  43. data/lib/sorcery/model/config.rb +96 -0
  44. data/lib/sorcery/model/submodules/activity_logging.rb +70 -0
  45. data/lib/sorcery/model/submodules/brute_force_protection.rb +125 -0
  46. data/lib/sorcery/model/submodules/external.rb +100 -0
  47. data/lib/sorcery/model/submodules/remember_me.rb +62 -0
  48. data/lib/sorcery/model/submodules/reset_password.rb +131 -0
  49. data/lib/sorcery/model/submodules/user_activation.rb +149 -0
  50. data/lib/sorcery/model/temporary_token.rb +30 -0
  51. data/lib/sorcery/protocols/certs/ca-bundle.crt +5182 -0
  52. data/lib/sorcery/protocols/oauth.rb +42 -0
  53. data/lib/sorcery/protocols/oauth2.rb +47 -0
  54. data/lib/sorcery/providers/base.rb +27 -0
  55. data/lib/sorcery/providers/facebook.rb +63 -0
  56. data/lib/sorcery/providers/github.rb +51 -0
  57. data/lib/sorcery/providers/google.rb +51 -0
  58. data/lib/sorcery/providers/jira.rb +77 -0
  59. data/lib/sorcery/providers/linkedin.rb +66 -0
  60. data/lib/sorcery/providers/liveid.rb +53 -0
  61. data/lib/sorcery/providers/twitter.rb +59 -0
  62. data/lib/sorcery/providers/vk.rb +63 -0
  63. data/lib/sorcery/providers/xing.rb +64 -0
  64. data/lib/sorcery/railties/tasks.rake +6 -0
  65. data/lib/sorcery/test_helpers/internal.rb +78 -0
  66. data/lib/sorcery/test_helpers/internal/rails.rb +68 -0
  67. data/lib/sorcery/test_helpers/rails/controller.rb +21 -0
  68. data/lib/sorcery/test_helpers/rails/integration.rb +26 -0
  69. data/lib/sorcery/version.rb +3 -0
  70. data/sorcery.gemspec +34 -0
  71. data/spec/active_record/user_activation_spec.rb +18 -0
  72. data/spec/active_record/user_activity_logging_spec.rb +17 -0
  73. data/spec/active_record/user_brute_force_protection_spec.rb +16 -0
  74. data/spec/active_record/user_oauth_spec.rb +16 -0
  75. data/spec/active_record/user_remember_me_spec.rb +16 -0
  76. data/spec/active_record/user_reset_password_spec.rb +16 -0
  77. data/spec/active_record/user_spec.rb +37 -0
  78. data/spec/controllers/controller_activity_logging_spec.rb +124 -0
  79. data/spec/controllers/controller_brute_force_protection_spec.rb +43 -0
  80. data/spec/controllers/controller_http_basic_auth_spec.rb +68 -0
  81. data/spec/controllers/controller_oauth2_spec.rb +407 -0
  82. data/spec/controllers/controller_oauth_spec.rb +240 -0
  83. data/spec/controllers/controller_remember_me_spec.rb +117 -0
  84. data/spec/controllers/controller_session_timeout_spec.rb +80 -0
  85. data/spec/controllers/controller_spec.rb +215 -0
  86. data/spec/orm/active_record.rb +21 -0
  87. data/spec/rails_app/app/active_record/authentication.rb +3 -0
  88. data/spec/rails_app/app/active_record/user.rb +5 -0
  89. data/spec/rails_app/app/active_record/user_provider.rb +3 -0
  90. data/spec/rails_app/app/controllers/sorcery_controller.rb +265 -0
  91. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  92. data/spec/rails_app/app/mailers/sorcery_mailer.rb +32 -0
  93. data/spec/rails_app/app/views/application/index.html.erb +17 -0
  94. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  95. data/spec/rails_app/app/views/sorcery_mailer/activation_email.html.erb +17 -0
  96. data/spec/rails_app/app/views/sorcery_mailer/activation_email.text.erb +9 -0
  97. data/spec/rails_app/app/views/sorcery_mailer/activation_needed_email.html.erb +17 -0
  98. data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.html.erb +17 -0
  99. data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.text.erb +9 -0
  100. data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.html.erb +16 -0
  101. data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.text.erb +8 -0
  102. data/spec/rails_app/app/views/sorcery_mailer/send_unlock_token_email.text.erb +1 -0
  103. data/spec/rails_app/config.ru +4 -0
  104. data/spec/rails_app/config/application.rb +56 -0
  105. data/spec/rails_app/config/boot.rb +4 -0
  106. data/spec/rails_app/config/database.yml +22 -0
  107. data/spec/rails_app/config/environment.rb +5 -0
  108. data/spec/rails_app/config/environments/test.rb +37 -0
  109. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  110. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  111. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  112. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  113. data/spec/rails_app/config/initializers/session_store.rb +12 -0
  114. data/spec/rails_app/config/locales/en.yml +5 -0
  115. data/spec/rails_app/config/routes.rb +48 -0
  116. data/spec/rails_app/db/migrate/activation/20101224223622_add_activation_to_users.rb +17 -0
  117. data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +19 -0
  118. data/spec/rails_app/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +13 -0
  119. data/spec/rails_app/db/migrate/core/20101224223620_create_users.rb +16 -0
  120. data/spec/rails_app/db/migrate/external/20101224223628_create_authentications_and_user_providers.rb +22 -0
  121. data/spec/rails_app/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +15 -0
  122. data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
  123. data/spec/rails_app/db/schema.rb +23 -0
  124. data/spec/rails_app/db/seeds.rb +7 -0
  125. data/spec/shared_examples/user_activation_shared_examples.rb +242 -0
  126. data/spec/shared_examples/user_activity_logging_shared_examples.rb +97 -0
  127. data/spec/shared_examples/user_brute_force_protection_shared_examples.rb +156 -0
  128. data/spec/shared_examples/user_oauth_shared_examples.rb +36 -0
  129. data/spec/shared_examples/user_remember_me_shared_examples.rb +57 -0
  130. data/spec/shared_examples/user_reset_password_shared_examples.rb +263 -0
  131. data/spec/shared_examples/user_shared_examples.rb +467 -0
  132. data/spec/sorcery_crypto_providers_spec.rb +198 -0
  133. data/spec/spec.opts +2 -0
  134. data/spec/spec_helper.rb +41 -0
  135. metadata +350 -0
@@ -0,0 +1,21 @@
1
+ require 'sorcery'
2
+ require 'rails'
3
+
4
+ module Sorcery
5
+ # The Sorcery engine takes care of extending ActiveRecord (if used) and ActionController,
6
+ # With the plugin logic.
7
+ class Engine < Rails::Engine
8
+ config.sorcery = ::Sorcery::Controller::Config
9
+
10
+ initializer "extend Controller with sorcery" do |app|
11
+ ActionController::Base.send(:include, Sorcery::Controller)
12
+ ActionController::Base.helper_method :current_user
13
+ ActionController::Base.helper_method :logged_in?
14
+ end
15
+
16
+ rake_tasks do
17
+ load "sorcery/railties/tasks.rake"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,183 @@
1
+ module Sorcery
2
+ # This module handles all plugin operations which are related to the Model layer in the MVC pattern.
3
+ # It should be included into the ORM base class.
4
+ # In the case of Rails this is usually ActiveRecord (actually, in that case, the plugin does this automatically).
5
+ #
6
+ # When included it defines a single method: 'authenticates_with_sorcery!'
7
+ # which when called adds the other capabilities to the class.
8
+ # This method is also the place to configure the plugin in the Model layer.
9
+ module Model
10
+ def authenticates_with_sorcery!
11
+ @sorcery_config = Config.new
12
+
13
+ extend ClassMethods # included here, before submodules, so they can be overriden by them.
14
+ include InstanceMethods
15
+ include TemporaryToken
16
+
17
+ include_required_submodules!
18
+
19
+ # This runs the options block set in the initializer on the model class.
20
+ ::Sorcery::Controller::Config.user_config.tap{|blk| blk.call(@sorcery_config) if blk}
21
+
22
+ define_base_fields
23
+ init_orm_hooks!
24
+
25
+ @sorcery_config.after_config << :add_config_inheritance if @sorcery_config.subclasses_inherit_config
26
+ @sorcery_config.after_config.each { |c| send(c) }
27
+ end
28
+
29
+ private
30
+
31
+ def define_base_fields
32
+ self.class_eval do
33
+ sorcery_config.username_attribute_names.each do |username|
34
+ sorcery_adapter.define_field username, String, length: 255
35
+ end
36
+ unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
37
+ sorcery_adapter.define_field sorcery_config.email_attribute_name, String, length: 255
38
+ end
39
+ sorcery_adapter.define_field sorcery_config.crypted_password_attribute_name, String, length: 255
40
+ sorcery_adapter.define_field sorcery_config.salt_attribute_name, String, length: 255
41
+ end
42
+
43
+ end
44
+
45
+ # includes required submodules into the model class,
46
+ # which usually is called User.
47
+ def include_required_submodules!
48
+ self.class_eval do
49
+ @sorcery_config.submodules = ::Sorcery::Controller::Config.submodules
50
+ @sorcery_config.submodules.each do |mod|
51
+ begin
52
+ include Submodules.const_get(mod.to_s.split('_').map {|p| p.capitalize}.join)
53
+ rescue NameError
54
+ # don't stop on a missing submodule. Needed because some submodules are only defined
55
+ # in the controller side.
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # add virtual password accessor and ORM callbacks.
62
+ def init_orm_hooks!
63
+ sorcery_adapter.define_callback :before, :validation, :encrypt_password, if: Proc.new {|record|
64
+ record.send(sorcery_config.password_attribute_name).present?
65
+ }
66
+
67
+ sorcery_adapter.define_callback :after, :save, :clear_virtual_password, if: Proc.new {|record|
68
+ record.send(sorcery_config.password_attribute_name).present?
69
+ }
70
+
71
+ attr_accessor sorcery_config.password_attribute_name
72
+ end
73
+
74
+ module ClassMethods
75
+ # Returns the class instance variable for configuration, when called by the class itself.
76
+ def sorcery_config
77
+ @sorcery_config
78
+ end
79
+
80
+ # The default authentication method.
81
+ # Takes a username and password,
82
+ # Finds the user by the username and compares the user's password to the one supplied to the method.
83
+ # returns the user if success, nil otherwise.
84
+ def authenticate(*credentials)
85
+ raise ArgumentError, "at least 2 arguments required" if credentials.size < 2
86
+
87
+ return false if credentials[0].blank?
88
+
89
+ if @sorcery_config.downcase_username_before_authenticating
90
+ credentials[0].downcase!
91
+ end
92
+
93
+ user = sorcery_adapter.find_by_credentials(credentials)
94
+
95
+ set_encryption_attributes
96
+
97
+ _salt = user.send(@sorcery_config.salt_attribute_name) if user && !@sorcery_config.salt_attribute_name.nil? && !@sorcery_config.encryption_provider.nil?
98
+ user if user && @sorcery_config.before_authenticate.all? {|c| user.send(c)} && credentials_match?(user.send(@sorcery_config.crypted_password_attribute_name),credentials[1],_salt)
99
+ end
100
+
101
+ # encrypt tokens using current encryption_provider.
102
+ def encrypt(*tokens)
103
+ return tokens.first if @sorcery_config.encryption_provider.nil?
104
+
105
+ set_encryption_attributes()
106
+
107
+ CryptoProviders::AES256.key = @sorcery_config.encryption_key
108
+ @sorcery_config.encryption_provider.encrypt(*tokens)
109
+ end
110
+
111
+ protected
112
+
113
+ def set_encryption_attributes()
114
+ @sorcery_config.encryption_provider.stretches = @sorcery_config.stretches if @sorcery_config.encryption_provider.respond_to?(:stretches) && @sorcery_config.stretches
115
+ @sorcery_config.encryption_provider.join_token = @sorcery_config.salt_join_token if @sorcery_config.encryption_provider.respond_to?(:join_token) && @sorcery_config.salt_join_token
116
+ end
117
+
118
+ # Calls the configured encryption provider to compare the supplied password with the encrypted one.
119
+ def credentials_match?(crypted, *tokens)
120
+ return crypted == tokens.join if @sorcery_config.encryption_provider.nil?
121
+ @sorcery_config.encryption_provider.matches?(crypted, *tokens)
122
+ end
123
+
124
+ def add_config_inheritance
125
+ self.class_eval do
126
+ def self.inherited(subclass)
127
+ subclass.class_eval do
128
+ class << self
129
+ attr_accessor :sorcery_config
130
+ end
131
+ end
132
+ subclass.sorcery_config = sorcery_config
133
+ super
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ module InstanceMethods
141
+ # Returns the class instance variable for configuration, when called by an instance.
142
+ def sorcery_config
143
+ self.class.sorcery_config
144
+ end
145
+
146
+ # identifies whether this user is regular, i.e. we hold his credentials in our db,
147
+ # or that he is external, and his credentials are saved elsewhere (twitter/facebook etc.).
148
+ def external?
149
+ send(sorcery_config.crypted_password_attribute_name).nil?
150
+ end
151
+
152
+ protected
153
+
154
+ # creates new salt and saves it.
155
+ # encrypts password with salt and saves it.
156
+ def encrypt_password
157
+ config = sorcery_config
158
+ self.send(:"#{config.salt_attribute_name}=", new_salt = TemporaryToken.generate_random_token) if !config.salt_attribute_name.nil?
159
+ self.send(:"#{config.crypted_password_attribute_name}=", self.class.encrypt(self.send(config.password_attribute_name),new_salt))
160
+ end
161
+
162
+ def clear_virtual_password
163
+ config = sorcery_config
164
+ self.send(:"#{config.password_attribute_name}=", nil)
165
+
166
+ if respond_to?(:"#{config.password_attribute_name}_confirmation=")
167
+ self.send(:"#{config.password_attribute_name}_confirmation=", nil)
168
+ end
169
+ end
170
+
171
+ # calls the requested email method on the configured mailer
172
+ # supports both the ActionMailer 3 way of calling, and the plain old Ruby object way.
173
+ def generic_send_email(method, mailer)
174
+ config = sorcery_config
175
+ mail = config.send(mailer).send(config.send(method),self)
176
+ if defined?(ActionMailer) and config.send(mailer).kind_of?(Class) and config.send(mailer) < ActionMailer::Base
177
+ mail.deliver
178
+ end
179
+ end
180
+ end
181
+
182
+ end
183
+ end
@@ -0,0 +1,96 @@
1
+ # Each class which calls 'activate_sorcery!' receives an instance of this class.
2
+ # Every submodule which gets loaded may add accessors to this class so that all
3
+ # options will be configured from a single place.
4
+ module Sorcery
5
+ module Model
6
+ class Config
7
+
8
+ attr_accessor :username_attribute_names, # change default username attribute, for example, to use :email
9
+ # as the login.
10
+
11
+ :password_attribute_name, # change *virtual* password attribute, the one which is used
12
+ # until an encrypted one is generated.
13
+
14
+ :email_attribute_name, # change default email attribute.
15
+
16
+ :downcase_username_before_authenticating, # downcase the username before trying to authenticate, default is false
17
+
18
+ :crypted_password_attribute_name, # change default crypted_password attribute.
19
+ :salt_join_token, # what pattern to use to join the password with the salt
20
+ :salt_attribute_name, # change default salt attribute.
21
+ :stretches, # how many times to apply encryption to the password.
22
+ :encryption_key, # encryption key used to encrypt reversible encryptions such as
23
+ # AES256.
24
+
25
+ :subclasses_inherit_config, # make this configuration inheritable for subclasses. Useful for
26
+ # ActiveRecord's STI.
27
+
28
+ :submodules, # configured in config/application.rb
29
+ :before_authenticate, # an array of method names to call before authentication
30
+ # completes. used internally.
31
+
32
+ :after_config # an array of method names to call after configuration by user.
33
+ # used internally.
34
+
35
+ attr_reader :encryption_provider, # change default encryption_provider.
36
+ :custom_encryption_provider, # use an external encryption class.
37
+ :encryption_algorithm # encryption algorithm name. See 'encryption_algorithm=' below
38
+ # for available options.
39
+
40
+ def initialize
41
+ @defaults = {
42
+ :@submodules => [],
43
+ :@username_attribute_names => [:email],
44
+ :@password_attribute_name => :password,
45
+ :@downcase_username_before_authenticating => false,
46
+ :@email_attribute_name => :email,
47
+ :@crypted_password_attribute_name => :crypted_password,
48
+ :@encryption_algorithm => :bcrypt,
49
+ :@encryption_provider => CryptoProviders::BCrypt,
50
+ :@custom_encryption_provider => nil,
51
+ :@encryption_key => nil,
52
+ :@salt_join_token => "",
53
+ :@salt_attribute_name => :salt,
54
+ :@stretches => nil,
55
+ :@subclasses_inherit_config => false,
56
+ :@before_authenticate => [],
57
+ :@after_config => []
58
+ }
59
+ reset!
60
+ end
61
+
62
+ # Resets all configuration options to their default values.
63
+ def reset!
64
+ @defaults.each do |k,v|
65
+ instance_variable_set(k,v)
66
+ end
67
+ end
68
+
69
+ def username_attribute_names=(fields)
70
+ @username_attribute_names = fields.kind_of?(Array) ? fields : [fields]
71
+ end
72
+
73
+ def custom_encryption_provider=(provider)
74
+ @custom_encryption_provider = @encryption_provider = provider
75
+ end
76
+
77
+ def encryption_algorithm=(algo)
78
+ @encryption_algorithm = algo
79
+ @encryption_provider = case @encryption_algorithm.to_sym
80
+ when :none then nil
81
+ when :md5 then CryptoProviders::MD5
82
+ when :sha1 then CryptoProviders::SHA1
83
+ when :sha256 then CryptoProviders::SHA256
84
+ when :sha512 then CryptoProviders::SHA512
85
+ when :aes256 then CryptoProviders::AES256
86
+ when :bcrypt then CryptoProviders::BCrypt
87
+ when :custom then @custom_encryption_provider
88
+ else raise ArgumentError.new("Encryption algorithm supplied, #{algo}, is invalid")
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+ end
96
+
@@ -0,0 +1,70 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ # This submodule keeps track of events such as login, logout, and last activity time, per user.
5
+ # It helps in estimating which users are active now in the site.
6
+ # This cannot be determined absolutely because a user might be reading a page without clicking anything
7
+ # for a while.
8
+ # This is the model part of the submodule, which provides configuration options.
9
+ module ActivityLogging
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ base.send(:include, InstanceMethods)
13
+
14
+ base.sorcery_config.class_eval do
15
+ attr_accessor :last_login_at_attribute_name, # last login attribute name.
16
+ :last_logout_at_attribute_name, # last logout attribute name.
17
+ :last_activity_at_attribute_name, # last activity attribute name.
18
+ :last_login_from_ip_address_name, # last activity login source
19
+ :activity_timeout # how long since last activity is
20
+ #the user defined logged out?
21
+ end
22
+
23
+ base.sorcery_config.instance_eval do
24
+ @defaults.merge!(:@last_login_at_attribute_name => :last_login_at,
25
+ :@last_logout_at_attribute_name => :last_logout_at,
26
+ :@last_activity_at_attribute_name => :last_activity_at,
27
+ :@last_login_from_ip_address_name => :last_login_from_ip_address,
28
+ :@activity_timeout => 10 * 60)
29
+ reset!
30
+ end
31
+
32
+ base.sorcery_config.after_config << :define_activity_logging_fields
33
+ end
34
+
35
+ module InstanceMethods
36
+ def set_last_login_at(time)
37
+ sorcery_adapter.update_attribute(sorcery_config.last_login_at_attribute_name, time)
38
+ end
39
+
40
+ def set_last_logout_at(time)
41
+ sorcery_adapter.update_attribute(sorcery_config.last_logout_at_attribute_name, time)
42
+ end
43
+
44
+ def set_last_activity_at(time)
45
+ sorcery_adapter.update_attribute(sorcery_config.last_activity_at_attribute_name, time)
46
+ end
47
+
48
+ def set_last_ip_addess(ip_address)
49
+ sorcery_adapter.update_attribute(sorcery_config.last_login_from_ip_address_name, ip_address)
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ # get all users with last_activity within timeout
55
+ def current_users
56
+ sorcery_adapter.get_current_users
57
+ end
58
+
59
+ protected
60
+ def define_activity_logging_fields
61
+ sorcery_adapter.define_field sorcery_config.last_login_at_attribute_name, Time
62
+ sorcery_adapter.define_field sorcery_config.last_logout_at_attribute_name, Time
63
+ sorcery_adapter.define_field sorcery_config.last_activity_at_attribute_name, Time
64
+ sorcery_adapter.define_field sorcery_config.last_login_from_ip_address_name, String
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,125 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ # This module helps protect user accounts by locking them down after too many failed attemps
5
+ # to login were detected.
6
+ # This is the model part of the submodule which provides configuration options and methods
7
+ # for locking and unlocking the user.
8
+ module BruteForceProtection
9
+ def self.included(base)
10
+ base.sorcery_config.class_eval do
11
+ attr_accessor :failed_logins_count_attribute_name, # failed logins attribute name.
12
+ :lock_expires_at_attribute_name, # this field indicates whether user
13
+ # is banned and when it will be active again.
14
+ :consecutive_login_retries_amount_limit, # how many failed logins allowed.
15
+ :login_lock_time_period, # how long the user should be banned.
16
+ # in seconds. 0 for permanent.
17
+
18
+ :unlock_token_attribute_name, # Unlock token attribute name
19
+ :unlock_token_email_method_name, # Mailer method name
20
+ :unlock_token_mailer_disabled, # When true, dont send unlock token via email
21
+ :unlock_token_mailer # Mailer class
22
+ end
23
+
24
+ base.sorcery_config.instance_eval do
25
+ @defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
26
+ :@lock_expires_at_attribute_name => :lock_expires_at,
27
+ :@consecutive_login_retries_amount_limit => 50,
28
+ :@login_lock_time_period => 60 * 60,
29
+
30
+ :@unlock_token_attribute_name => :unlock_token,
31
+ :@unlock_token_email_method_name => :send_unlock_token_email,
32
+ :@unlock_token_mailer_disabled => false,
33
+ :@unlock_token_mailer => nil)
34
+ reset!
35
+ end
36
+
37
+ base.sorcery_config.before_authenticate << :prevent_locked_user_login
38
+ base.sorcery_config.after_config << :define_brute_force_protection_fields
39
+ base.extend(ClassMethods)
40
+ base.send(:include, InstanceMethods)
41
+ end
42
+
43
+ module ClassMethods
44
+ def load_from_unlock_token(token)
45
+ return nil if token.blank?
46
+ user = sorcery_adapter.find_by_token(sorcery_config.unlock_token_attribute_name,token)
47
+ user
48
+ end
49
+
50
+ protected
51
+
52
+ def define_brute_force_protection_fields
53
+ sorcery_adapter.define_field sorcery_config.failed_logins_count_attribute_name, Integer, :default => 0
54
+ sorcery_adapter.define_field sorcery_config.lock_expires_at_attribute_name, Time
55
+ sorcery_adapter.define_field sorcery_config.unlock_token_attribute_name, String
56
+ end
57
+ end
58
+
59
+ module InstanceMethods
60
+ # Called by the controller to increment the failed logins counter.
61
+ # Calls 'lock!' if login retries limit was reached.
62
+ def register_failed_login!
63
+ config = sorcery_config
64
+ return if !unlocked?
65
+
66
+ sorcery_adapter.increment(config.failed_logins_count_attribute_name)
67
+
68
+ if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
69
+ lock!
70
+ end
71
+ end
72
+
73
+ # /!\
74
+ # Moved out of protected for use like activate! in controller
75
+ # /!\
76
+ def unlock!
77
+ config = sorcery_config
78
+ attributes = {config.lock_expires_at_attribute_name => nil,
79
+ config.failed_logins_count_attribute_name => 0,
80
+ config.unlock_token_attribute_name => nil}
81
+ sorcery_adapter.update_attributes(attributes)
82
+ end
83
+
84
+ def locked?
85
+ !unlocked?
86
+ end
87
+
88
+ protected
89
+
90
+ def lock!
91
+ config = sorcery_config
92
+ attributes = {config.lock_expires_at_attribute_name => Time.now.in_time_zone + config.login_lock_time_period,
93
+ config.unlock_token_attribute_name => TemporaryToken.generate_random_token}
94
+ sorcery_adapter.update_attributes(attributes)
95
+
96
+ unless config.unlock_token_mailer_disabled || config.unlock_token_mailer.nil?
97
+ send_unlock_token_email!
98
+ end
99
+ end
100
+
101
+ def unlocked?
102
+ config = sorcery_config
103
+ self.send(config.lock_expires_at_attribute_name).nil?
104
+ end
105
+
106
+ def send_unlock_token_email!
107
+ return if sorcery_config.unlock_token_email_method_name.nil?
108
+
109
+ generic_send_email(:unlock_token_email_method_name, :unlock_token_mailer)
110
+ end
111
+
112
+ # Prevents a locked user from logging in, and unlocks users that expired their lock time.
113
+ # Runs as a hook before authenticate.
114
+ def prevent_locked_user_login
115
+ config = sorcery_config
116
+ if !self.unlocked? && config.login_lock_time_period != 0
117
+ self.unlock! if self.send(config.lock_expires_at_attribute_name) <= Time.now.in_time_zone
118
+ end
119
+ unlocked?
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end