cbsorcery 0.8.6

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 (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