clearance 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of clearance might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Appraisals +1 -1
- data/CONTRIBUTING.md +2 -2
- data/Gemfile +2 -4
- data/Gemfile.lock +13 -19
- data/NEWS.md +14 -0
- data/README.md +69 -9
- data/app/controllers/clearance/sessions_controller.rb +7 -6
- data/app/views/users/new.html.erb +1 -1
- data/config/locales/clearance.en.yml +2 -0
- data/lib/clearance.rb +1 -0
- data/lib/clearance/authentication.rb +4 -2
- data/lib/clearance/configuration.rb +7 -2
- data/lib/clearance/default_sign_in_guard.rb +19 -0
- data/lib/clearance/session.rb +66 -16
- data/lib/clearance/session_status.rb +19 -0
- data/lib/clearance/sign_in_guard.rb +36 -0
- data/lib/clearance/testing/application.rb +2 -1
- data/lib/clearance/user.rb +1 -1
- data/lib/clearance/version.rb +1 -1
- data/spec/clearance/session_spec.rb +207 -35
- data/spec/clearance/sign_in_guard_spec.rb +29 -0
- data/spec/configuration_spec.rb +51 -27
- data/spec/controllers/apis_controller_spec.rb +0 -6
- data/spec/controllers/permissions_controller_spec.rb +70 -0
- data/spec/controllers/sessions_controller_spec.rb +1 -1
- data/spec/models/user_spec.rb +2 -0
- metadata +10 -5
- data/spec/controllers/denies_controller_spec.rb +0 -62
@@ -0,0 +1,19 @@
|
|
1
|
+
module Clearance
|
2
|
+
class SuccessStatus
|
3
|
+
def success?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class FailureStatus
|
9
|
+
attr_reader :failure_message
|
10
|
+
|
11
|
+
def initialize(failure_message)
|
12
|
+
@failure_message = failure_message
|
13
|
+
end
|
14
|
+
|
15
|
+
def success?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'clearance/session_status'
|
2
|
+
|
3
|
+
module Clearance
|
4
|
+
class SignInGuard
|
5
|
+
def initialize(session, stack = [])
|
6
|
+
@session = session
|
7
|
+
@stack = stack
|
8
|
+
end
|
9
|
+
|
10
|
+
def success
|
11
|
+
SuccessStatus.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure(message)
|
15
|
+
FailureStatus.new(message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def next_guard
|
19
|
+
stack.call
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
attr_reader :stack, :session
|
25
|
+
|
26
|
+
|
27
|
+
def signed_in?
|
28
|
+
session.signed_in?
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_user
|
32
|
+
session.current_user
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -28,6 +28,7 @@ module Clearance
|
|
28
28
|
|
29
29
|
if Clearance::Testing.rails4?
|
30
30
|
config.paths.add 'config/routes.rb', with: "#{APP_ROOT}/config/routes.rb"
|
31
|
+
config.secret_key_base = 'SECRET_KEY_BASE'
|
31
32
|
else
|
32
33
|
config.paths.add 'config/routes', with: "#{APP_ROOT}/config/routes.rb"
|
33
34
|
end
|
@@ -36,7 +37,7 @@ module Clearance
|
|
36
37
|
initialize!
|
37
38
|
end
|
38
39
|
|
39
|
-
def initialize!
|
40
|
+
def initialize!(&block)
|
40
41
|
FileUtils.mkdir_p(Rails.root.join('db').to_s)
|
41
42
|
super unless @initialized
|
42
43
|
end
|
data/lib/clearance/user.rb
CHANGED
data/lib/clearance/version.rb
CHANGED
@@ -17,7 +17,6 @@ describe Clearance::Session do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'returns nil for an unknown user' do
|
20
|
-
user = create(:user)
|
21
20
|
env = env_with_remember_token('bogus')
|
22
21
|
session = Clearance::Session.new(env)
|
23
22
|
session.should be_signed_out
|
@@ -38,6 +37,38 @@ describe Clearance::Session do
|
|
38
37
|
expect(session.current_user).to eq user
|
39
38
|
end
|
40
39
|
|
40
|
+
context 'with a block' do
|
41
|
+
it 'passes the success status to the block when sign in succeeds' do
|
42
|
+
success_status = stub_status(Clearance::SuccessStatus, true)
|
43
|
+
success_lambda = stub_callable
|
44
|
+
|
45
|
+
session.sign_in build(:user), &success_lambda
|
46
|
+
|
47
|
+
expect(success_lambda).to have_been_called.with(success_status)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'passes the failure status to the block when sign in fails' do
|
51
|
+
failure_status = stub_status(Clearance::FailureStatus, false)
|
52
|
+
failure_lambda = stub_callable
|
53
|
+
|
54
|
+
session.sign_in nil, &failure_lambda
|
55
|
+
|
56
|
+
expect(failure_lambda).to have_been_called.with(failure_status)
|
57
|
+
end
|
58
|
+
|
59
|
+
def stub_status(status_class, success)
|
60
|
+
stub('status', success?: success).tap do |status|
|
61
|
+
status_class.stubs(new: status)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def stub_callable
|
66
|
+
lambda {}.tap do |callable|
|
67
|
+
callable.stubs(:call)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
41
72
|
context 'with nil argument' do
|
42
73
|
it 'assigns current_user' do
|
43
74
|
session.sign_in nil
|
@@ -45,6 +76,57 @@ describe Clearance::Session do
|
|
45
76
|
expect(session.current_user).to be_nil
|
46
77
|
end
|
47
78
|
end
|
79
|
+
|
80
|
+
context 'with a sign in stack' do
|
81
|
+
|
82
|
+
it 'runs the first guard' do
|
83
|
+
guard = stub_sign_in_guard(succeed: true)
|
84
|
+
user = build(:user)
|
85
|
+
|
86
|
+
session.sign_in user
|
87
|
+
|
88
|
+
expect(guard).to have_received(:call)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'will not sign in the user if the guard stack fails' do
|
92
|
+
stub_sign_in_guard(succeed: false)
|
93
|
+
user = build(:user)
|
94
|
+
|
95
|
+
session.sign_in user
|
96
|
+
|
97
|
+
expect(session.instance_variable_get("@cookies")).to be_nil
|
98
|
+
expect(session.current_user).to be_nil
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def stub_sign_in_guard(options)
|
103
|
+
session_status = stub_status(options.fetch(:succeed))
|
104
|
+
|
105
|
+
stub('guard', call: session_status).tap do |guard|
|
106
|
+
Clearance.configuration.sign_in_guards << stub_guard_class(guard)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def stub_default_sign_in_guard
|
111
|
+
stub(:default_sign_in_guard).tap do |sign_in_guard|
|
112
|
+
Clearance::DefaultSignInGuard.stubs(:new).with(session).returns(sign_in_guard)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def stub_guard_class(guard)
|
117
|
+
stub(:guard_class).tap do |guard_class|
|
118
|
+
guard_class.stubs(:new).with(session, stub_default_sign_in_guard).returns(guard)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def stub_status(success)
|
123
|
+
stub('status', success?: success)
|
124
|
+
end
|
125
|
+
|
126
|
+
after do
|
127
|
+
Clearance.configuration.sign_in_guards = []
|
128
|
+
end
|
129
|
+
end
|
48
130
|
end
|
49
131
|
|
50
132
|
context 'if httponly is set' do
|
@@ -74,54 +156,139 @@ describe Clearance::Session do
|
|
74
156
|
end
|
75
157
|
end
|
76
158
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
159
|
+
describe 'remember token cookie expiration' do
|
160
|
+
context 'default configuration' do
|
161
|
+
it 'is set to 1 year from now' do
|
162
|
+
user = stub('User', remember_token: '123abc')
|
163
|
+
headers = {}
|
164
|
+
session = Clearance::Session.new(env_without_remember_token)
|
165
|
+
session.sign_in user
|
166
|
+
session.add_cookie_to_headers headers
|
167
|
+
headers.should set_cookie('remember_token', user.remember_token, 1.year.from_now)
|
168
|
+
end
|
169
|
+
end
|
85
170
|
|
86
|
-
|
87
|
-
|
171
|
+
context 'configured with lambda taking no arguments' do
|
172
|
+
it 'logs a deprecation warning' do
|
173
|
+
expiration = -> { Time.now }
|
174
|
+
with_custom_expiration expiration do
|
175
|
+
session = Clearance::Session.new(env_without_remember_token)
|
176
|
+
session.stubs(:warn)
|
177
|
+
session.add_cookie_to_headers headers
|
178
|
+
expect(session).to have_received(:warn).once
|
179
|
+
end
|
180
|
+
end
|
88
181
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
182
|
+
it 'is set to the value of the evaluated lambda' do
|
183
|
+
expires_at = -> { 1.day.from_now }
|
184
|
+
with_custom_expiration expires_at do
|
185
|
+
user = stub('User', remember_token: '123abc')
|
186
|
+
headers = {}
|
187
|
+
session = Clearance::Session.new(env_without_remember_token)
|
188
|
+
session.sign_in user
|
189
|
+
session.stubs(:warn)
|
190
|
+
session.add_cookie_to_headers headers
|
191
|
+
headers.should set_cookie('remember_token', user.remember_token, expires_at.call)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'configured with lambda taking one argument' do
|
197
|
+
it 'it can use other cookies to set the value of the expires token' do
|
198
|
+
remembered_expires = 12.hours.from_now
|
199
|
+
expires_at = ->(cookies) { cookies['remember_me'] ? remembered_expires : nil }
|
200
|
+
with_custom_expiration expires_at do
|
201
|
+
user = stub('User', remember_token: '123abc')
|
202
|
+
headers = {}
|
203
|
+
session = Clearance::Session.new(env_with_cookies(remember_me: 'true'))
|
204
|
+
session.sign_in user
|
205
|
+
session.add_cookie_to_headers headers
|
206
|
+
headers.should set_cookie('remember_token', user.remember_token, remembered_expires)
|
207
|
+
end
|
208
|
+
end
|
98
209
|
end
|
99
210
|
end
|
100
211
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
212
|
+
describe 'secure cookie option' do
|
213
|
+
context 'when not set' do
|
214
|
+
before do
|
215
|
+
session.sign_in(user)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'sets a standard cookie' do
|
219
|
+
session.add_cookie_to_headers(headers)
|
220
|
+
|
221
|
+
headers['Set-Cookie'].should_not =~ /remember_token=.+; secure/
|
222
|
+
end
|
105
223
|
end
|
106
224
|
|
107
|
-
|
108
|
-
|
225
|
+
context 'when set' do
|
226
|
+
before do
|
227
|
+
Clearance.configuration.secure_cookie = true
|
228
|
+
session.sign_in(user)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'sets a secure cookie' do
|
232
|
+
session.add_cookie_to_headers(headers)
|
233
|
+
|
234
|
+
headers['Set-Cookie'].should =~ /remember_token=.+; secure/
|
235
|
+
end
|
109
236
|
|
110
|
-
|
237
|
+
after { restore_default_config }
|
111
238
|
end
|
239
|
+
end
|
112
240
|
|
113
|
-
|
241
|
+
describe 'cookie domain option' do
|
242
|
+
context 'when set' do
|
243
|
+
before do
|
244
|
+
Clearance.configuration.cookie_domain = '.example.com'
|
245
|
+
session.sign_in(user)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'sets a standard cookie' do
|
249
|
+
session.add_cookie_to_headers(headers)
|
250
|
+
|
251
|
+
headers['Set-Cookie'].should =~ /domain=\.example\.com; path/
|
252
|
+
end
|
253
|
+
|
254
|
+
after { restore_default_config }
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'when not set' do
|
258
|
+
before { session.sign_in(user) }
|
259
|
+
|
260
|
+
it 'sets a standard cookie' do
|
261
|
+
session.add_cookie_to_headers(headers)
|
262
|
+
|
263
|
+
headers['Set-Cookie'].should_not =~ /domain=.+; path/
|
264
|
+
end
|
265
|
+
end
|
114
266
|
end
|
115
267
|
|
116
|
-
|
117
|
-
|
118
|
-
session.sign_in(user)
|
268
|
+
describe 'cookie path option' do
|
269
|
+
context 'when not set' do
|
270
|
+
before { session.sign_in(user) }
|
271
|
+
|
272
|
+
it 'sets a standard cookie' do
|
273
|
+
session.add_cookie_to_headers(headers)
|
274
|
+
|
275
|
+
headers['Set-Cookie'].should_not =~ /domain=.+; path/
|
276
|
+
end
|
119
277
|
end
|
120
278
|
|
121
|
-
|
122
|
-
|
279
|
+
context 'when set' do
|
280
|
+
before do
|
281
|
+
Clearance.configuration.cookie_path = '/user'
|
282
|
+
session.sign_in(user)
|
283
|
+
end
|
123
284
|
|
124
|
-
|
285
|
+
it 'sets a standard cookie' do
|
286
|
+
session.add_cookie_to_headers(headers)
|
287
|
+
|
288
|
+
headers['Set-Cookie'].should =~ /path=\/user; expires/
|
289
|
+
end
|
290
|
+
|
291
|
+
after { restore_default_config }
|
125
292
|
end
|
126
293
|
end
|
127
294
|
|
@@ -164,8 +331,13 @@ describe Clearance::Session do
|
|
164
331
|
header['Set-Cookie']
|
165
332
|
end
|
166
333
|
|
334
|
+
def have_been_called
|
335
|
+
have_received(:call)
|
336
|
+
end
|
337
|
+
|
167
338
|
def with_custom_expiration(custom_duration)
|
168
|
-
Clearance.configuration.cookie_expiration =
|
339
|
+
Clearance.configuration.cookie_expiration = custom_duration
|
340
|
+
yield
|
169
341
|
ensure
|
170
342
|
restore_default_config
|
171
343
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Clearance
|
4
|
+
describe SignInGuard do
|
5
|
+
it 'handles success' do
|
6
|
+
sign_in_guard = SignInGuard.new(stub('session'))
|
7
|
+
status = stub('status')
|
8
|
+
SuccessStatus.stubs(:new).returns(status)
|
9
|
+
|
10
|
+
expect(sign_in_guard.success).to eq(status)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'handles failure' do
|
14
|
+
sign_in_guard = SignInGuard.new(stub('session'))
|
15
|
+
status = stub('status')
|
16
|
+
failure_message = "Failed"
|
17
|
+
FailureStatus.stubs(:new).with(failure_message).returns(status)
|
18
|
+
|
19
|
+
expect(sign_in_guard.failure(failure_message)).to eq(status)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can proceed to the next guard' do
|
23
|
+
guards = stub('guards', call: true)
|
24
|
+
sign_in_guard = SignInGuard.new(stub('session'), guards)
|
25
|
+
sign_in_guard.next_guard
|
26
|
+
expect(guards).to have_received(:call)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -3,18 +3,18 @@ require 'spec_helper'
|
|
3
3
|
describe Clearance::Configuration do
|
4
4
|
after { restore_default_config }
|
5
5
|
|
6
|
-
|
6
|
+
context 'when no user_model_name is specified' do
|
7
7
|
before do
|
8
8
|
Clearance.configure do |config|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'defaults to User' do
|
13
|
-
Clearance.configuration.user_model.
|
13
|
+
expect(Clearance.configuration.user_model).to eq ::User
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
context 'when a custom user_model_name is specified' do
|
18
18
|
before do
|
19
19
|
MyUser = Class.new
|
20
20
|
|
@@ -23,53 +23,41 @@ describe Clearance::Configuration do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
after do
|
27
|
-
Clearance.configure do |config|
|
28
|
-
config.user_model = ::User
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
26
|
it 'is used instead of User' do
|
33
|
-
Clearance.configuration.user_model.
|
27
|
+
expect(Clearance.configuration.user_model).to eq ::MyUser
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
37
|
-
|
31
|
+
context 'when secure_cookie is set to true' do
|
38
32
|
before do
|
39
33
|
Clearance.configure do |config|
|
40
34
|
config.secure_cookie = true
|
41
35
|
end
|
42
36
|
end
|
43
37
|
|
44
|
-
after do
|
45
|
-
Clearance.configure do |config|
|
46
|
-
config.secure_cookie = false
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
38
|
it 'returns true' do
|
51
|
-
Clearance.configuration.secure_cookie.
|
39
|
+
expect(Clearance.configuration.secure_cookie).to be_true
|
52
40
|
end
|
53
41
|
end
|
54
42
|
|
55
|
-
|
43
|
+
context 'when secure_cookie is not specified' do
|
56
44
|
before do
|
57
45
|
Clearance.configure do |config|
|
58
46
|
end
|
59
47
|
end
|
60
48
|
|
61
49
|
it 'defaults to false' do
|
62
|
-
Clearance.configuration.secure_cookie.
|
50
|
+
expect(Clearance.configuration.secure_cookie).to be_false
|
63
51
|
end
|
64
52
|
end
|
65
53
|
|
66
|
-
|
54
|
+
context 'when no redirect URL specified' do
|
67
55
|
it 'should return "/" as redirect URL' do
|
68
|
-
Clearance::Configuration.new.redirect_url.
|
56
|
+
expect(Clearance::Configuration.new.redirect_url).to eq '/'
|
69
57
|
end
|
70
58
|
end
|
71
59
|
|
72
|
-
|
60
|
+
context 'when redirect URL is specified' do
|
73
61
|
let(:new_redirect_url) { '/admin' }
|
74
62
|
|
75
63
|
before do
|
@@ -78,14 +66,50 @@ describe Clearance::Configuration do
|
|
78
66
|
end
|
79
67
|
end
|
80
68
|
|
81
|
-
|
69
|
+
it 'should return new redirect URL' do
|
70
|
+
expect(Clearance.configuration.redirect_url).to eq new_redirect_url
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when specifying sign in guards' do
|
75
|
+
DummyGuard = Class.new
|
76
|
+
|
77
|
+
before do
|
82
78
|
Clearance.configure do |config|
|
83
|
-
config.
|
79
|
+
config.sign_in_guards = [DummyGuard]
|
84
80
|
end
|
85
81
|
end
|
86
82
|
|
87
|
-
it 'should return
|
88
|
-
Clearance.configuration.
|
83
|
+
it 'should return the stack with added guards' do
|
84
|
+
expect(Clearance.configuration.sign_in_guards).to eq [DummyGuard]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when cookie domain is specified' do
|
89
|
+
let(:domain) { '.example.com' }
|
90
|
+
|
91
|
+
before do
|
92
|
+
Clearance.configure do |config|
|
93
|
+
config.cookie_domain = domain
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns configured value' do
|
98
|
+
expect(Clearance.configuration.cookie_domain).to eq domain
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when cookie path is specified' do
|
103
|
+
let(:path) { '/user' }
|
104
|
+
|
105
|
+
before do
|
106
|
+
Clearance.configure do |config|
|
107
|
+
config.cookie_path = path
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns configured value' do
|
112
|
+
expect(Clearance.configuration.cookie_path).to eq path
|
89
113
|
end
|
90
114
|
end
|
91
115
|
end
|