sorcery 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sorcery might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -26
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +1 -1
  5. data/README.md +2 -1
  6. data/lib/generators/sorcery/templates/initializer.rb +85 -85
  7. data/lib/generators/sorcery/templates/migration/activity_logging.rb +4 -4
  8. data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +3 -3
  9. data/lib/generators/sorcery/templates/migration/core.rb +2 -2
  10. data/lib/generators/sorcery/templates/migration/external.rb +3 -3
  11. data/lib/generators/sorcery/templates/migration/magic_login.rb +3 -3
  12. data/lib/generators/sorcery/templates/migration/remember_me.rb +2 -2
  13. data/lib/generators/sorcery/templates/migration/reset_password.rb +4 -4
  14. data/lib/generators/sorcery/templates/migration/user_activation.rb +3 -3
  15. data/lib/sorcery/controller/submodules/activity_logging.rb +10 -3
  16. data/lib/sorcery/controller/submodules/brute_force_protection.rb +7 -3
  17. data/lib/sorcery/controller/submodules/external.rb +1 -0
  18. data/lib/sorcery/controller/submodules/http_basic_auth.rb +4 -1
  19. data/lib/sorcery/controller/submodules/remember_me.rb +7 -2
  20. data/lib/sorcery/controller/submodules/session_timeout.rb +7 -2
  21. data/lib/sorcery/crypto_providers/aes256.rb +1 -1
  22. data/lib/sorcery/crypto_providers/bcrypt.rb +6 -1
  23. data/lib/sorcery/model.rb +1 -0
  24. data/lib/sorcery/model/config.rb +5 -0
  25. data/lib/sorcery/model/submodules/magic_login.rb +7 -4
  26. data/lib/sorcery/model/submodules/reset_password.rb +6 -2
  27. data/lib/sorcery/providers/line.rb +47 -0
  28. data/lib/sorcery/providers/linkedin.rb +20 -36
  29. data/lib/sorcery/version.rb +1 -1
  30. data/spec/controllers/controller_oauth2_spec.rb +8 -0
  31. data/spec/rails_app/app/controllers/sorcery_controller.rb +20 -0
  32. data/spec/rails_app/config/routes.rb +3 -0
  33. data/spec/shared_examples/user_reset_password_shared_examples.rb +18 -2
  34. data/spec/shared_examples/user_shared_examples.rb +63 -0
  35. data/spec/sorcery_crypto_providers_spec.rb +60 -0
  36. metadata +3 -5
  37. data/gemfiles/active_record_rails_40.gemfile +0 -6
  38. data/gemfiles/active_record_rails_41.gemfile +0 -6
  39. data/gemfiles/active_record_rails_42.gemfile +0 -6
@@ -1,9 +1,9 @@
1
1
  class SorceryActivityLogging < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :last_login_at, :datetime, :default => nil
4
- add_column :<%= model_class_name.tableize %>, :last_logout_at, :datetime, :default => nil
5
- add_column :<%= model_class_name.tableize %>, :last_activity_at, :datetime, :default => nil
6
- add_column :<%= model_class_name.tableize %>, :last_login_from_ip_address, :string, :default => nil
3
+ add_column :<%= model_class_name.tableize %>, :last_login_at, :datetime, default: nil
4
+ add_column :<%= model_class_name.tableize %>, :last_logout_at, :datetime, default: nil
5
+ add_column :<%= model_class_name.tableize %>, :last_activity_at, :datetime, default: nil
6
+ add_column :<%= model_class_name.tableize %>, :last_login_from_ip_address, :string, default: nil
7
7
 
8
8
  add_index :<%= model_class_name.tableize %>, [:last_logout_at, :last_activity_at]
9
9
  end
@@ -1,8 +1,8 @@
1
1
  class SorceryBruteForceProtection < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :failed_logins_count, :integer, :default => 0
4
- add_column :<%= model_class_name.tableize %>, :lock_expires_at, :datetime, :default => nil
5
- add_column :<%= model_class_name.tableize %>, :unlock_token, :string, :default => nil
3
+ add_column :<%= model_class_name.tableize %>, :failed_logins_count, :integer, default: 0
4
+ add_column :<%= model_class_name.tableize %>, :lock_expires_at, :datetime, default: nil
5
+ add_column :<%= model_class_name.tableize %>, :unlock_token, :string, default: nil
6
6
 
7
7
  add_index :<%= model_class_name.tableize %>, :unlock_token
8
8
  end
@@ -1,11 +1,11 @@
1
1
  class SorceryCore < <%= migration_class_name %>
2
2
  def change
3
3
  create_table :<%= model_class_name.tableize %> do |t|
4
- t.string :email, :null => false
4
+ t.string :email, null: false
5
5
  t.string :crypted_password
6
6
  t.string :salt
7
7
 
8
- t.timestamps :null => false
8
+ t.timestamps null: false
9
9
  end
10
10
 
11
11
  add_index :<%= model_class_name.tableize %>, :email, unique: true
@@ -1,10 +1,10 @@
1
1
  class SorceryExternal < <%= migration_class_name %>
2
2
  def change
3
3
  create_table :authentications do |t|
4
- t.integer :<%= model_class_name.tableize.singularize %>_id, :null => false
5
- t.string :provider, :uid, :null => false
4
+ t.integer :<%= model_class_name.tableize.singularize %>_id, null: false
5
+ t.string :provider, :uid, null: false
6
6
 
7
- t.timestamps :null => false
7
+ t.timestamps null: false
8
8
  end
9
9
 
10
10
  add_index :authentications, [:provider, :uid]
@@ -1,8 +1,8 @@
1
1
  class SorceryMagicLogin < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :magic_login_token, :string, :default => nil
4
- add_column :<%= model_class_name.tableize %>, :magic_login_token_expires_at, :datetime, :default => nil
5
- add_column :<%= model_class_name.tableize %>, :magic_login_email_sent_at, :datetime, :default => nil
3
+ add_column :<%= model_class_name.tableize %>, :magic_login_token, :string, default: nil
4
+ add_column :<%= model_class_name.tableize %>, :magic_login_token_expires_at, :datetime, default: nil
5
+ add_column :<%= model_class_name.tableize %>, :magic_login_email_sent_at, :datetime, default: nil
6
6
 
7
7
  add_index :<%= model_class_name.tableize %>, :magic_login_token
8
8
  end
@@ -1,7 +1,7 @@
1
1
  class SorceryRememberMe < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :remember_me_token, :string, :default => nil
4
- add_column :<%= model_class_name.tableize %>, :remember_me_token_expires_at, :datetime, :default => nil
3
+ add_column :<%= model_class_name.tableize %>, :remember_me_token, :string, default: nil
4
+ add_column :<%= model_class_name.tableize %>, :remember_me_token_expires_at, :datetime, default: nil
5
5
 
6
6
  add_index :<%= model_class_name.tableize %>, :remember_me_token
7
7
  end
@@ -1,9 +1,9 @@
1
1
  class SorceryResetPassword < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :reset_password_token, :string, :default => nil
4
- add_column :<%= model_class_name.tableize %>, :reset_password_token_expires_at, :datetime, :default => nil
5
- add_column :<%= model_class_name.tableize %>, :reset_password_email_sent_at, :datetime, :default => nil
6
- add_column :<%= model_class_name.tableize %>, :access_count_to_reset_password_page, :integer, :default => 0
3
+ add_column :<%= model_class_name.tableize %>, :reset_password_token, :string, default: nil
4
+ add_column :<%= model_class_name.tableize %>, :reset_password_token_expires_at, :datetime, default: nil
5
+ add_column :<%= model_class_name.tableize %>, :reset_password_email_sent_at, :datetime, default: nil
6
+ add_column :<%= model_class_name.tableize %>, :access_count_to_reset_password_page, :integer, default: 0
7
7
 
8
8
  add_index :<%= model_class_name.tableize %>, :reset_password_token
9
9
  end
@@ -1,8 +1,8 @@
1
1
  class SorceryUserActivation < <%= migration_class_name %>
2
2
  def change
3
- add_column :<%= model_class_name.tableize %>, :activation_state, :string, :default => nil
4
- add_column :<%= model_class_name.tableize %>, :activation_token, :string, :default => nil
5
- add_column :<%= model_class_name.tableize %>, :activation_token_expires_at, :datetime, :default => nil
3
+ add_column :<%= model_class_name.tableize %>, :activation_state, :string, default: nil
4
+ add_column :<%= model_class_name.tableize %>, :activation_token, :string, default: nil
5
+ add_column :<%= model_class_name.tableize %>, :activation_token_expires_at, :datetime, default: nil
6
6
 
7
7
  add_index :<%= model_class_name.tableize %>, :activation_token
8
8
  end
@@ -30,9 +30,16 @@ module Sorcery
30
30
  end
31
31
  merge_activity_logging_defaults!
32
32
  end
33
- Config.after_login << :register_login_time_to_db
34
- Config.after_login << :register_last_ip_address
35
- Config.before_logout << :register_logout_time_to_db
33
+ # FIXME: There is likely a more elegant way to safeguard these callbacks.
34
+ unless Config.after_login.include?(:register_login_time_to_db)
35
+ Config.after_login << :register_login_time_to_db
36
+ end
37
+ unless Config.after_login.include?(:register_last_ip_address)
38
+ Config.after_login << :register_last_ip_address
39
+ end
40
+ unless Config.before_logout.include?(:register_logout_time_to_db)
41
+ Config.before_logout << :register_logout_time_to_db
42
+ end
36
43
  base.after_action :register_last_activity_time_to_db
37
44
  end
38
45
 
@@ -10,9 +10,13 @@ module Sorcery
10
10
  module BruteForceProtection
11
11
  def self.included(base)
12
12
  base.send(:include, InstanceMethods)
13
-
14
- Config.after_login << :reset_failed_logins_count!
15
- Config.after_failed_login << :update_failed_logins_count!
13
+ # FIXME: There is likely a more elegant way to safeguard these callbacks.
14
+ unless Config.after_login.include?(:reset_failed_logins_count!)
15
+ Config.after_login << :reset_failed_logins_count!
16
+ end
17
+ unless Config.after_failed_login.include?(:update_failed_logins_count!)
18
+ Config.after_failed_login << :update_failed_logins_count!
19
+ end
16
20
  end
17
21
 
18
22
  module InstanceMethods
@@ -25,6 +25,7 @@ module Sorcery
25
25
  require 'sorcery/providers/microsoft'
26
26
  require 'sorcery/providers/instagram'
27
27
  require 'sorcery/providers/auth0'
28
+ require 'sorcery/providers/line'
28
29
 
29
30
  Config.module_eval do
30
31
  class << self
@@ -19,7 +19,10 @@ module Sorcery
19
19
  end
20
20
  merge_http_basic_auth_defaults!
21
21
  end
22
- Config.login_sources << :login_from_basic_auth
22
+ # FIXME: There is likely a more elegant way to safeguard these callbacks.
23
+ unless Config.login_sources.include?(:login_from_basic_auth)
24
+ Config.login_sources << :login_from_basic_auth
25
+ end
23
26
  end
24
27
 
25
28
  module InstanceMethods
@@ -17,8 +17,13 @@ module Sorcery
17
17
  end
18
18
  merge_remember_me_defaults!
19
19
  end
20
- Config.login_sources << :login_from_cookie
21
- Config.before_logout << :forget_me!
20
+ # FIXME: There is likely a more elegant way to safeguard these callbacks.
21
+ unless Config.login_sources.include?(:login_from_cookie)
22
+ Config.login_sources << :login_from_cookie
23
+ end
24
+ unless Config.before_logout.include?(:forget_me!)
25
+ Config.before_logout << :forget_me!
26
+ end
22
27
  end
23
28
 
24
29
  module InstanceMethods
@@ -23,8 +23,13 @@ module Sorcery
23
23
  end
24
24
  merge_session_timeout_defaults!
25
25
  end
26
- Config.after_login << :register_login_time
27
- Config.after_remember_me << :register_login_time
26
+ # FIXME: There is likely a more elegant way to safeguard these callbacks.
27
+ unless Config.after_login.include?(:register_login_time)
28
+ Config.after_login << :register_login_time
29
+ end
30
+ unless Config.after_remember_me.include?(:register_login_time)
31
+ Config.after_remember_me << :register_login_time
32
+ end
28
33
  base.prepend_before_action :validate_session
29
34
  end
30
35
 
@@ -29,7 +29,7 @@ module Sorcery
29
29
 
30
30
  def matches?(crypted, *tokens)
31
31
  decrypt(crypted) == tokens.join
32
- rescue OpenSSL::CipherError
32
+ rescue OpenSSL::Cipher::CipherError
33
33
  false
34
34
  end
35
35
 
@@ -40,6 +40,10 @@ module Sorcery
40
40
  # You are good to go!
41
41
  class BCrypt
42
42
  class << self
43
+ # Setting the option :pepper allows users to append an app-specific secret token.
44
+ # Basically it's equivalent to :salt_join_token option, but have a different name to ensure
45
+ # backward compatibility in generating/matching passwords.
46
+ attr_accessor :pepper
43
47
  # This is the :cost option for the BCrpyt library.
44
48
  # The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
45
49
  # Set this to whatever you want, play around with it to get that perfect balance between
@@ -77,12 +81,13 @@ module Sorcery
77
81
 
78
82
  def reset!
79
83
  @cost = 10
84
+ @pepper = ''
80
85
  end
81
86
 
82
87
  private
83
88
 
84
89
  def join_tokens(tokens)
85
- tokens.flatten.join
90
+ tokens.flatten.join.concat(pepper.to_s) # make sure to add pepper in case tokens have only one element
86
91
  end
87
92
 
88
93
  def new_from_hash(hash)
data/lib/sorcery/model.rb CHANGED
@@ -142,6 +142,7 @@ module Sorcery
142
142
  def set_encryption_attributes
143
143
  @sorcery_config.encryption_provider.stretches = @sorcery_config.stretches if @sorcery_config.encryption_provider.respond_to?(:stretches) && @sorcery_config.stretches
144
144
  @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
145
+ @sorcery_config.encryption_provider.pepper = @sorcery_config.pepper if @sorcery_config.encryption_provider.respond_to?(:pepper) && @sorcery_config.pepper
145
146
  end
146
147
 
147
148
  def add_config_inheritance
@@ -12,7 +12,11 @@ module Sorcery
12
12
  attr_accessor :downcase_username_before_authenticating
13
13
  # change default crypted_password attribute.
14
14
  attr_accessor :crypted_password_attribute_name
15
+ # application-specific secret token that is joined with the password and its salt.
16
+ # Currently available with BCrypt (default crypt provider) only.
17
+ attr_accessor :pepper
15
18
  # what pattern to use to join the password with the salt
19
+ # APPLICABLE TO MD5, SHA1, SHA256, SHA512. Other crypt providers (incl. BCrypt) ignore this parameter.
16
20
  attr_accessor :salt_join_token
17
21
  # change default salt attribute.
18
22
  attr_accessor :salt_attribute_name
@@ -57,6 +61,7 @@ module Sorcery
57
61
  :@encryption_provider => CryptoProviders::BCrypt,
58
62
  :@custom_encryption_provider => nil,
59
63
  :@encryption_key => nil,
64
+ :@pepper => '',
60
65
  :@salt_join_token => '',
61
66
  :@salt_attribute_name => :salt,
62
67
  :@stretches => nil,
@@ -52,10 +52,13 @@ module Sorcery
52
52
  module ClassMethods
53
53
  # Find user by token, also checks for expiration.
54
54
  # Returns the user if token found and is valid.
55
- def load_from_magic_login_token(token)
56
- token_attr_name = @sorcery_config.magic_login_token_attribute_name
57
- token_expiration_date_attr = @sorcery_config.magic_login_token_expires_at_attribute_name
58
- load_from_token(token, token_attr_name, token_expiration_date_attr)
55
+ def load_from_magic_login_token(token, &block)
56
+ load_from_token(
57
+ token,
58
+ @sorcery_config.magic_login_token_attribute_name,
59
+ @sorcery_config.magic_login_token_expires_at_attribute_name,
60
+ &block
61
+ )
59
62
  end
60
63
 
61
64
  protected
@@ -124,10 +124,14 @@ module Sorcery
124
124
  end
125
125
 
126
126
  # Clears token and tries to update the new password for the user.
127
- def change_password!(new_password)
127
+ def change_password(new_password, raise_on_failure: false)
128
128
  clear_reset_password_token
129
129
  send(:"#{sorcery_config.password_attribute_name}=", new_password)
130
- sorcery_adapter.save raise_on_failure: true
130
+ sorcery_adapter.save raise_on_failure: raise_on_failure
131
+ end
132
+
133
+ def change_password!(new_password)
134
+ change_password(new_password, raise_on_failure: true)
131
135
  end
132
136
 
133
137
  protected
@@ -0,0 +1,47 @@
1
+ module Sorcery
2
+ module Providers
3
+ # This class adds support for OAuth with line.com.
4
+ #
5
+ # config.line.key = <key>
6
+ # config.line.secret = <secret>
7
+ # ...
8
+ #
9
+ class Line < Base
10
+ include Protocols::Oauth2
11
+
12
+ attr_accessor :token_url, :user_info_path, :auth_path
13
+
14
+ def initialize
15
+ super
16
+
17
+ @site = 'https://access.line.me'
18
+ @user_info_path = 'https://api.line.me/v2/profile'
19
+ @token_url = 'https://api.line.me/v2/oauth/accessToken'
20
+ @auth_path = 'dialog/oauth/weblogin'
21
+ end
22
+
23
+ def get_user_hash(access_token)
24
+ response = access_token.get(user_info_path)
25
+ auth_hash(access_token).tap do |h|
26
+ h[:user_info] = JSON.parse(response.body)
27
+ h[:uid] = h[:user_info]['userId'].to_s
28
+ end
29
+ end
30
+
31
+ # calculates and returns the url to which the user should be redirected,
32
+ # to get authenticated at the external provider's site.
33
+ def login_url(_params, _session)
34
+ @state = SecureRandom.hex(16)
35
+ authorize_url(authorize_url: auth_path)
36
+ end
37
+ # tries to login the user from access token
38
+ def process_callback(params, _session)
39
+ args = {}.tap do |a|
40
+ a[:code] = params[:code] if params[:code]
41
+ end
42
+
43
+ get_access_token(args, token_url: token_url, token_method: :post)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,65 +1,49 @@
1
1
  module Sorcery
2
2
  module Providers
3
- # This class adds support for OAuth with Linkedin.com.
3
+ # This class adds support for OAuth with LinkedIn.
4
4
  #
5
5
  # config.linkedin.key = <key>
6
6
  # config.linkedin.secret = <secret>
7
7
  # ...
8
8
  #
9
9
  class Linkedin < Base
10
- include Protocols::Oauth
10
+ include Protocols::Oauth2
11
11
 
12
- attr_accessor :authorize_path, :access_permissions, :access_token_path,
13
- :request_token_path, :user_info_fields, :user_info_path
12
+ attr_accessor :auth_url, :scope, :token_url, :user_info_url
14
13
 
15
14
  def initialize
16
- @configuration = {
17
- site: 'https://api.linkedin.com',
18
- authorize_path: '/uas/oauth/authenticate',
19
- request_token_path: '/uas/oauth/requestToken',
20
- access_token_path: '/uas/oauth/accessToken'
21
- }
22
- @user_info_path = '/v1/people/~'
23
- end
24
-
25
- # Override included get_consumer method to provide authorize_path
26
- def get_consumer
27
- # Add access permissions to request token path
28
- @configuration[:request_token_path] += '?scope=' + access_permissions.join('+') unless access_permissions.blank? || @configuration[:request_token_path].include?('?scope=')
29
- ::OAuth::Consumer.new(@key, @secret, @configuration)
15
+ super
16
+
17
+ @site = 'https://api.linkedin.com'
18
+ @auth_url = '/oauth/v2/authorization'
19
+ @token_url = '/oauth/v2/accessToken'
20
+ @user_info_url = 'https://api.linkedin.com/v2/me'
21
+ @scope = 'r_liteprofile'
22
+ @state = SecureRandom.hex(16)
30
23
  end
31
24
 
32
25
  def get_user_hash(access_token)
33
- # Always include id for provider uid and prevent accidental duplication via setting `user_info_field = ['id']` (needed in Sorcery 0.9.1)
34
- info_fields = user_info_fields ? user_info_fields.reject { |n| n == 'id' } : []
35
- fields = info_fields.any? ? 'id,' + info_fields.join(',') : 'id'
36
- response = access_token.get("#{@user_info_path}:(#{fields})", 'x-li-format' => 'json')
26
+ response = access_token.get(user_info_url)
37
27
 
38
28
  auth_hash(access_token).tap do |h|
39
29
  h[:user_info] = JSON.parse(response.body)
40
- h[:uid] = h[:user_info]['id'].to_s
30
+ h[:uid] = h[:user_info]['id']
41
31
  end
42
32
  end
43
33
 
44
34
  # calculates and returns the url to which the user should be redirected,
45
35
  # to get authenticated at the external provider's site.
46
- def login_url(_params, session)
47
- req_token = get_request_token
48
- session[:request_token] = req_token.token
49
- session[:request_token_secret] = req_token.secret
50
- authorize_url(request_token: req_token.token, request_token_secret: req_token.secret)
36
+ def login_url(_params, _session)
37
+ authorize_url(authorize_url: auth_url)
51
38
  end
52
39
 
53
40
  # tries to login the user from access token
54
- def process_callback(params, session)
55
- args = {
56
- oauth_verifier: params[:oauth_verifier],
57
- request_token: session[:request_token],
58
- request_token_secret: session[:request_token_secret]
59
- }
41
+ def process_callback(params, _session)
42
+ args = {}.tap do |a|
43
+ a[:code] = params[:code] if params[:code]
44
+ end
60
45
 
61
- args[:code] = params[:code] if params[:code]
62
- get_access_token(args)
46
+ get_access_token(args, token_url: token_url, token_method: :post)
63
47
  end
64
48
  end
65
49
  end
@@ -1,3 +1,3 @@
1
1
  module Sorcery
2
- VERSION = '0.13.0'.freeze
2
+ VERSION = '0.14.0'.freeze
3
3
  end