sorcery 0.11.0 → 0.15.1
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.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +145 -0
- data/.travis.yml +3 -52
- data/CHANGELOG.md +69 -0
- data/Gemfile +3 -3
- data/{LICENSE.txt → LICENSE.md} +1 -1
- data/README.md +34 -7
- data/lib/generators/sorcery/USAGE +1 -1
- data/lib/generators/sorcery/install_generator.rb +21 -21
- data/lib/generators/sorcery/templates/initializer.rb +164 -69
- data/lib/generators/sorcery/templates/migration/activity_logging.rb +4 -4
- data/lib/generators/sorcery/templates/migration/brute_force_protection.rb +3 -3
- data/lib/generators/sorcery/templates/migration/core.rb +2 -2
- data/lib/generators/sorcery/templates/migration/external.rb +3 -3
- data/lib/generators/sorcery/templates/migration/magic_login.rb +9 -0
- data/lib/generators/sorcery/templates/migration/remember_me.rb +2 -2
- data/lib/generators/sorcery/templates/migration/reset_password.rb +4 -3
- data/lib/generators/sorcery/templates/migration/user_activation.rb +3 -3
- data/lib/sorcery.rb +2 -0
- data/lib/sorcery/adapters/active_record_adapter.rb +3 -2
- data/lib/sorcery/adapters/mongoid_adapter.rb +23 -11
- data/lib/sorcery/controller.rb +26 -15
- data/lib/sorcery/controller/config.rb +2 -0
- data/lib/sorcery/controller/submodules/activity_logging.rb +14 -3
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +7 -3
- data/lib/sorcery/controller/submodules/external.rb +48 -33
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +5 -1
- data/lib/sorcery/controller/submodules/remember_me.rb +9 -10
- data/lib/sorcery/controller/submodules/session_timeout.rb +32 -6
- data/lib/sorcery/crypto_providers/aes256.rb +2 -1
- data/lib/sorcery/crypto_providers/bcrypt.rb +8 -2
- data/lib/sorcery/engine.rb +16 -3
- data/lib/sorcery/model.rb +14 -10
- data/lib/sorcery/model/config.rb +12 -4
- data/lib/sorcery/model/submodules/brute_force_protection.rb +6 -7
- data/lib/sorcery/model/submodules/external.rb +19 -3
- data/lib/sorcery/model/submodules/magic_login.rb +130 -0
- data/lib/sorcery/model/submodules/reset_password.rb +25 -2
- data/lib/sorcery/model/submodules/user_activation.rb +1 -1
- data/lib/sorcery/model/temporary_token.rb +3 -1
- data/lib/sorcery/protocols/oauth.rb +1 -0
- data/lib/sorcery/providers/auth0.rb +46 -0
- data/lib/sorcery/providers/discord.rb +52 -0
- data/lib/sorcery/providers/heroku.rb +1 -0
- data/lib/sorcery/providers/instagram.rb +73 -0
- data/lib/sorcery/providers/line.rb +47 -0
- data/lib/sorcery/providers/linkedin.rb +45 -36
- data/lib/sorcery/providers/vk.rb +5 -4
- data/lib/sorcery/providers/wechat.rb +8 -6
- data/lib/sorcery/test_helpers/internal.rb +5 -4
- data/lib/sorcery/test_helpers/internal/rails.rb +11 -11
- data/lib/sorcery/test_helpers/rails/request.rb +20 -0
- data/lib/sorcery/version.rb +1 -1
- data/sorcery.gemspec +28 -11
- data/spec/active_record/user_activation_spec.rb +2 -2
- data/spec/active_record/user_activity_logging_spec.rb +2 -2
- data/spec/active_record/user_brute_force_protection_spec.rb +2 -2
- data/spec/active_record/user_magic_login_spec.rb +15 -0
- data/spec/active_record/user_oauth_spec.rb +2 -2
- data/spec/active_record/user_remember_me_spec.rb +2 -2
- data/spec/active_record/user_reset_password_spec.rb +2 -2
- data/spec/active_record/user_spec.rb +0 -10
- data/spec/controllers/controller_http_basic_auth_spec.rb +1 -1
- data/spec/controllers/controller_oauth2_spec.rb +212 -123
- data/spec/controllers/controller_oauth_spec.rb +7 -7
- data/spec/controllers/controller_remember_me_spec.rb +16 -8
- data/spec/controllers/controller_session_timeout_spec.rb +90 -3
- data/spec/controllers/controller_spec.rb +13 -3
- data/spec/orm/active_record.rb +2 -2
- data/spec/providers/example_provider_spec.rb +17 -0
- data/spec/providers/example_spec.rb +17 -0
- data/spec/providers/vk_spec.rb +42 -0
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +131 -32
- data/spec/rails_app/app/mailers/sorcery_mailer.rb +7 -0
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb +13 -0
- data/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb +6 -0
- data/spec/rails_app/config/application.rb +8 -3
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environment.rb +1 -1
- data/spec/rails_app/config/routes.rb +14 -0
- data/spec/rails_app/config/secrets.yml +4 -0
- data/spec/rails_app/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +2 -2
- data/spec/rails_app/db/migrate/invalidate_active_sessions/20180221093235_add_invalidate_active_sessions_before_to_users.rb +9 -0
- data/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb +17 -0
- data/spec/rails_app/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +2 -0
- data/spec/rails_app/db/schema.rb +7 -9
- data/spec/shared_examples/user_magic_login_shared_examples.rb +150 -0
- data/spec/shared_examples/user_oauth_shared_examples.rb +1 -1
- data/spec/shared_examples/user_remember_me_shared_examples.rb +1 -1
- data/spec/shared_examples/user_reset_password_shared_examples.rb +37 -5
- data/spec/shared_examples/user_shared_examples.rb +104 -43
- data/spec/sorcery_crypto_providers_spec.rb +61 -1
- data/spec/sorcery_temporary_token_spec.rb +27 -0
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +2 -2
- data/spec/support/migration_helper.rb +19 -0
- data/spec/support/providers/example.rb +11 -0
- data/spec/support/providers/example_provider.rb +11 -0
- metadata +89 -33
- data/gemfiles/active_record-rails40.gemfile +0 -7
- data/gemfiles/active_record-rails41.gemfile +0 -7
- data/gemfiles/active_record-rails42.gemfile +0 -7
- 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
|
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, :
|
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(
|
17
|
+
SecureRandom.urlsafe_base64(@sorcery_config.token_randomness).tr('lIO0', 'sxyz')
|
16
18
|
end
|
17
19
|
|
18
20
|
module ClassMethods
|
@@ -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,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,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,74 @@
|
|
1
1
|
module Sorcery
|
2
2
|
module Providers
|
3
|
-
# This class adds support for OAuth with
|
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::
|
10
|
+
include Protocols::Oauth2
|
11
11
|
|
12
|
-
attr_accessor :
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@
|
29
|
-
|
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
|
-
|
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] =
|
40
|
-
h[:uid]
|
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,
|
47
|
-
|
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,
|
55
|
-
args = {
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
62
|
-
get_access_token(args)
|
71
|
+
email_info['handle~']
|
63
72
|
end
|
64
73
|
end
|
65
74
|
end
|
data/lib/sorcery/providers/vk.rb
CHANGED
@@ -9,7 +9,7 @@ module Sorcery
|
|
9
9
|
class Vk < Base
|
10
10
|
include Protocols::Oauth2
|
11
11
|
|
12
|
-
attr_accessor :auth_path, :token_path, :user_info_url, :scope
|
12
|
+
attr_accessor :auth_path, :token_path, :user_info_url, :scope, :api_version
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
super
|
@@ -28,15 +28,16 @@ module Sorcery
|
|
28
28
|
access_token: access_token.token,
|
29
29
|
uids: access_token.params['user_id'],
|
30
30
|
fields: user_info_mapping.values.join(','),
|
31
|
-
scope: scope
|
31
|
+
scope: scope,
|
32
|
+
v: api_version.to_s
|
32
33
|
}
|
33
34
|
|
34
35
|
response = access_token.get(user_info_url, params: params)
|
35
|
-
if user_hash[:user_info] = JSON.parse(response.body)
|
36
|
+
if (user_hash[:user_info] = JSON.parse(response.body))
|
36
37
|
user_hash[:user_info] = user_hash[:user_info]['response'][0]
|
37
38
|
user_hash[:user_info]['full_name'] = [user_hash[:user_info]['first_name'], user_hash[:user_info]['last_name']].join(' ')
|
38
39
|
|
39
|
-
user_hash[:uid] = user_hash[:user_info]['
|
40
|
+
user_hash[:uid] = user_hash[:user_info]['id']
|
40
41
|
user_hash[:user_info]['email'] = access_token.params['email']
|
41
42
|
end
|
42
43
|
user_hash
|