sorcery 0.11.0 → 0.16.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/ISSUE_TEMPLATE.md +20 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  5. data/.github/workflows/ruby.yml +49 -0
  6. data/.rubocop.yml +55 -0
  7. data/.rubocop_todo.yml +163 -0
  8. data/CHANGELOG.md +84 -0
  9. data/CODE_OF_CONDUCT.md +14 -0
  10. data/Gemfile +2 -2
  11. data/{LICENSE.txt → LICENSE.md} +1 -1
  12. data/README.md +34 -8
  13. data/Rakefile +3 -1
  14. data/SECURITY.md +19 -0
  15. data/gemfiles/rails_52.gemfile +7 -0
  16. data/gemfiles/rails_60.gemfile +7 -0
  17. data/lib/generators/sorcery/USAGE +1 -1
  18. data/lib/generators/sorcery/helpers.rb +4 -0
  19. data/lib/generators/sorcery/install_generator.rb +21 -21
  20. data/lib/generators/sorcery/templates/initializer.rb +176 -69
  21. data/lib/generators/sorcery/templates/migration/activity_logging.rb +5 -5
  22. data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +4 -4
  23. data/lib/generators/sorcery/templates/migration/core.rb +4 -4
  24. data/lib/generators/sorcery/templates/migration/external.rb +3 -3
  25. data/lib/generators/sorcery/templates/migration/magic_login.rb +9 -0
  26. data/lib/generators/sorcery/templates/migration/remember_me.rb +3 -3
  27. data/lib/generators/sorcery/templates/migration/reset_password.rb +5 -4
  28. data/lib/generators/sorcery/templates/migration/user_activation.rb +4 -4
  29. data/lib/sorcery.rb +2 -0
  30. data/lib/sorcery/adapters/active_record_adapter.rb +4 -3
  31. data/lib/sorcery/adapters/mongoid_adapter.rb +23 -11
  32. data/lib/sorcery/controller.rb +26 -15
  33. data/lib/sorcery/controller/config.rb +7 -5
  34. data/lib/sorcery/controller/submodules/activity_logging.rb +9 -3
  35. data/lib/sorcery/controller/submodules/external.rb +49 -33
  36. data/lib/sorcery/controller/submodules/http_basic_auth.rb +2 -0
  37. data/lib/sorcery/controller/submodules/remember_me.rb +3 -8
  38. data/lib/sorcery/controller/submodules/session_timeout.rb +28 -5
  39. data/lib/sorcery/crypto_providers/aes256.rb +2 -1
  40. data/lib/sorcery/crypto_providers/bcrypt.rb +8 -2
  41. data/lib/sorcery/engine.rb +16 -3
  42. data/lib/sorcery/model.rb +14 -10
  43. data/lib/sorcery/model/config.rb +12 -4
  44. data/lib/sorcery/model/submodules/brute_force_protection.rb +6 -7
  45. data/lib/sorcery/model/submodules/external.rb +19 -3
  46. data/lib/sorcery/model/submodules/magic_login.rb +130 -0
  47. data/lib/sorcery/model/submodules/reset_password.rb +25 -2
  48. data/lib/sorcery/model/submodules/user_activation.rb +1 -1
  49. data/lib/sorcery/model/temporary_token.rb +3 -1
  50. data/lib/sorcery/protocols/oauth.rb +1 -0
  51. data/lib/sorcery/providers/auth0.rb +46 -0
  52. data/lib/sorcery/providers/battlenet.rb +51 -0
  53. data/lib/sorcery/providers/discord.rb +52 -0
  54. data/lib/sorcery/providers/heroku.rb +1 -0
  55. data/lib/sorcery/providers/instagram.rb +73 -0
  56. data/lib/sorcery/providers/line.rb +63 -0
  57. data/lib/sorcery/providers/linkedin.rb +45 -36
  58. data/lib/sorcery/providers/vk.rb +5 -4
  59. data/lib/sorcery/providers/wechat.rb +8 -6
  60. data/lib/sorcery/test_helpers/internal.rb +5 -4
  61. data/lib/sorcery/test_helpers/internal/rails.rb +11 -11
  62. data/lib/sorcery/test_helpers/rails/request.rb +20 -0
  63. data/lib/sorcery/version.rb +1 -1
  64. data/sorcery.gemspec +27 -11
  65. data/spec/active_record/user_activation_spec.rb +2 -2
  66. data/spec/active_record/user_activity_logging_spec.rb +2 -2
  67. data/spec/active_record/user_brute_force_protection_spec.rb +2 -2
  68. data/spec/active_record/user_magic_login_spec.rb +15 -0
  69. data/spec/active_record/user_oauth_spec.rb +2 -2
  70. data/spec/active_record/user_remember_me_spec.rb +2 -2
  71. data/spec/active_record/user_reset_password_spec.rb +2 -2
  72. data/spec/active_record/user_spec.rb +0 -10
  73. data/spec/controllers/controller_http_basic_auth_spec.rb +1 -1
  74. data/spec/controllers/controller_oauth2_spec.rb +230 -123
  75. data/spec/controllers/controller_oauth_spec.rb +13 -7
  76. data/spec/controllers/controller_remember_me_spec.rb +16 -8
  77. data/spec/controllers/controller_session_timeout_spec.rb +90 -3
  78. data/spec/controllers/controller_spec.rb +13 -3
  79. data/spec/orm/active_record.rb +2 -2
  80. data/spec/providers/example_provider_spec.rb +17 -0
  81. data/spec/providers/example_spec.rb +17 -0
  82. data/spec/providers/vk_spec.rb +42 -0
  83. data/spec/rails_app/app/assets/config/manifest.js +1 -0
  84. data/spec/rails_app/app/controllers/application_controller.rb +2 -0
  85. data/spec/rails_app/app/controllers/sorcery_controller.rb +152 -33
  86. data/spec/rails_app/app/mailers/sorcery_mailer.rb +7 -0
  87. data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb +13 -0
  88. data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb +6 -0
  89. data/spec/rails_app/config/application.rb +8 -3
  90. data/spec/rails_app/config/boot.rb +1 -1
  91. data/spec/rails_app/config/environment.rb +1 -1
  92. data/spec/rails_app/config/routes.rb +17 -0
  93. data/spec/rails_app/config/secrets.yml +4 -0
  94. data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +2 -2
  95. data/spec/rails_app/db/migrate/invalidate_active_sessions/20180221093235_add_invalidate_active_sessions_before_to_users.rb +9 -0
  96. data/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb +17 -0
  97. data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +2 -0
  98. data/spec/rails_app/db/schema.rb +7 -9
  99. data/spec/shared_examples/user_magic_login_shared_examples.rb +150 -0
  100. data/spec/shared_examples/user_oauth_shared_examples.rb +1 -1
  101. data/spec/shared_examples/user_remember_me_shared_examples.rb +1 -1
  102. data/spec/shared_examples/user_reset_password_shared_examples.rb +37 -5
  103. data/spec/shared_examples/user_shared_examples.rb +104 -43
  104. data/spec/sorcery_crypto_providers_spec.rb +61 -1
  105. data/spec/sorcery_temporary_token_spec.rb +27 -0
  106. data/spec/spec.opts +1 -1
  107. data/spec/spec_helper.rb +2 -2
  108. data/spec/support/migration_helper.rb +29 -0
  109. data/spec/support/providers/example.rb +11 -0
  110. data/spec/support/providers/example_provider.rb +11 -0
  111. metadata +97 -34
  112. data/.travis.yml +0 -57
  113. data/gemfiles/active_record-rails40.gemfile +0 -7
  114. data/gemfiles/active_record-rails41.gemfile +0 -7
  115. data/gemfiles/active_record-rails42.gemfile +0 -7
  116. data/spec/rails_app/config/initializers/secret_token.rb +0 -7
@@ -16,6 +16,8 @@ module Sorcery
16
16
  attr_accessor :reset_password_token_attribute_name
17
17
  # Expires at attribute name.
18
18
  attr_accessor :reset_password_token_expires_at_attribute_name
19
+ # Counter access to reset password page
20
+ attr_accessor :reset_password_page_access_count_attribute_name
19
21
  # When was email sent, used for hammering protection.
20
22
  attr_accessor :reset_password_email_sent_at_attribute_name
21
23
  # Mailer class (needed)
@@ -34,6 +36,8 @@ module Sorcery
34
36
  base.sorcery_config.instance_eval do
35
37
  @defaults.merge!(:@reset_password_token_attribute_name => :reset_password_token,
36
38
  :@reset_password_token_expires_at_attribute_name => :reset_password_token_expires_at,
39
+ :@reset_password_page_access_count_attribute_name =>
40
+ :access_count_to_reset_password_page,
37
41
  :@reset_password_email_sent_at_attribute_name => :reset_password_email_sent_at,
38
42
  :@reset_password_mailer => nil,
39
43
  :@reset_password_mailer_disabled => false,
@@ -97,6 +101,7 @@ module Sorcery
97
101
  config = sorcery_config
98
102
  # hammering protection
99
103
  return false if config.reset_password_time_between_emails.present? && send(config.reset_password_email_sent_at_attribute_name) && send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.seconds.ago.utc
104
+
100
105
  self.class.sorcery_adapter.transaction do
101
106
  generate_reset_password_token!
102
107
  mail = send_reset_password_email! unless config.reset_password_mailer_disabled
@@ -104,11 +109,29 @@ module Sorcery
104
109
  mail
105
110
  end
106
111
 
112
+ # Increment access_count_to_reset_password_page attribute.
113
+ # For example, access_count_to_reset_password_page attribute is over 1, which
114
+ # means the user doesn't have a right to access.
115
+ def increment_password_reset_page_access_counter
116
+ sorcery_adapter.increment(sorcery_config.reset_password_page_access_count_attribute_name)
117
+ end
118
+
119
+ # Reset access_count_to_reset_password_page attribute into 0.
120
+ # This is expected to be used after sending an instruction email.
121
+ def reset_password_reset_page_access_counter
122
+ send(:"#{sorcery_config.reset_password_page_access_count_attribute_name}=", 0)
123
+ sorcery_adapter.save
124
+ end
125
+
107
126
  # Clears token and tries to update the new password for the user.
108
- def change_password!(new_password)
127
+ def change_password(new_password, raise_on_failure: false)
109
128
  clear_reset_password_token
110
129
  send(:"#{sorcery_config.password_attribute_name}=", new_password)
111
- sorcery_adapter.save
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)
112
135
  end
113
136
 
114
137
  protected
@@ -45,7 +45,7 @@ module Sorcery
45
45
  # don't setup activation if no password supplied - this user is created automatically
46
46
  sorcery_adapter.define_callback :before, :create, :setup_activation, if: proc { |user| user.send(sorcery_config.password_attribute_name).present? }
47
47
  # don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
48
- sorcery_adapter.define_callback :after, :create, :send_activation_needed_email!, if: :send_activation_needed_email?
48
+ sorcery_adapter.define_callback :after, :commit, :send_activation_needed_email!, on: :create, if: :send_activation_needed_email?
49
49
  end
50
50
 
51
51
  base.sorcery_config.after_config << :validate_mailer_defined
@@ -7,12 +7,14 @@ module Sorcery
7
7
  # such as reseting password and activating the user by email.
8
8
  module TemporaryToken
9
9
  def self.included(base)
10
+ # FIXME: This may not be the ideal way of passing sorcery_config to generate_random_token.
11
+ @sorcery_config = base.sorcery_config
10
12
  base.extend(ClassMethods)
11
13
  end
12
14
 
13
15
  # Random code, used for salt and temporary tokens.
14
16
  def self.generate_random_token
15
- SecureRandom.urlsafe_base64(15).tr('lIO0', 'sxyz')
17
+ SecureRandom.urlsafe_base64(@sorcery_config.token_randomness).tr('lIO0', 'sxyz')
16
18
  end
17
19
 
18
20
  module ClassMethods
@@ -9,6 +9,7 @@ module Sorcery
9
9
 
10
10
  def get_request_token(token = nil, secret = nil)
11
11
  return ::OAuth::RequestToken.new(get_consumer, token, secret) if token && secret
12
+
12
13
  get_consumer.get_request_token(oauth_callback: @callback_url)
13
14
  end
14
15
 
@@ -0,0 +1,46 @@
1
+ module Sorcery
2
+ module Providers
3
+ # This class adds support for OAuth with Auth0.com
4
+ #
5
+ # config.auth0.key = <key>
6
+ # config.auth0.secret = <secret>
7
+ # config.auth0.domain = <domain>
8
+ # ...
9
+ #
10
+ class Auth0 < Base
11
+ include Protocols::Oauth2
12
+
13
+ attr_accessor :auth_path, :token_path, :user_info_path, :scope
14
+
15
+ def initialize
16
+ super
17
+
18
+ @auth_path = '/authorize'
19
+ @token_path = '/oauth/token'
20
+ @user_info_path = '/userinfo'
21
+ @scope = 'openid profile email'
22
+ end
23
+
24
+ def get_user_hash(access_token)
25
+ response = access_token.get(user_info_path)
26
+
27
+ auth_hash(access_token).tap do |h|
28
+ h[:user_info] = JSON.parse(response.body)
29
+ h[:uid] = h[:user_info]['sub']
30
+ end
31
+ end
32
+
33
+ def login_url(_params, _session)
34
+ authorize_url(authorize_url: auth_path)
35
+ end
36
+
37
+ def process_callback(params, _session)
38
+ args = {}.tap do |a|
39
+ a[:code] = params[:code] if params[:code]
40
+ end
41
+
42
+ get_access_token(args, token_url: token_path, token_method: :post)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ module Sorcery
2
+ module Providers
3
+ # This class adds support for OAuth with BattleNet
4
+
5
+ class Battlenet < Base
6
+ include Protocols::Oauth2
7
+
8
+ attr_accessor :auth_path, :scope, :token_url, :user_info_path
9
+
10
+ def initialize
11
+ super
12
+
13
+ @scope = 'openid'
14
+ @site = 'https://eu.battle.net/'
15
+ @auth_path = '/oauth/authorize'
16
+ @token_url = '/oauth/token'
17
+ @user_info_path = '/oauth/userinfo'
18
+ @state = SecureRandom.hex(16)
19
+ end
20
+
21
+ def get_user_hash(access_token)
22
+ response = access_token.get(user_info_path)
23
+ body = JSON.parse(response.body)
24
+ auth_hash(access_token).tap do |h|
25
+ h[:user_info] = body
26
+ h[:battletag] = body['battletag']
27
+ h[:uid] = body['id']
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
+ authorize_url(authorize_url: auth_path)
35
+ end
36
+
37
+ # tries to login the user from access token
38
+ def process_callback(params, _session)
39
+ args = { code: params[:code] }
40
+ get_access_token(
41
+ args,
42
+ token_url: token_url,
43
+ client_id: @key,
44
+ client_secret: @secret,
45
+ grant_type: 'authorization_code',
46
+ token_method: :post
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module Sorcery
2
+ module Providers
3
+ # This class adds support for OAuth with discordapp.com
4
+
5
+ class Discord < Base
6
+ include Protocols::Oauth2
7
+
8
+ attr_accessor :auth_path, :scope, :token_url, :user_info_path
9
+
10
+ def initialize
11
+ super
12
+
13
+ @scope = 'identify'
14
+ @site = 'https://discordapp.com/'
15
+ @auth_path = '/api/oauth2/authorize'
16
+ @token_url = '/api/oauth2/token'
17
+ @user_info_path = '/api/users/@me'
18
+ @state = SecureRandom.hex(16)
19
+ end
20
+
21
+ def get_user_hash(access_token)
22
+ response = access_token.get(user_info_path)
23
+ body = JSON.parse(response.body)
24
+ auth_hash(access_token).tap do |h|
25
+ h[:user_info] = body
26
+ h[:uid] = body['id']
27
+ end
28
+ end
29
+
30
+ # calculates and returns the url to which the user should be redirected,
31
+ # to get authenticated at the external provider's site.
32
+ def login_url(_params, _session)
33
+ authorize_url(authorize_url: auth_path)
34
+ end
35
+
36
+ # tries to login the user from access token
37
+ def process_callback(params, _session)
38
+ args = {}.tap do |a|
39
+ a[:code] = params[:code] if params[:code]
40
+ end
41
+ get_access_token(
42
+ args,
43
+ token_url: token_url,
44
+ client_id: @key,
45
+ client_secret: @secret,
46
+ grant_type: 'authorization_code',
47
+ token_method: :post
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -45,6 +45,7 @@ module Sorcery
45
45
  # tries to login the user from access token
46
46
  def process_callback(params, _session)
47
47
  raise 'Invalid state. Potential Cross Site Forgery' if params[:state] != state
48
+
48
49
  args = {}.tap do |a|
49
50
  a[:code] = params[:code] if params[:code]
50
51
  end
@@ -0,0 +1,73 @@
1
+ module Sorcery
2
+ module Providers
3
+ # This class adds support for OAuth with Instagram.com.
4
+ class Instagram < Base
5
+ include Protocols::Oauth2
6
+
7
+ attr_accessor :access_permissions, :token_url,
8
+ :authorization_path, :user_info_path,
9
+ :scope, :user_info_fields
10
+
11
+ def initialize
12
+ super
13
+
14
+ @site = 'https://api.instagram.com'
15
+ @token_url = '/oauth/access_token'
16
+ @authorization_path = '/oauth/authorize/'
17
+ @user_info_path = '/v1/users/self'
18
+ @scope = 'basic'
19
+ end
20
+
21
+ def self.included(base)
22
+ base.extend Sorcery::Providers
23
+ end
24
+
25
+ # provider implements method to build Oauth client
26
+ def login_url(_params, _session)
27
+ authorize_url(token_url: @token_url)
28
+ end
29
+
30
+ # overrides oauth2#authorize_url to allow customized scope.
31
+ def authorize_url(opts = {})
32
+ @scope = access_permissions.present? ? access_permissions.join(' ') : scope
33
+ super(opts.merge(token_url: @token_url))
34
+ end
35
+
36
+ # pass oauth2 param `code` provided by instgrm server
37
+ def process_callback(params, _session)
38
+ args = {}.tap do |a|
39
+ a[:code] = params[:code] if params[:code]
40
+ end
41
+ get_access_token(
42
+ args,
43
+ token_url: @token_url,
44
+ client_id: @key,
45
+ client_secret: @secret
46
+ )
47
+ end
48
+
49
+ # see `user_info_mapping` in config/initializer,
50
+ # given `user_info_mapping` to specify
51
+ # {:db_attribute_name => 'instagram_attr_name'}
52
+ # so that Sorcery can build AR model from attr names
53
+ #
54
+ # NOTE: instead of just getting the user info
55
+ # from the access_token (which already returns them),
56
+ # testing strategy relies on querying user_info_path
57
+ def get_user_hash(access_token)
58
+ call_api_params = {
59
+ access_token: access_token.token,
60
+ client_id: access_token[:client_id]
61
+ }
62
+ response = access_token.get(
63
+ "#{user_info_path}?#{call_api_params.to_param}"
64
+ )
65
+
66
+ user_attrs = {}
67
+ user_attrs[:user_info] = JSON.parse(response.body)['data']
68
+ user_attrs[:uid] = user_attrs[:user_info]['id']
69
+ user_attrs
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,63 @@
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, :scope, :bot_prompt
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/oauth2/v2.1/token'
20
+ @auth_path = 'oauth2/v2.1/authorize'
21
+ @scope = 'profile'
22
+ end
23
+
24
+ def get_user_hash(access_token)
25
+ response = access_token.get(user_info_path)
26
+ auth_hash(access_token).tap do |h|
27
+ h[:user_info] = JSON.parse(response.body)
28
+ h[:uid] = h[:user_info]['userId'].to_s
29
+ end
30
+ end
31
+
32
+ # calculates and returns the url to which the user should be redirected,
33
+ # to get authenticated at the external provider's site.
34
+ def login_url(_params, _session)
35
+ @state = SecureRandom.hex(16)
36
+ authorize_url(authorize_url: auth_path)
37
+ end
38
+
39
+ # overrides oauth2#authorize_url to add bot_prompt query.
40
+ def authorize_url(options = {})
41
+ options.merge!({
42
+ connection_opts: { params: { bot_prompt: bot_prompt } }
43
+ }) if bot_prompt.present?
44
+
45
+ super(options)
46
+ end
47
+
48
+ # tries to login the user from access token
49
+ def process_callback(params, _session)
50
+ args = {}.tap do |a|
51
+ a[:code] = params[:code] if params[:code]
52
+ end
53
+
54
+ get_access_token(
55
+ args,
56
+ token_url: token_url,
57
+ token_method: :post,
58
+ grant_type: 'authorization_code'
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,65 +1,74 @@
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, :email_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
15
+ super
24
16
 
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)
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
+ @email_info_url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))'
22
+ @scope = 'r_liteprofile r_emailaddress'
23
+ @state = SecureRandom.hex(16)
30
24
  end
31
25
 
32
26
  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')
27
+ user_info = get_user_info(access_token)
37
28
 
38
29
  auth_hash(access_token).tap do |h|
39
- h[:user_info] = JSON.parse(response.body)
40
- h[:uid] = h[:user_info]['id'].to_s
30
+ h[:user_info] = user_info
31
+ h[:uid] = h[:user_info]['id']
41
32
  end
42
33
  end
43
34
 
44
35
  # calculates and returns the url to which the user should be redirected,
45
36
  # 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)
37
+ def login_url(_params, _session)
38
+ authorize_url(authorize_url: auth_url)
51
39
  end
52
40
 
53
41
  # 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
- }
42
+ def process_callback(params, _session)
43
+ args = {}.tap do |a|
44
+ a[:code] = params[:code] if params[:code]
45
+ end
46
+
47
+ get_access_token(args, token_url: token_url, token_method: :post)
48
+ end
49
+
50
+ def get_user_info(access_token)
51
+ response = access_token.get(user_info_url)
52
+ user_info = JSON.parse(response.body)
53
+
54
+ if email_in_scope?
55
+ email = fetch_email(access_token)
56
+
57
+ return user_info.merge(email)
58
+ end
59
+
60
+ user_info
61
+ end
62
+
63
+ def email_in_scope?
64
+ scope.include?('r_emailaddress')
65
+ end
66
+
67
+ def fetch_email(access_token)
68
+ email_response = access_token.get(email_info_url)
69
+ email_info = JSON.parse(email_response.body)['elements'].first
60
70
 
61
- args[:code] = params[:code] if params[:code]
62
- get_access_token(args)
71
+ email_info['handle~']
63
72
  end
64
73
  end
65
74
  end