sorcery 0.10.1 → 0.11.0
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 +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +20 -32
- data/lib/generators/sorcery/templates/initializer.rb +13 -1
- data/lib/sorcery/adapters/mongoid_adapter.rb +97 -0
- data/lib/sorcery/controller/submodules/external.rb +1 -0
- data/lib/sorcery/controller.rb +12 -6
- data/lib/sorcery/model/submodules/brute_force_protection.rb +13 -5
- data/lib/sorcery/model/submodules/reset_password.rb +7 -4
- data/lib/sorcery/model/submodules/user_activation.rb +15 -5
- data/lib/sorcery/model/temporary_token.rb +26 -5
- data/lib/sorcery/model.rb +29 -5
- data/lib/sorcery/providers/facebook.rb +3 -3
- data/lib/sorcery/providers/microsoft.rb +59 -0
- data/lib/sorcery/test_helpers/internal.rb +1 -1
- data/lib/sorcery/version.rb +1 -1
- data/lib/sorcery.rb +14 -1
- data/sorcery.gemspec +3 -2
- data/spec/controllers/controller_brute_force_protection_spec.rb +2 -2
- data/spec/controllers/controller_oauth2_spec.rb +12 -5
- data/spec/controllers/controller_remember_me_spec.rb +2 -2
- data/spec/controllers/controller_session_timeout_spec.rb +2 -2
- data/spec/controllers/controller_spec.rb +1 -1
- data/spec/rails_app/app/controllers/sorcery_controller.rb +20 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/shared_examples/user_activation_shared_examples.rb +78 -0
- data/spec/shared_examples/user_reset_password_shared_examples.rb +65 -0
- data/spec/shared_examples/user_shared_examples.rb +25 -0
- data/spec/spec_helper.rb +1 -0
- metadata +21 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc71bccb13f8e3fcbbf0b94f660a98bc34cc4826
|
|
4
|
+
data.tar.gz: 9c60fb5db29c7a41378e7c45234b0b0e9df47501
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c557af8ef3828be476750ba465ea4d3c7a34f5ccbf975c5c731d0841891de4f77d56fa867dfd55be9424a2eea0eed768cdfe1a104e6ed70ae643207bb2573eb1
|
|
7
|
+
data.tar.gz: 582ea847785099a70c4866feee1b1f891a411e4f572ff89497ac353914cffe78669102d8a7a6b76c208976894ca932f83e61e590645306e7b5a9c3ebc6868bc2
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## HEAD
|
|
3
|
+
|
|
4
|
+
## 0.11.0
|
|
5
|
+
|
|
6
|
+
* Refer to User before calling remove_const to avoid NameError [#58](https://github.com/Sorcery/sorcery/pull/58)
|
|
7
|
+
* Resurrect block authentication, showing auth failure reason. [#41](https://github.com/Sorcery/sorcery/pull/41)
|
|
8
|
+
* Add github scope option to initializer.rb [#50](https://github.com/Sorcery/sorcery/pull/50)
|
|
9
|
+
|
|
10
|
+
## 0.10.3
|
|
11
|
+
|
|
12
|
+
* Revert removal of MongoID Adapter (breaks Sorcery for MongoID users until separate gem is created) [#45](https://github.com/Sorcery/sorcery/pull/45)
|
|
13
|
+
|
|
14
|
+
## 0.10.2
|
|
15
|
+
|
|
16
|
+
* Added support for Microsoft OAuth (thanks to @athix) [#37](https://github.com/Sorcery/sorcery/pull/37)
|
|
17
|
+
|
|
2
18
|
## 0.10.1
|
|
19
|
+
|
|
3
20
|
* Fixed LinkedIn bug [#36](https://github.com/Sorcery/sorcery/pull/36)
|
|
4
21
|
|
|
5
22
|
## 0.10.0
|
|
@@ -77,7 +94,7 @@
|
|
|
77
94
|
|
|
78
95
|
## 0.8.4
|
|
79
96
|
|
|
80
|
-
|
|
97
|
+
* Few security fixes in `external` module
|
|
81
98
|
|
|
82
99
|
## 0.8.3 (yanked because of bad Jeweler release)
|
|
83
100
|
|
|
@@ -85,38 +102,9 @@
|
|
|
85
102
|
|
|
86
103
|
* Activity logging feature has a new column called `last_login_from_ip_address` (string type). If you use ActiveRecord, you will have to add this column to DB ([#465](https://github.com/NoamB/sorcery/issues/465))
|
|
87
104
|
|
|
88
|
-
## 0.8.1
|
|
89
|
-
<!-- TO BE WRITTEN -->
|
|
90
|
-
|
|
91
|
-
## 0.8.0
|
|
92
|
-
<!-- TO BE WRITTEN -->
|
|
93
|
-
|
|
94
|
-
## 0.7.13
|
|
95
|
-
<!-- TO BE WRITTEN -->
|
|
96
|
-
|
|
97
|
-
## 0.7.12
|
|
98
|
-
<!-- TO BE WRITTEN -->
|
|
99
|
-
|
|
100
|
-
## 0.7.11
|
|
101
|
-
<!-- TO BE WRITTEN -->
|
|
102
|
-
|
|
103
|
-
## 0.7.10
|
|
104
|
-
<!-- TO BE WRITTEN -->
|
|
105
|
-
|
|
106
|
-
## 0.7.9
|
|
107
|
-
<!-- TO BE WRITTEN -->
|
|
108
|
-
|
|
109
|
-
## 0.7.8
|
|
110
|
-
<!-- TO BE WRITTEN -->
|
|
111
|
-
|
|
112
|
-
## 0.7.7
|
|
113
|
-
<!-- TO BE WRITTEN -->
|
|
114
|
-
|
|
115
|
-
## 0.7.6
|
|
116
|
-
<!-- TO BE WRITTEN -->
|
|
105
|
+
## 0.7.5-0.8.1
|
|
117
106
|
|
|
118
|
-
|
|
119
|
-
<!-- TO BE WRITTEN -->
|
|
107
|
+
<!-- HERE BE DRAGONS (Changelogs never written) -->
|
|
120
108
|
|
|
121
109
|
## 0.7.1-0.7.4
|
|
122
110
|
|
|
@@ -109,12 +109,14 @@ Rails.application.config.sorcery.configure do |config|
|
|
|
109
109
|
# config.facebook.user_info_mapping = {:email => "name"}
|
|
110
110
|
# config.facebook.access_permissions = ["email", "publish_actions"]
|
|
111
111
|
# config.facebook.display = "page"
|
|
112
|
-
# config.facebook.api_version = "v2.
|
|
112
|
+
# config.facebook.api_version = "v2.3"
|
|
113
|
+
# config.facebook.parse = :json
|
|
113
114
|
#
|
|
114
115
|
# config.github.key = ""
|
|
115
116
|
# config.github.secret = ""
|
|
116
117
|
# config.github.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=github"
|
|
117
118
|
# config.github.user_info_mapping = {:email => "name"}
|
|
119
|
+
# config.github.scope = ""
|
|
118
120
|
#
|
|
119
121
|
# config.paypal.key = ""
|
|
120
122
|
# config.paypal.secret = ""
|
|
@@ -131,6 +133,16 @@ Rails.application.config.sorcery.configure do |config|
|
|
|
131
133
|
# config.google.user_info_mapping = {:email => "email", :username => "name"}
|
|
132
134
|
# config.google.scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
|
|
133
135
|
#
|
|
136
|
+
# For Microsoft Graph, the key will be your App ID, and the secret will be your app password/public key.
|
|
137
|
+
# The callback URL "can't contain a query string or invalid special characters", see: https://docs.microsoft.com/en-us/azure/active-directory/active-directory-v2-limitations#restrictions-on-redirect-uris
|
|
138
|
+
# More information at https://graph.microsoft.io/en-us/docs
|
|
139
|
+
#
|
|
140
|
+
# config.microsoft.key = ""
|
|
141
|
+
# config.microsoft.secret = ""
|
|
142
|
+
# config.microsoft.callback_url = "http://0.0.0.0:3000/oauth/callback/microsoft"
|
|
143
|
+
# config.microsoft.user_info_mapping = {:email => "userPrincipalName", :username => "displayName"}
|
|
144
|
+
# config.microsoft.scope = "openid email https://graph.microsoft.com/User.Read"
|
|
145
|
+
#
|
|
134
146
|
# config.vk.key = ""
|
|
135
147
|
# config.vk.secret = ""
|
|
136
148
|
# config.vk.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=vk"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Adapters
|
|
3
|
+
class MongoidAdapter < BaseAdapter
|
|
4
|
+
def increment(attr)
|
|
5
|
+
mongoid_4? ? @model.inc(attr => 1) : @model.inc(attr, 1)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def update_attributes(attrs)
|
|
9
|
+
attrs.each do |name, value|
|
|
10
|
+
attrs[name] = value.utc if value.is_a?(ActiveSupport::TimeWithZone)
|
|
11
|
+
@model.send(:"#{name}=", value)
|
|
12
|
+
end
|
|
13
|
+
@model.class.where(:_id => @model.id).update_all(attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def update_attribute(name, value)
|
|
17
|
+
update_attributes(name => value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save(options = {})
|
|
21
|
+
mthd = options.delete(:raise_on_failure) ? :save! : :save
|
|
22
|
+
@model.send(mthd, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def mongoid_4?
|
|
26
|
+
Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new("4.0.0.alpha")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
|
|
31
|
+
def define_field(name, type, options={})
|
|
32
|
+
@klass.field name, options.slice(:default).merge(type: type)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def define_callback(time, event, method_name, options={})
|
|
36
|
+
@klass.send "#{time}_#{event}", method_name, options.slice(:if)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def credential_regex(credential)
|
|
40
|
+
return { :$regex => /^#{Regexp.escape(credential)}$/i } if (@klass.sorcery_config.downcase_username_before_authenticating)
|
|
41
|
+
credential
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_by_credentials(credentials)
|
|
45
|
+
@klass.sorcery_config.username_attribute_names.each do |attribute|
|
|
46
|
+
@user = @klass.where(attribute => credential_regex(credentials[0])).first
|
|
47
|
+
break if @user
|
|
48
|
+
end
|
|
49
|
+
@user
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_by_oauth_credentials(provider, uid)
|
|
53
|
+
@user_config ||= ::Sorcery::Controller::Config.user_class.to_s.constantize.sorcery_config
|
|
54
|
+
@klass.where(@user_config.provider_attribute_name => provider, @user_config.provider_uid_attribute_name => uid).first
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_by_activation_token(token)
|
|
58
|
+
@klass.where(@klass.sorcery_config.activation_token_attribute_name => token).first
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def find_by_remember_me_token(token)
|
|
62
|
+
@klass.where(@klass.sorcery_config.remember_me_token_attribute_name => token).first
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def transaction(&blk)
|
|
66
|
+
tap(&blk)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def find_by_id(id)
|
|
70
|
+
@klass.find(id)
|
|
71
|
+
rescue ::Mongoid::Errors::DocumentNotFound
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_by_username(username)
|
|
76
|
+
query = @klass.sorcery_config.username_attribute_names.map {|name| {name => username}}
|
|
77
|
+
@klass.any_of(*query).first
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def find_by_token(token_attr_name, token)
|
|
81
|
+
@klass.where(token_attr_name => token).first
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def find_by_email(email)
|
|
85
|
+
@klass.where(@klass.sorcery_config.email_attribute_name => email).first
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def get_current_users
|
|
89
|
+
config = @klass.sorcery_config
|
|
90
|
+
@klass.where(config.last_activity_at_attribute_name.ne => nil) \
|
|
91
|
+
.where("this.#{config.last_logout_at_attribute_name} == null || this.#{config.last_activity_at_attribute_name} > this.#{config.last_logout_at_attribute_name}") \
|
|
92
|
+
.where(config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc).order_by([:_id,:asc])
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/sorcery/controller.rb
CHANGED
|
@@ -30,8 +30,16 @@ module Sorcery
|
|
|
30
30
|
# Runs hooks after login or failed login.
|
|
31
31
|
def login(*credentials)
|
|
32
32
|
@current_user = nil
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
user_class.authenticate(*credentials) do |user, failure_reason|
|
|
35
|
+
if failure_reason
|
|
36
|
+
after_failed_login!(credentials)
|
|
37
|
+
|
|
38
|
+
yield(user, failure_reason) if block_given?
|
|
39
|
+
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
35
43
|
old_session = session.dup.to_hash
|
|
36
44
|
reset_sorcery_session
|
|
37
45
|
old_session.each_pair do |k, v|
|
|
@@ -41,10 +49,8 @@ module Sorcery
|
|
|
41
49
|
|
|
42
50
|
auto_login(user)
|
|
43
51
|
after_login!(user, credentials)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
after_failed_login!(credentials)
|
|
47
|
-
nil
|
|
52
|
+
|
|
53
|
+
block_given? ? yield(current_user, nil) : current_user
|
|
48
54
|
end
|
|
49
55
|
end
|
|
50
56
|
|
|
@@ -41,10 +41,15 @@ module Sorcery
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
module ClassMethods
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
# This doesn't check to see if the account is still locked
|
|
45
|
+
def load_from_unlock_token(token, &block)
|
|
46
|
+
return if token.blank?
|
|
47
|
+
|
|
48
|
+
load_from_token(
|
|
49
|
+
token,
|
|
50
|
+
sorcery_config.unlock_token_attribute_name,
|
|
51
|
+
&block
|
|
52
|
+
)
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
protected
|
|
@@ -116,7 +121,10 @@ module Sorcery
|
|
|
116
121
|
if !login_unlocked? && config.login_lock_time_period != 0
|
|
117
122
|
login_unlock! if send(config.lock_expires_at_attribute_name) <= Time.now.in_time_zone
|
|
118
123
|
end
|
|
119
|
-
|
|
124
|
+
|
|
125
|
+
return false, :locked unless login_unlocked?
|
|
126
|
+
|
|
127
|
+
true
|
|
120
128
|
end
|
|
121
129
|
end
|
|
122
130
|
end
|
|
@@ -55,10 +55,13 @@ module Sorcery
|
|
|
55
55
|
module ClassMethods
|
|
56
56
|
# Find user by token, also checks for expiration.
|
|
57
57
|
# Returns the user if token found and is valid.
|
|
58
|
-
def load_from_reset_password_token(token)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
def load_from_reset_password_token(token, &block)
|
|
59
|
+
load_from_token(
|
|
60
|
+
token,
|
|
61
|
+
@sorcery_config.reset_password_token_attribute_name,
|
|
62
|
+
@sorcery_config.reset_password_token_expires_at_attribute_name,
|
|
63
|
+
&block
|
|
64
|
+
)
|
|
62
65
|
end
|
|
63
66
|
|
|
64
67
|
protected
|
|
@@ -59,10 +59,13 @@ module Sorcery
|
|
|
59
59
|
module ClassMethods
|
|
60
60
|
# Find user by token, also checks for expiration.
|
|
61
61
|
# Returns the user if token found and is valid.
|
|
62
|
-
def load_from_activation_token(token)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
def load_from_activation_token(token, &block)
|
|
63
|
+
load_from_token(
|
|
64
|
+
token,
|
|
65
|
+
@sorcery_config.activation_token_attribute_name,
|
|
66
|
+
@sorcery_config.activation_token_expires_at_attribute_name,
|
|
67
|
+
&block
|
|
68
|
+
)
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
protected
|
|
@@ -128,7 +131,14 @@ module Sorcery
|
|
|
128
131
|
|
|
129
132
|
def prevent_non_active_login
|
|
130
133
|
config = sorcery_config
|
|
131
|
-
|
|
134
|
+
|
|
135
|
+
if config.prevent_non_active_users_to_login
|
|
136
|
+
unless send(config.activation_state_attribute_name) == 'active'
|
|
137
|
+
return false, :inactive
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
true
|
|
132
142
|
end
|
|
133
143
|
end
|
|
134
144
|
end
|
|
@@ -16,13 +16,34 @@ module Sorcery
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
module ClassMethods
|
|
19
|
-
def load_from_token(token, token_attr_name, token_expiration_date_attr)
|
|
20
|
-
return
|
|
19
|
+
def load_from_token(token, token_attr_name, token_expiration_date_attr = nil, &block)
|
|
20
|
+
return token_response(failure: :invalid_token, &block) if token.blank?
|
|
21
|
+
|
|
21
22
|
user = sorcery_adapter.find_by_token(token_attr_name, token)
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
return token_response(failure: :user_not_found, &block) unless user
|
|
25
|
+
|
|
26
|
+
unless check_expiration_date(user, token_expiration_date_attr)
|
|
27
|
+
return token_response(user: user, failure: :token_expired, &block)
|
|
24
28
|
end
|
|
25
|
-
|
|
29
|
+
|
|
30
|
+
token_response(user: user, return_value: user, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def check_expiration_date(user, token_expiration_date_attr)
|
|
36
|
+
return true unless token_expiration_date_attr
|
|
37
|
+
|
|
38
|
+
expires_at = user.send(token_expiration_date_attr)
|
|
39
|
+
|
|
40
|
+
!expires_at || (Time.now.in_time_zone < expires_at)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def token_response(options = {})
|
|
44
|
+
yield(options[:user], options[:failure]) if block_given?
|
|
45
|
+
|
|
46
|
+
options[:return_value]
|
|
26
47
|
end
|
|
27
48
|
end
|
|
28
49
|
end
|
data/lib/sorcery/model.rb
CHANGED
|
@@ -80,10 +80,12 @@ module Sorcery
|
|
|
80
80
|
# Takes a username and password,
|
|
81
81
|
# Finds the user by the username and compares the user's password to the one supplied to the method.
|
|
82
82
|
# returns the user if success, nil otherwise.
|
|
83
|
-
def authenticate(*credentials)
|
|
83
|
+
def authenticate(*credentials, &block)
|
|
84
84
|
raise ArgumentError, 'at least 2 arguments required' if credentials.size < 2
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
if credentials[0].blank?
|
|
87
|
+
return authentication_response(return_value: false, failure: :invalid_login, &block)
|
|
88
|
+
end
|
|
87
89
|
|
|
88
90
|
if @sorcery_config.downcase_username_before_authenticating
|
|
89
91
|
credentials[0].downcase!
|
|
@@ -91,13 +93,29 @@ module Sorcery
|
|
|
91
93
|
|
|
92
94
|
user = sorcery_adapter.find_by_credentials(credentials)
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
return
|
|
96
|
+
unless user
|
|
97
|
+
return authentication_response(failure: :invalid_login, &block)
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
set_encryption_attributes
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
unless user.valid_password?(credentials[1])
|
|
103
|
+
return authentication_response(user: user, failure: :invalid_password, &block)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if user.respond_to?(:active_for_authentication?) && !user.active_for_authentication?
|
|
107
|
+
return authentication_response(user: user, failure: :inactive, &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@sorcery_config.before_authenticate.each do |callback|
|
|
111
|
+
success, reason = user.send(callback)
|
|
112
|
+
|
|
113
|
+
unless success
|
|
114
|
+
return authentication_response(user: user, failure: reason, &block)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
authentication_response(user: user, return_value: user, &block)
|
|
101
119
|
end
|
|
102
120
|
|
|
103
121
|
# encrypt tokens using current encryption_provider.
|
|
@@ -112,6 +130,12 @@ module Sorcery
|
|
|
112
130
|
|
|
113
131
|
protected
|
|
114
132
|
|
|
133
|
+
def authentication_response(options = {})
|
|
134
|
+
yield(options[:user], options[:failure]) if block_given?
|
|
135
|
+
|
|
136
|
+
options[:return_value]
|
|
137
|
+
end
|
|
138
|
+
|
|
115
139
|
def set_encryption_attributes
|
|
116
140
|
@sorcery_config.encryption_provider.stretches = @sorcery_config.stretches if @sorcery_config.encryption_provider.respond_to?(:stretches) && @sorcery_config.stretches
|
|
117
141
|
@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
|
|
@@ -9,9 +9,9 @@ module Sorcery
|
|
|
9
9
|
class Facebook < Base
|
|
10
10
|
include Protocols::Oauth2
|
|
11
11
|
|
|
12
|
-
attr_reader :mode, :param_name
|
|
12
|
+
attr_reader :mode, :param_name
|
|
13
13
|
attr_accessor :access_permissions, :display, :scope, :token_url,
|
|
14
|
-
:user_info_path, :auth_path, :api_version
|
|
14
|
+
:user_info_path, :auth_path, :api_version, :parse
|
|
15
15
|
|
|
16
16
|
def initialize
|
|
17
17
|
super
|
|
@@ -24,7 +24,7 @@ module Sorcery
|
|
|
24
24
|
@token_url = 'oauth/access_token'
|
|
25
25
|
@auth_path = 'dialog/oauth'
|
|
26
26
|
@mode = :query
|
|
27
|
-
@parse = :
|
|
27
|
+
@parse = :json
|
|
28
28
|
@param_name = 'access_token'
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Providers
|
|
3
|
+
# This class adds support for OAuth with Microsoft Graph.
|
|
4
|
+
#
|
|
5
|
+
# config.microsoft.key = <key>
|
|
6
|
+
# config.microsoft.secret = <secret>
|
|
7
|
+
# ...
|
|
8
|
+
#
|
|
9
|
+
class Microsoft < Base
|
|
10
|
+
include Protocols::Oauth2
|
|
11
|
+
|
|
12
|
+
attr_accessor :auth_url, :scope, :token_url, :user_info_url
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
@site = 'https://login.microsoftonline.com'
|
|
18
|
+
@auth_url = '/common/oauth2/v2.0/authorize'
|
|
19
|
+
@token_url = '/common/oauth2/v2.0/token'
|
|
20
|
+
@user_info_url = 'https://graph.microsoft.com/v1.0/me'
|
|
21
|
+
@scope = 'openid email https://graph.microsoft.com/User.Read'
|
|
22
|
+
@state = SecureRandom.hex(16)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def authorize_url(options = {})
|
|
26
|
+
oauth_params = {
|
|
27
|
+
client_id: @key,
|
|
28
|
+
response_type: 'code'
|
|
29
|
+
}
|
|
30
|
+
options.merge!(oauth_params)
|
|
31
|
+
super(options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_user_hash(access_token)
|
|
35
|
+
response = access_token.get(user_info_url)
|
|
36
|
+
|
|
37
|
+
auth_hash(access_token).tap do |h|
|
|
38
|
+
h[:user_info] = JSON.parse(response.body)
|
|
39
|
+
h[:uid] = h[:user_info]['id']
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# calculates and returns the url to which the user should be redirected,
|
|
44
|
+
# to get authenticated at the external provider's site.
|
|
45
|
+
def login_url(_params, _session)
|
|
46
|
+
authorize_url(authorize_url: auth_url)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# tries to login the user from access token
|
|
50
|
+
def process_callback(params, _session)
|
|
51
|
+
args = {}.tap do |a|
|
|
52
|
+
a[:code] = params[:code] if params[:code]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
get_access_token(args, token_url: token_url, token_method: :post)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -67,7 +67,7 @@ module Sorcery
|
|
|
67
67
|
# reload user class between specs
|
|
68
68
|
# so it will be possible to test the different submodules in isolation
|
|
69
69
|
def reload_user_class
|
|
70
|
-
Object.send(:remove_const, 'User')
|
|
70
|
+
User && Object.send(:remove_const, 'User')
|
|
71
71
|
load 'user.rb'
|
|
72
72
|
if User.respond_to?(:reset_column_information)
|
|
73
73
|
User.reset_column_information
|
data/lib/sorcery/version.rb
CHANGED
data/lib/sorcery.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Sorcery
|
|
|
65
65
|
|
|
66
66
|
require 'sorcery/adapters/base_adapter'
|
|
67
67
|
|
|
68
|
-
if defined?(ActiveRecord)
|
|
68
|
+
if defined?(ActiveRecord::Base)
|
|
69
69
|
require 'sorcery/adapters/active_record_adapter'
|
|
70
70
|
ActiveRecord::Base.extend Sorcery::Model
|
|
71
71
|
|
|
@@ -78,5 +78,18 @@ module Sorcery
|
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
if defined?(Mongoid::Document)
|
|
82
|
+
require 'sorcery/adapters/mongoid_adapter'
|
|
83
|
+
Mongoid::Document::ClassMethods.send :include, Sorcery::Model
|
|
84
|
+
|
|
85
|
+
Mongoid::Document.send :define_method, :sorcery_adapter do
|
|
86
|
+
@sorcery_adapter ||= Sorcery::Adapters::MongoidAdapter.new(self)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
Mongoid::Document::ClassMethods.send :define_method, :sorcery_adapter do
|
|
90
|
+
Sorcery::Adapters::MongoidAdapter.from(self)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
81
94
|
require 'sorcery/engine' if defined?(Rails)
|
|
82
95
|
end
|
data/sorcery.gemspec
CHANGED
|
@@ -10,8 +10,8 @@ Gem::Specification.new do |s|
|
|
|
10
10
|
s.description = 'Provides common authentication needs such as signing in/out, activating by email and resetting password.'
|
|
11
11
|
s.summary = 'Magical authentication for Rails applications'
|
|
12
12
|
s.homepage = 'https://github.com/Sorcery/sorcery'
|
|
13
|
-
s.post_install_message = "As of version 1.0 oauth/oauth2 won't be automatically bundled
|
|
14
|
-
s.post_install_message += '
|
|
13
|
+
s.post_install_message = "As of version 1.0 oauth/oauth2 won't be automatically bundled so you may need to add those dependencies to your Gemfile.\n"
|
|
14
|
+
s.post_install_message += 'You may need oauth2 if you use external providers such as any of these: https://github.com/Sorcery/sorcery/tree/master/lib/sorcery/providers'
|
|
15
15
|
|
|
16
16
|
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
17
17
|
s.require_paths = ['lib']
|
|
@@ -29,4 +29,5 @@ Gem::Specification.new do |s|
|
|
|
29
29
|
s.add_development_dependency 'simplecov', '>= 0.3.8'
|
|
30
30
|
s.add_development_dependency 'rspec-rails', '~> 3.5.0'
|
|
31
31
|
s.add_development_dependency 'test-unit', '~> 3.1.0'
|
|
32
|
+
s.add_development_dependency 'byebug', '~> 9.0.0'
|
|
32
33
|
end
|
|
@@ -20,7 +20,7 @@ describe SorceryController, type: :controller do
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it 'counts login retries' do
|
|
23
|
-
allow(User).to receive(:authenticate)
|
|
23
|
+
allow(User).to receive(:authenticate) { |&block| block.call(nil, :other) }
|
|
24
24
|
allow(User.sorcery_adapter).to receive(:find_by_credentials).with(['bla@bla.com', 'blabla']).and_return(user)
|
|
25
25
|
|
|
26
26
|
expect(user).to receive(:register_failed_login!).exactly(3).times
|
|
@@ -32,7 +32,7 @@ describe SorceryController, type: :controller do
|
|
|
32
32
|
# dirty hack for rails 4
|
|
33
33
|
allow(@controller).to receive(:register_last_activity_time_to_db)
|
|
34
34
|
|
|
35
|
-
allow(User).to receive(:authenticate).
|
|
35
|
+
allow(User).to receive(:authenticate) { |&block| block.call(user, nil) }
|
|
36
36
|
expect(user).to receive_message_chain(:sorcery_adapter, :update_attribute).with(:failed_logins_count, 0)
|
|
37
37
|
|
|
38
38
|
get :test_login, params: { email: 'bla@bla.com', password: 'secret' }
|
|
@@ -151,7 +151,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
151
151
|
expect(flash[:notice]).to eq 'Success!'
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
-
[:github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat].each do |provider|
|
|
154
|
+
[:github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat, :microsoft].each do |provider|
|
|
155
155
|
describe "with #{provider}" do
|
|
156
156
|
it 'login_at redirects correctly' do
|
|
157
157
|
get :"login_at_test_#{provider}"
|
|
@@ -201,7 +201,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
201
201
|
end
|
|
202
202
|
|
|
203
203
|
sorcery_reload!([:user_activation,:external], :user_activation_mailer => ::SorceryMailer)
|
|
204
|
-
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat])
|
|
204
|
+
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat, :microsoft])
|
|
205
205
|
|
|
206
206
|
# TODO: refactor
|
|
207
207
|
sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
|
|
@@ -231,6 +231,9 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
231
231
|
sorcery_controller_external_property_set(:wechat, :key, "eYVNBjBDi33aa9GkA3w")
|
|
232
232
|
sorcery_controller_external_property_set(:wechat, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
|
233
233
|
sorcery_controller_external_property_set(:wechat, :callback_url, "http://blabla.com")
|
|
234
|
+
sorcery_controller_external_property_set(:microsoft, :key, "eYVNBjBDi33aa9GkA3w")
|
|
235
|
+
sorcery_controller_external_property_set(:microsoft, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
|
236
|
+
sorcery_controller_external_property_set(:microsoft, :callback_url, "http://blabla.com")
|
|
234
237
|
end
|
|
235
238
|
|
|
236
239
|
after(:all) do
|
|
@@ -260,7 +263,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
260
263
|
expect(ActionMailer::Base.deliveries.size).to eq old_size
|
|
261
264
|
end
|
|
262
265
|
|
|
263
|
-
[:github, :google, :liveid, :vk, :salesforce, :paypal, :wechat].each do |provider|
|
|
266
|
+
[:github, :google, :liveid, :vk, :salesforce, :paypal, :wechat, :microsoft].each do |provider|
|
|
264
267
|
it "does not send activation email to external users (#{provider})" do
|
|
265
268
|
old_size = ActionMailer::Base.deliveries.size
|
|
266
269
|
create_new_external_user provider
|
|
@@ -409,7 +412,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
409
412
|
end
|
|
410
413
|
|
|
411
414
|
def set_external_property
|
|
412
|
-
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat])
|
|
415
|
+
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack, :wechat, :microsoft])
|
|
413
416
|
sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
|
|
414
417
|
sorcery_controller_external_property_set(:facebook, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
|
415
418
|
sorcery_controller_external_property_set(:facebook, :callback_url, "http://blabla.com")
|
|
@@ -437,6 +440,9 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
437
440
|
sorcery_controller_external_property_set(:wechat, :key, "eYVNBjBDi33aa9GkA3w")
|
|
438
441
|
sorcery_controller_external_property_set(:wechat, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
|
439
442
|
sorcery_controller_external_property_set(:wechat, :callback_url, "http://blabla.com")
|
|
443
|
+
sorcery_controller_external_property_set(:microsoft, :key, "eYVNBjBDi33aa9GkA3w")
|
|
444
|
+
sorcery_controller_external_property_set(:microsoft, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
|
445
|
+
sorcery_controller_external_property_set(:microsoft, :callback_url, "http://blabla.com")
|
|
440
446
|
end
|
|
441
447
|
|
|
442
448
|
def provider_url(provider)
|
|
@@ -448,7 +454,8 @@ describe SorceryController, active_record: true, type: :controller do
|
|
|
448
454
|
vk: "https://oauth.vk.com/authorize?client_id=#{::Sorcery::Controller::Config.vk.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.vk.scope}&state",
|
|
449
455
|
salesforce: "https://login.salesforce.com/services/oauth2/authorize?client_id=#{::Sorcery::Controller::Config.salesforce.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope#{'=' + ::Sorcery::Controller::Config.salesforce.scope unless ::Sorcery::Controller::Config.salesforce.scope.nil?}&state",
|
|
450
456
|
slack: "https://slack.com/oauth/authorize?client_id=#{::Sorcery::Controller::Config.slack.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=identity.basic%2C+identity.email&state",
|
|
451
|
-
wechat: "https://open.weixin.qq.com/connect/qrconnect?appid=#{::Sorcery::Controller::Config.wechat.key}&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=snsapi_login&state=#wechat_redirect"
|
|
457
|
+
wechat: "https://open.weixin.qq.com/connect/qrconnect?appid=#{::Sorcery::Controller::Config.wechat.key}&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=snsapi_login&state=#wechat_redirect",
|
|
458
|
+
microsoft: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=#{::Sorcery::Controller::Config.microsoft.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=openid+email+https%3A%2F%2Fgraph.microsoft.com%2FUser.Read&state"
|
|
452
459
|
}[provider]
|
|
453
460
|
end
|
|
454
461
|
end
|
|
@@ -22,7 +22,7 @@ describe SorceryController, type: :controller do
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it 'sets cookie on remember_me!' do
|
|
25
|
-
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret').
|
|
25
|
+
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) }
|
|
26
26
|
expect(user).to receive(:remember_me!)
|
|
27
27
|
|
|
28
28
|
post :test_login_with_remember, params: { email: 'bla@bla.com', password: 'secret' }
|
|
@@ -45,7 +45,7 @@ describe SorceryController, type: :controller do
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
it 'login(email,password,remember_me) logs user in and remembers' do
|
|
48
|
-
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret', '1').
|
|
48
|
+
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret', '1') { |&block| block.call(user, nil) }
|
|
49
49
|
expect(user).to receive(:remember_me!)
|
|
50
50
|
expect(user).to receive(:remember_me_token).and_return('abracadabra').twice
|
|
51
51
|
|
|
@@ -39,7 +39,7 @@ describe SorceryController, type: :controller do
|
|
|
39
39
|
it 'works if the session is stored as a string or a Time' do
|
|
40
40
|
session[:login_time] = Time.now.to_s
|
|
41
41
|
# TODO: ???
|
|
42
|
-
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret').
|
|
42
|
+
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) }
|
|
43
43
|
|
|
44
44
|
get :test_login, params: { email: 'bla@bla.com', password: 'secret' }
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ describe SorceryController, type: :controller do
|
|
|
50
50
|
context "with 'session_timeout_from_last_action'" do
|
|
51
51
|
it 'does not logout if there was activity' do
|
|
52
52
|
sorcery_controller_property_set(:session_timeout_from_last_action, true)
|
|
53
|
-
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret').
|
|
53
|
+
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) }
|
|
54
54
|
|
|
55
55
|
get :test_login, params: { email: 'bla@bla.com', password: 'secret' }
|
|
56
56
|
Timecop.travel(Time.now.in_time_zone + 0.3)
|
|
@@ -52,7 +52,7 @@ describe SorceryController, type: :controller do
|
|
|
52
52
|
describe '#login' do
|
|
53
53
|
context 'when succeeds' do
|
|
54
54
|
before do
|
|
55
|
-
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret').
|
|
55
|
+
expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) }
|
|
56
56
|
get :test_login, params: { email: 'bla@bla.com', password: 'secret' }
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -104,6 +104,10 @@ class SorceryController < ActionController::Base
|
|
|
104
104
|
login_at(:wechat)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
def login_at_test_microsoft
|
|
108
|
+
login_at(:microsoft)
|
|
109
|
+
end
|
|
110
|
+
|
|
107
111
|
def login_at_test_google
|
|
108
112
|
login_at(:google)
|
|
109
113
|
end
|
|
@@ -174,6 +178,14 @@ class SorceryController < ActionController::Base
|
|
|
174
178
|
end
|
|
175
179
|
end
|
|
176
180
|
|
|
181
|
+
def test_login_from_microsoft
|
|
182
|
+
if @user = login_from(:microsoft)
|
|
183
|
+
redirect_to 'bla', notice: 'Success!'
|
|
184
|
+
else
|
|
185
|
+
redirect_to 'blu', alert: 'Failed!'
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
177
189
|
def test_login_from_google
|
|
178
190
|
if @user = login_from(:google)
|
|
179
191
|
redirect_to 'bla', notice: 'Success!'
|
|
@@ -272,6 +284,14 @@ class SorceryController < ActionController::Base
|
|
|
272
284
|
end
|
|
273
285
|
end
|
|
274
286
|
|
|
287
|
+
def test_return_to_with_external_microsoft
|
|
288
|
+
if @user = login_from(:microsoft)
|
|
289
|
+
redirect_back_or_to 'bla', notice: 'Success!'
|
|
290
|
+
else
|
|
291
|
+
redirect_to 'blu', alert: 'Failed!'
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
275
295
|
def test_return_to_with_external_google
|
|
276
296
|
if @user = login_from(:google)
|
|
277
297
|
redirect_back_or_to 'bla', notice: 'Success!'
|
|
@@ -22,6 +22,7 @@ AppRoot::Application.routes.draw do
|
|
|
22
22
|
get :test_login_from_github
|
|
23
23
|
get :test_login_from_paypal
|
|
24
24
|
get :test_login_from_wechat
|
|
25
|
+
get :test_login_from_microsoft
|
|
25
26
|
get :test_login_from_google
|
|
26
27
|
get :test_login_from_liveid
|
|
27
28
|
get :test_login_from_vk
|
|
@@ -34,6 +35,7 @@ AppRoot::Application.routes.draw do
|
|
|
34
35
|
get :login_at_test_github
|
|
35
36
|
get :login_at_test_paypal
|
|
36
37
|
get :login_at_test_wechat
|
|
38
|
+
get :login_at_test_microsoft
|
|
37
39
|
get :login_at_test_google
|
|
38
40
|
get :login_at_test_liveid
|
|
39
41
|
get :login_at_test_vk
|
|
@@ -46,6 +48,7 @@ AppRoot::Application.routes.draw do
|
|
|
46
48
|
get :test_return_to_with_external_github
|
|
47
49
|
get :test_return_to_with_external_paypal
|
|
48
50
|
get :test_return_to_with_external_wechat
|
|
51
|
+
get :test_return_to_with_external_microsoft
|
|
49
52
|
get :test_return_to_with_external_google
|
|
50
53
|
get :test_return_to_with_external_liveid
|
|
51
54
|
get :test_return_to_with_external_vk
|
|
@@ -235,6 +235,27 @@ shared_examples_for 'rails_3_activation_model' do
|
|
|
235
235
|
|
|
236
236
|
expect(User.authenticate(user.email, 'secret')).to be_truthy
|
|
237
237
|
end
|
|
238
|
+
|
|
239
|
+
context 'in block mode' do
|
|
240
|
+
it 'does not allow a non-active user to authenticate' do
|
|
241
|
+
sorcery_model_property_set(:prevent_non_active_users_to_login, true)
|
|
242
|
+
|
|
243
|
+
User.authenticate(user.email, 'secret') do |user2, failure|
|
|
244
|
+
expect(user2).to eq user
|
|
245
|
+
expect(user2.activation_state).to eq 'pending'
|
|
246
|
+
expect(failure).to eq :inactive
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'allows a non-active user to authenticate if configured so' do
|
|
251
|
+
sorcery_model_property_set(:prevent_non_active_users_to_login, false)
|
|
252
|
+
|
|
253
|
+
User.authenticate(user.email, 'secret') do |user2, failure|
|
|
254
|
+
expect(user2).to eq user
|
|
255
|
+
expect(failure).to be_nil
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
238
259
|
end
|
|
239
260
|
|
|
240
261
|
describe 'load_from_activation_token' do
|
|
@@ -279,5 +300,62 @@ shared_examples_for 'rails_3_activation_model' do
|
|
|
279
300
|
|
|
280
301
|
expect(User.load_from_activation_token(user.activation_token)).to eq user
|
|
281
302
|
end
|
|
303
|
+
|
|
304
|
+
describe '#load_from_activation_token' do
|
|
305
|
+
context 'in block mode' do
|
|
306
|
+
it 'yields user when token is found' do
|
|
307
|
+
User.load_from_activation_token(user.activation_token) do |user2, failure|
|
|
308
|
+
expect(user2).to eq user
|
|
309
|
+
expect(failure).to be_nil
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'does NOT yield user when token is NOT found' do
|
|
314
|
+
User.load_from_activation_token('a') do |user2, failure|
|
|
315
|
+
expect(user2).to be_nil
|
|
316
|
+
expect(failure).to eq :user_not_found
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it 'yields user when token is found and not expired' do
|
|
321
|
+
sorcery_model_property_set(:activation_token_expiration_period, 500)
|
|
322
|
+
|
|
323
|
+
User.load_from_activation_token(user.activation_token) do |user2, failure|
|
|
324
|
+
expect(user2).to eq user
|
|
325
|
+
expect(failure).to be_nil
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it 'yields the user and failure reason when token is found and expired' do
|
|
330
|
+
sorcery_model_property_set(:activation_token_expiration_period, 0.1)
|
|
331
|
+
user
|
|
332
|
+
|
|
333
|
+
Timecop.travel(Time.now.in_time_zone + 0.5)
|
|
334
|
+
|
|
335
|
+
User.load_from_activation_token(user.activation_token) do |user2, failure|
|
|
336
|
+
expect(user2).to eq user
|
|
337
|
+
expect(failure).to eq :token_expired
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
it 'yields a failure reason if token is blank' do
|
|
342
|
+
[nil, ''].each do |token|
|
|
343
|
+
User.load_from_activation_token(token) do |user2, failure|
|
|
344
|
+
expect(user2).to be_nil
|
|
345
|
+
expect(failure).to eq :invalid_token
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'is always valid if expiration period is nil' do
|
|
351
|
+
sorcery_model_property_set(:activation_token_expiration_period, nil)
|
|
352
|
+
|
|
353
|
+
User.load_from_activation_token(user.activation_token) do |user2, failure|
|
|
354
|
+
expect(user2).to eq user
|
|
355
|
+
expect(failure).to be_nil
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
282
360
|
end
|
|
283
361
|
end
|
|
@@ -128,6 +128,71 @@ shared_examples_for 'rails_3_reset_password_model' do
|
|
|
128
128
|
expect(User.load_from_reset_password_token('')).to be_nil
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
+
describe '#load_from_reset_password_token' do
|
|
132
|
+
context 'in block mode' do
|
|
133
|
+
it 'yields user when token is found' do
|
|
134
|
+
user.generate_reset_password_token!
|
|
135
|
+
updated_user = User.sorcery_adapter.find(user.id)
|
|
136
|
+
|
|
137
|
+
User.load_from_reset_password_token(user.reset_password_token) do |user2, failure|
|
|
138
|
+
expect(user2).to eq updated_user
|
|
139
|
+
expect(failure).to be_nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'does NOT yield user when token is NOT found' do
|
|
144
|
+
user.generate_reset_password_token!
|
|
145
|
+
|
|
146
|
+
User.load_from_reset_password_token('a') do |user2, failure|
|
|
147
|
+
expect(user2).to be_nil
|
|
148
|
+
expect(failure).to eq :user_not_found
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'yields user when token is found and not expired' do
|
|
153
|
+
sorcery_model_property_set(:reset_password_expiration_period, 500)
|
|
154
|
+
user.generate_reset_password_token!
|
|
155
|
+
updated_user = User.sorcery_adapter.find(user.id)
|
|
156
|
+
|
|
157
|
+
User.load_from_reset_password_token(user.reset_password_token) do |user2, failure|
|
|
158
|
+
expect(user2).to eq updated_user
|
|
159
|
+
expect(failure).to be_nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'yields user and failure reason when token is found and expired' do
|
|
164
|
+
sorcery_model_property_set(:reset_password_expiration_period, 0.1)
|
|
165
|
+
user.generate_reset_password_token!
|
|
166
|
+
Timecop.travel(Time.now.in_time_zone + 0.5)
|
|
167
|
+
|
|
168
|
+
User.load_from_reset_password_token(user.reset_password_token) do |user2, failure|
|
|
169
|
+
expect(user2).to eq user
|
|
170
|
+
expect(failure).to eq :token_expired
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'is always valid if expiration period is nil' do
|
|
175
|
+
sorcery_model_property_set(:reset_password_expiration_period, nil)
|
|
176
|
+
user.generate_reset_password_token!
|
|
177
|
+
updated_user = User.sorcery_adapter.find(user.id)
|
|
178
|
+
|
|
179
|
+
User.load_from_reset_password_token(user.reset_password_token) do |user2, failure|
|
|
180
|
+
expect(user2).to eq updated_user
|
|
181
|
+
expect(failure).to be_nil
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'returns nil if token is blank' do
|
|
186
|
+
[nil, ''].each do |token|
|
|
187
|
+
User.load_from_reset_password_token(token) do |user2, failure|
|
|
188
|
+
expect(user2).to be_nil
|
|
189
|
+
expect(failure).to eq :invalid_token
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
131
196
|
it "'deliver_reset_password_instructions!' generates a reset_password_token" do
|
|
132
197
|
expect(user.reset_password_token).to be_nil
|
|
133
198
|
|
|
@@ -146,6 +146,31 @@ shared_examples_for 'rails_3_core_model' do
|
|
|
146
146
|
expect(User.authenticate(user.email, 'secret')).to be_nil
|
|
147
147
|
end
|
|
148
148
|
end
|
|
149
|
+
|
|
150
|
+
context 'in block mode' do
|
|
151
|
+
it 'yields the user if credentials are good' do
|
|
152
|
+
User.authenticate(user.email, 'secret') do |user2, failure|
|
|
153
|
+
expect(user2).to eq user
|
|
154
|
+
expect(failure).to be_nil
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'yields the user and proper error if credentials are bad' do
|
|
159
|
+
User.authenticate(user.email, 'wrong!') do |user2, failure|
|
|
160
|
+
expect(user2).to eq user
|
|
161
|
+
expect(failure).to eq :invalid_password
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'yields the proper error if no user exists' do
|
|
166
|
+
[nil, '', 'not@a.user'].each do |email|
|
|
167
|
+
User.authenticate(email, 'wrong!') do |user2, failure|
|
|
168
|
+
expect(user2).to be_nil
|
|
169
|
+
expect(failure).to eq :invalid_login
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
149
174
|
end
|
|
150
175
|
|
|
151
176
|
specify { expect(User).to respond_to(:encrypt) }
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sorcery
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Noam Ben Ari
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2017-
|
|
14
|
+
date: 2017-05-16 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: oauth
|
|
@@ -137,6 +137,20 @@ dependencies:
|
|
|
137
137
|
- - "~>"
|
|
138
138
|
- !ruby/object:Gem::Version
|
|
139
139
|
version: 3.1.0
|
|
140
|
+
- !ruby/object:Gem::Dependency
|
|
141
|
+
name: byebug
|
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
|
143
|
+
requirements:
|
|
144
|
+
- - "~>"
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: 9.0.0
|
|
147
|
+
type: :development
|
|
148
|
+
prerelease: false
|
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - "~>"
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: 9.0.0
|
|
140
154
|
description: Provides common authentication needs such as signing in/out, activating
|
|
141
155
|
by email and resetting password.
|
|
142
156
|
email: chase.gilliam@gmail.com
|
|
@@ -170,6 +184,7 @@ files:
|
|
|
170
184
|
- lib/sorcery.rb
|
|
171
185
|
- lib/sorcery/adapters/active_record_adapter.rb
|
|
172
186
|
- lib/sorcery/adapters/base_adapter.rb
|
|
187
|
+
- lib/sorcery/adapters/mongoid_adapter.rb
|
|
173
188
|
- lib/sorcery/controller.rb
|
|
174
189
|
- lib/sorcery/controller/config.rb
|
|
175
190
|
- lib/sorcery/controller/submodules/activity_logging.rb
|
|
@@ -206,6 +221,7 @@ files:
|
|
|
206
221
|
- lib/sorcery/providers/jira.rb
|
|
207
222
|
- lib/sorcery/providers/linkedin.rb
|
|
208
223
|
- lib/sorcery/providers/liveid.rb
|
|
224
|
+
- lib/sorcery/providers/microsoft.rb
|
|
209
225
|
- lib/sorcery/providers/paypal.rb
|
|
210
226
|
- lib/sorcery/providers/salesforce.rb
|
|
211
227
|
- lib/sorcery/providers/slack.rb
|
|
@@ -289,8 +305,8 @@ licenses:
|
|
|
289
305
|
- MIT
|
|
290
306
|
metadata: {}
|
|
291
307
|
post_install_message: |-
|
|
292
|
-
As of version 1.0 oauth/oauth2 won't be automatically bundled
|
|
293
|
-
|
|
308
|
+
As of version 1.0 oauth/oauth2 won't be automatically bundled so you may need to add those dependencies to your Gemfile.
|
|
309
|
+
You may need oauth2 if you use external providers such as any of these: https://github.com/Sorcery/sorcery/tree/master/lib/sorcery/providers
|
|
294
310
|
rdoc_options: []
|
|
295
311
|
require_paths:
|
|
296
312
|
- lib
|
|
@@ -311,3 +327,4 @@ signing_key:
|
|
|
311
327
|
specification_version: 4
|
|
312
328
|
summary: Magical authentication for Rails applications
|
|
313
329
|
test_files: []
|
|
330
|
+
has_rdoc:
|