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.
- data/.document +5 -0
- data/.gitignore +56 -0
- data/.rspec +1 -0
- data/.travis.yml +40 -0
- data/CHANGELOG.md +263 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +360 -0
- data/Rakefile +6 -0
- data/gemfiles/active_record-rails40.gemfile +7 -0
- data/gemfiles/active_record-rails41.gemfile +7 -0
- data/lib/generators/sorcery/USAGE +22 -0
- data/lib/generators/sorcery/helpers.rb +40 -0
- data/lib/generators/sorcery/install_generator.rb +95 -0
- data/lib/generators/sorcery/templates/initializer.rb +451 -0
- data/lib/generators/sorcery/templates/migration/activity_logging.rb +10 -0
- data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +9 -0
- data/lib/generators/sorcery/templates/migration/core.rb +13 -0
- data/lib/generators/sorcery/templates/migration/external.rb +12 -0
- data/lib/generators/sorcery/templates/migration/remember_me.rb +8 -0
- data/lib/generators/sorcery/templates/migration/reset_password.rb +9 -0
- data/lib/generators/sorcery/templates/migration/user_activation.rb +9 -0
- data/lib/sorcery.rb +85 -0
- data/lib/sorcery/adapters/active_record_adapter.rb +120 -0
- data/lib/sorcery/adapters/base_adapter.rb +30 -0
- data/lib/sorcery/controller.rb +157 -0
- data/lib/sorcery/controller/config.rb +65 -0
- data/lib/sorcery/controller/submodules/activity_logging.rb +82 -0
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +38 -0
- data/lib/sorcery/controller/submodules/external.rb +199 -0
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +74 -0
- data/lib/sorcery/controller/submodules/remember_me.rb +81 -0
- data/lib/sorcery/controller/submodules/session_timeout.rb +56 -0
- data/lib/sorcery/crypto_providers/aes256.rb +51 -0
- data/lib/sorcery/crypto_providers/bcrypt.rb +97 -0
- data/lib/sorcery/crypto_providers/common.rb +35 -0
- data/lib/sorcery/crypto_providers/md5.rb +19 -0
- data/lib/sorcery/crypto_providers/sha1.rb +28 -0
- data/lib/sorcery/crypto_providers/sha256.rb +36 -0
- data/lib/sorcery/crypto_providers/sha512.rb +36 -0
- data/lib/sorcery/engine.rb +21 -0
- data/lib/sorcery/model.rb +183 -0
- data/lib/sorcery/model/config.rb +96 -0
- data/lib/sorcery/model/submodules/activity_logging.rb +70 -0
- data/lib/sorcery/model/submodules/brute_force_protection.rb +125 -0
- data/lib/sorcery/model/submodules/external.rb +100 -0
- data/lib/sorcery/model/submodules/remember_me.rb +62 -0
- data/lib/sorcery/model/submodules/reset_password.rb +131 -0
- data/lib/sorcery/model/submodules/user_activation.rb +149 -0
- data/lib/sorcery/model/temporary_token.rb +30 -0
- data/lib/sorcery/protocols/certs/ca-bundle.crt +5182 -0
- data/lib/sorcery/protocols/oauth.rb +42 -0
- data/lib/sorcery/protocols/oauth2.rb +47 -0
- data/lib/sorcery/providers/base.rb +27 -0
- data/lib/sorcery/providers/facebook.rb +63 -0
- data/lib/sorcery/providers/github.rb +51 -0
- data/lib/sorcery/providers/google.rb +51 -0
- data/lib/sorcery/providers/jira.rb +77 -0
- data/lib/sorcery/providers/linkedin.rb +66 -0
- data/lib/sorcery/providers/liveid.rb +53 -0
- data/lib/sorcery/providers/twitter.rb +59 -0
- data/lib/sorcery/providers/vk.rb +63 -0
- data/lib/sorcery/providers/xing.rb +64 -0
- data/lib/sorcery/railties/tasks.rake +6 -0
- data/lib/sorcery/test_helpers/internal.rb +78 -0
- data/lib/sorcery/test_helpers/internal/rails.rb +68 -0
- data/lib/sorcery/test_helpers/rails/controller.rb +21 -0
- data/lib/sorcery/test_helpers/rails/integration.rb +26 -0
- data/lib/sorcery/version.rb +3 -0
- data/sorcery.gemspec +34 -0
- data/spec/active_record/user_activation_spec.rb +18 -0
- data/spec/active_record/user_activity_logging_spec.rb +17 -0
- data/spec/active_record/user_brute_force_protection_spec.rb +16 -0
- data/spec/active_record/user_oauth_spec.rb +16 -0
- data/spec/active_record/user_remember_me_spec.rb +16 -0
- data/spec/active_record/user_reset_password_spec.rb +16 -0
- data/spec/active_record/user_spec.rb +37 -0
- data/spec/controllers/controller_activity_logging_spec.rb +124 -0
- data/spec/controllers/controller_brute_force_protection_spec.rb +43 -0
- data/spec/controllers/controller_http_basic_auth_spec.rb +68 -0
- data/spec/controllers/controller_oauth2_spec.rb +407 -0
- data/spec/controllers/controller_oauth_spec.rb +240 -0
- data/spec/controllers/controller_remember_me_spec.rb +117 -0
- data/spec/controllers/controller_session_timeout_spec.rb +80 -0
- data/spec/controllers/controller_spec.rb +215 -0
- data/spec/orm/active_record.rb +21 -0
- data/spec/rails_app/app/active_record/authentication.rb +3 -0
- data/spec/rails_app/app/active_record/user.rb +5 -0
- data/spec/rails_app/app/active_record/user_provider.rb +3 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +265 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/sorcery_mailer.rb +32 -0
- data/spec/rails_app/app/views/application/index.html.erb +17 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_email.text.erb +9 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_needed_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.html.erb +17 -0
- data/spec/rails_app/app/views/sorcery_mailer/activation_success_email.text.erb +9 -0
- data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.html.erb +16 -0
- data/spec/rails_app/app/views/sorcery_mailer/reset_password_email.text.erb +8 -0
- data/spec/rails_app/app/views/sorcery_mailer/send_unlock_token_email.text.erb +1 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +56 -0
- data/spec/rails_app/config/boot.rb +4 -0
- data/spec/rails_app/config/database.yml +22 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +12 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +48 -0
- data/spec/rails_app/db/migrate/activation/20101224223622_add_activation_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +19 -0
- data/spec/rails_app/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +13 -0
- data/spec/rails_app/db/migrate/core/20101224223620_create_users.rb +16 -0
- data/spec/rails_app/db/migrate/external/20101224223628_create_authentications_and_user_providers.rb +22 -0
- data/spec/rails_app/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +15 -0
- data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
- data/spec/rails_app/db/schema.rb +23 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/shared_examples/user_activation_shared_examples.rb +242 -0
- data/spec/shared_examples/user_activity_logging_shared_examples.rb +97 -0
- data/spec/shared_examples/user_brute_force_protection_shared_examples.rb +156 -0
- data/spec/shared_examples/user_oauth_shared_examples.rb +36 -0
- data/spec/shared_examples/user_remember_me_shared_examples.rb +57 -0
- data/spec/shared_examples/user_reset_password_shared_examples.rb +263 -0
- data/spec/shared_examples/user_shared_examples.rb +467 -0
- data/spec/sorcery_crypto_providers_spec.rb +198 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +41 -0
- 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
|