omniauth-identity 3.0.4 → 3.0.9
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/CHANGELOG.md +73 -0
- data/README.md +87 -13
- data/lib/omniauth-identity/version.rb +1 -1
- data/lib/omniauth/identity.rb +1 -1
- data/lib/omniauth/identity/model.rb +92 -29
- data/lib/omniauth/identity/models/active_record.rb +5 -2
- data/lib/omniauth/identity/models/couch_potato.rb +9 -1
- data/lib/omniauth/identity/models/mongoid.rb +3 -0
- data/lib/omniauth/identity/models/{no_brainer.rb → nobrainer.rb} +3 -1
- data/lib/omniauth/identity/models/sequel.rb +16 -5
- data/lib/omniauth/identity/secure_password.rb +98 -37
- data/lib/omniauth/strategies/identity.rb +47 -23
- data/spec/omniauth/identity/model_spec.rb +19 -99
- data/spec/omniauth/identity/models/active_record_spec.rb +20 -10
- data/spec/omniauth/identity/models/sequel_spec.rb +31 -15
- data/spec/omniauth/strategies/identity_spec.rb +124 -5
- data/spec/spec_helper.rb +16 -5
- data/spec/support/shared_contexts/instance_with_instance_methods.rb +89 -0
- data/spec/support/shared_contexts/model_with_class_methods.rb +29 -0
- data/spec/support/shared_contexts/persistable_model.rb +24 -0
- metadata +17 -65
- data/spec/omniauth/identity/models/couch_potato_spec.rb +0 -21
- data/spec/omniauth/identity/models/mongoid_spec.rb +0 -28
- data/spec/omniauth/identity/models/no_brainer_spec.rb +0 -17
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'sqlite3'
|
4
|
+
require 'active_record'
|
5
|
+
require 'anonymous_active_record'
|
6
|
+
|
7
|
+
class TestIdentity < OmniAuth::Identity::Models::ActiveRecord; end
|
8
|
+
|
9
|
+
RSpec.describe(OmniAuth::Identity::Models::ActiveRecord, sqlite3: true) do
|
4
10
|
describe 'model', type: :model do
|
5
11
|
subject(:model_klass) do
|
6
12
|
AnonymousActiveRecord.generate(
|
@@ -8,23 +14,27 @@ RSpec.describe(OmniAuth::Identity::Models::ActiveRecord, db: true) do
|
|
8
14
|
columns: OmniAuth::Identity::Model::SCHEMA_ATTRIBUTES | %w[provider password_digest],
|
9
15
|
connection_params: { adapter: 'sqlite3', encoding: 'utf8', database: ':memory:' }
|
10
16
|
) do
|
17
|
+
auth_key :email
|
11
18
|
def flower
|
12
19
|
'🌸'
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
include_context 'persistable model'
|
25
|
+
|
26
|
+
describe '::table_name' do
|
27
|
+
it 'does not use STI rules for its table name' do
|
28
|
+
expect(TestIdentity.table_name).to eq('test_identities')
|
29
|
+
end
|
21
30
|
end
|
22
|
-
end
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
describe '::locate' do
|
33
|
+
it 'delegates locate to the where query method' do
|
34
|
+
allow(model_klass).to receive(:where).with('email' => 'open faced', 'category' => 'sandwiches',
|
35
|
+
'provider' => 'identity').and_return(['wakka'])
|
36
|
+
expect(model_klass.locate('email' => 'open faced', 'category' => 'sandwiches')).to eq('wakka')
|
37
|
+
end
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -1,23 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'sqlite3'
|
3
4
|
require 'sequel'
|
4
|
-
|
5
|
+
|
5
6
|
DB = Sequel.sqlite
|
6
|
-
DB.create_table :sequel_test_identities do
|
7
|
-
primary_key :id
|
8
|
-
String :ham_sandwich, null: false
|
9
|
-
String :password_digest, null: false
|
10
|
-
end
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
RSpec.describe(OmniAuth::Identity::Models::Sequel, sqlite3: true) do
|
9
|
+
before(:all) do
|
10
|
+
# Connect to an in-memory sqlite3 database.
|
11
|
+
DB.create_table :sequel_test_identities do
|
12
|
+
primary_key :id
|
13
|
+
String :email, null: false
|
14
|
+
String :password_digest, null: false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
sequel_test_identity = Class.new(Sequel::Model(:sequel_test_identities)) do
|
20
|
+
include ::OmniAuth::Identity::Models::Sequel
|
21
|
+
auth_key :email
|
22
|
+
end
|
23
|
+
stub_const('SequelTestIdentity', sequel_test_identity)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'model', type: :model do
|
27
|
+
subject(:model_klass) { SequelTestIdentity }
|
28
|
+
|
29
|
+
include_context 'persistable model'
|
16
30
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
31
|
+
describe '::locate' do
|
32
|
+
it 'delegates to the where query method' do
|
33
|
+
allow(model_klass).to receive(:where).with('email' => 'open faced',
|
34
|
+
'category' => 'sandwiches').and_return(['wakka'])
|
35
|
+
expect(model_klass.locate('email' => 'open faced', 'category' => 'sandwiches')).to eq('wakka')
|
36
|
+
end
|
37
|
+
end
|
22
38
|
end
|
23
39
|
end
|
@@ -1,10 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'sqlite3'
|
4
|
+
require 'active_record'
|
5
|
+
require 'anonymous_active_record'
|
6
|
+
|
7
|
+
RSpec.describe OmniAuth::Strategies::Identity, sqlite3: true do
|
4
8
|
attr_accessor :app
|
5
9
|
|
6
|
-
let(:
|
7
|
-
let(:
|
10
|
+
let(:env_hash) { last_response.headers['env'] }
|
11
|
+
let(:auth_hash) { env_hash['omniauth.auth'] }
|
12
|
+
let(:identity_hash) { env_hash['omniauth.identity'] }
|
8
13
|
let(:identity_options) { {} }
|
9
14
|
let(:anon_ar) do
|
10
15
|
AnonymousActiveRecord.generate(
|
@@ -12,6 +17,7 @@ RSpec.describe OmniAuth::Strategies::Identity do
|
|
12
17
|
columns: OmniAuth::Identity::Model::SCHEMA_ATTRIBUTES | %w[provider password_digest],
|
13
18
|
connection_params: { adapter: 'sqlite3', encoding: 'utf8', database: ':memory:' }
|
14
19
|
) do
|
20
|
+
auth_key :email
|
15
21
|
def balloon
|
16
22
|
'🎈'
|
17
23
|
end
|
@@ -193,7 +199,7 @@ RSpec.describe OmniAuth::Strategies::Identity do
|
|
193
199
|
end
|
194
200
|
end
|
195
201
|
|
196
|
-
context 'with
|
202
|
+
context 'with good identity' do
|
197
203
|
let(:properties) do
|
198
204
|
{
|
199
205
|
name: 'Awesome Dude',
|
@@ -209,9 +215,66 @@ RSpec.describe OmniAuth::Strategies::Identity do
|
|
209
215
|
expect(auth_hash['uid']).to match(/\d+/)
|
210
216
|
expect(auth_hash['provider']).to eq('identity')
|
211
217
|
end
|
218
|
+
|
219
|
+
context 'with on_validation proc' do
|
220
|
+
let(:identity_options) do
|
221
|
+
{ model: anon_ar, on_validation: on_validation_proc }
|
222
|
+
end
|
223
|
+
let(:on_validation_proc) do
|
224
|
+
lambda { |_env|
|
225
|
+
false
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
context 'when validation fails' do
|
230
|
+
it 'does not set the env hash' do
|
231
|
+
post '/auth/identity/register', properties
|
232
|
+
expect(env_hash).to eq(nil)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'renders registration form' do
|
236
|
+
post '/auth/identity/register', properties
|
237
|
+
expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'displays validation failure message' do
|
241
|
+
post '/auth/identity/register', properties
|
242
|
+
expect(last_response.body).to be_include(described_class.default_options[:validation_failure_message])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'when validation succeeds' do
|
247
|
+
let(:on_validation_proc) do
|
248
|
+
lambda { |_env|
|
249
|
+
true
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'sets the auth hash' do
|
254
|
+
post '/auth/identity/register', properties
|
255
|
+
expect(auth_hash['uid']).to match(/\d+/)
|
256
|
+
expect(auth_hash['provider']).to eq('identity')
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'does not render registration form' do
|
260
|
+
post '/auth/identity/register', properties
|
261
|
+
expect(last_response.body).not_to be_include(described_class.default_options[:registration_form_title])
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'does not display validation failure message' do
|
265
|
+
post '/auth/identity/register', properties
|
266
|
+
expect(last_response.body).not_to be_include(described_class.default_options[:validation_failure_message])
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'does not display registration failure message' do
|
270
|
+
post '/auth/identity/register', properties
|
271
|
+
expect(last_response.body).not_to be_include(described_class.default_options[:registration_failure_message])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
212
275
|
end
|
213
276
|
|
214
|
-
context 'with
|
277
|
+
context 'with bad identity' do
|
215
278
|
let(:properties) do
|
216
279
|
{
|
217
280
|
name: 'Awesome Dude',
|
@@ -249,6 +312,62 @@ RSpec.describe OmniAuth::Strategies::Identity do
|
|
249
312
|
expect(last_response.body).not_to be_include('One or more fields were invalid')
|
250
313
|
end
|
251
314
|
end
|
315
|
+
|
316
|
+
context 'with on_validation proc' do
|
317
|
+
let(:identity_options) do
|
318
|
+
{ model: anon_ar, on_validation: on_validation_proc }
|
319
|
+
end
|
320
|
+
let(:on_validation_proc) do
|
321
|
+
lambda { |_env|
|
322
|
+
false
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
context 'when validation fails' do
|
327
|
+
it 'does not set the env hash' do
|
328
|
+
post '/auth/identity/register', properties
|
329
|
+
expect(env_hash).to eq(nil)
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'renders registration form' do
|
333
|
+
post '/auth/identity/register', properties
|
334
|
+
expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'displays validation failure message' do
|
338
|
+
post '/auth/identity/register', properties
|
339
|
+
expect(last_response.body).to be_include(described_class.default_options[:validation_failure_message])
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
context 'when validation succeeds' do
|
344
|
+
let(:on_validation_proc) do
|
345
|
+
lambda { |_env|
|
346
|
+
true
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'does not set the env hash' do
|
351
|
+
post '/auth/identity/register', properties
|
352
|
+
expect(env_hash).to eq(nil)
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'renders registration form' do
|
356
|
+
post '/auth/identity/register', properties
|
357
|
+
expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'does not display validation failure message' do
|
361
|
+
post '/auth/identity/register', properties
|
362
|
+
expect(last_response.body).not_to be_include(described_class.default_options[:validation_failure_message])
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'display registration failure message' do
|
366
|
+
post '/auth/identity/register', properties
|
367
|
+
expect(last_response.body).to be_include(described_class.default_options[:registration_failure_message])
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
252
371
|
end
|
253
372
|
end
|
254
373
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# NOTE: mongoid and no_brainer can't be loaded at the same time.
|
4
|
+
# If you try it, one or both of them will not work.
|
5
|
+
# This is why the ORM specs are split into a separate directory and run in separate threads.
|
6
|
+
|
7
|
+
ENV['RUBY_ENV'] = 'test' # Used by NoBrainer
|
8
|
+
ENV['MONGOID_ENV'] = 'test' # Used by Mongoid
|
9
|
+
|
3
10
|
ruby_version = Gem::Version.new(RUBY_VERSION)
|
4
11
|
require 'simplecov' if ruby_version >= Gem::Version.new('2.7') && RUBY_ENGINE == 'ruby'
|
5
12
|
|
6
13
|
require 'rack/test'
|
7
|
-
require '
|
8
|
-
require 'sqlite3'
|
9
|
-
require 'sequel'
|
10
|
-
require 'anonymous_active_record'
|
14
|
+
require 'rspec/block_is_expected'
|
11
15
|
require 'byebug' if RUBY_ENGINE == 'ruby'
|
12
16
|
|
13
17
|
# This gem
|
14
18
|
require 'omniauth/identity'
|
15
19
|
|
20
|
+
spec_root_matcher = %r{#{__dir__}/(.+)\.rb\Z}
|
21
|
+
Dir.glob(Pathname.new(__dir__).join('support/**/', '*.rb')).each { |f| require f.match(spec_root_matcher)[1] }
|
22
|
+
|
23
|
+
DEFAULT_PASSWORD = 'hang-a-left-at-the-diner'
|
24
|
+
DEFAULT_EMAIL = 'mojo@example.com'
|
25
|
+
|
16
26
|
RSpec.configure do |config|
|
17
27
|
config.include Rack::Test::Methods
|
18
|
-
|
28
|
+
|
29
|
+
# config.include ::Mongoid::Matchers, db: :mongodb
|
19
30
|
|
20
31
|
# Enable flags like --only-failures and --next-failure
|
21
32
|
config.example_status_persistence_file_path = '.rspec_status'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'instance with instance methods' do
|
4
|
+
describe '#initialize' do
|
5
|
+
it 'does not raise an error' do
|
6
|
+
block_is_expected.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#uid' do
|
11
|
+
it 'defaults to #id' do
|
12
|
+
allow(instance).to receive(:respond_to?).with(:id).and_return(true)
|
13
|
+
allow(instance).to receive(:id).and_return 'wakka-do'
|
14
|
+
expect(instance.uid).to eq('wakka-do')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'stringifies it' do
|
18
|
+
allow(instance).to receive(:id).and_return 123
|
19
|
+
expect(instance.uid).to eq('123')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'raises NotImplementedError if #id is not defined' do
|
23
|
+
allow(instance).to receive(:respond_to?).with(:id).and_return(false)
|
24
|
+
expect { instance.uid }.to raise_error(NotImplementedError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#auth_key' do
|
29
|
+
it 'defaults to #email' do
|
30
|
+
allow(instance).to receive(:respond_to?).with(:email).and_return(true)
|
31
|
+
allow(instance).to receive(:email).and_return('bob@bob.com')
|
32
|
+
expect(instance.auth_key).to eq('bob@bob.com')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'uses the class .auth_key' do
|
36
|
+
instance.class.auth_key 'login'
|
37
|
+
allow(instance).to receive(:login).and_return 'bob'
|
38
|
+
expect(instance.auth_key).to eq('bob')
|
39
|
+
instance.class.auth_key nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#auth_key=' do
|
44
|
+
it 'defaults to setting email' do
|
45
|
+
allow(instance).to receive(:respond_to?).with(:email=).and_return(true)
|
46
|
+
expect(instance).to receive(:email=).with 'abc'
|
47
|
+
|
48
|
+
instance.auth_key = 'abc'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'uses a custom .auth_key if one is provided' do
|
52
|
+
instance.class.auth_key 'login'
|
53
|
+
allow(instance).to receive(:respond_to?).with(:login=).and_return(true)
|
54
|
+
expect(instance).to receive(:login=).with('abc')
|
55
|
+
|
56
|
+
instance.auth_key = 'abc'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#info' do
|
61
|
+
it 'includes all attributes as they have been set' do
|
62
|
+
allow(instance).to receive(:name).and_return('Bob Bobson')
|
63
|
+
allow(instance).to receive(:nickname).and_return('bob')
|
64
|
+
|
65
|
+
expect(instance.info).to include({
|
66
|
+
'name' => 'Bob Bobson',
|
67
|
+
'nickname' => 'bob'
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'uses firstname and lastname, over nickname, to set missing name' do
|
72
|
+
allow(instance).to receive(:first_name).and_return('shoeless')
|
73
|
+
allow(instance).to receive(:last_name).and_return('joe')
|
74
|
+
allow(instance).to receive(:nickname).and_return('george')
|
75
|
+
instance.info['name'] == 'shoeless joe'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'uses nickname to set missing name when first and last are not set' do
|
79
|
+
allow(instance).to receive(:nickname).and_return('bob')
|
80
|
+
instance.info['name'] == 'bob'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'does not overwrite a provided name' do
|
84
|
+
allow(instance).to receive(:name).and_return('Awesome Dude')
|
85
|
+
allow(instance).to receive(:first_name).and_return('Frank')
|
86
|
+
expect(instance.info['name']).to eq('Awesome Dude')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'model with class methods' do
|
4
|
+
describe 'class definition' do
|
5
|
+
it 'does not raise an error' do
|
6
|
+
block_is_expected.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '::authenticate' do
|
11
|
+
it 'calls locate and then authenticate' do
|
12
|
+
mocked_instance = double('ExampleModel', authenticate: 'abbadoo')
|
13
|
+
allow(model_klass).to receive(:locate).with('email' => 'example').and_return(mocked_instance)
|
14
|
+
expect(model_klass.authenticate({ 'email' => 'example' }, 'pass')).to eq('abbadoo')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'calls locate with additional scopes when provided' do
|
18
|
+
mocked_instance = double('ExampleModel', authenticate: 'abbadoo')
|
19
|
+
allow(model_klass).to receive(:locate).with('email' => 'example',
|
20
|
+
'user_type' => 'admin').and_return(mocked_instance)
|
21
|
+
expect(model_klass.authenticate({ 'email' => 'example', 'user_type' => 'admin' }, 'pass')).to eq('abbadoo')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'recovers gracefully if locate is nil' do
|
25
|
+
allow(model_klass).to receive(:locate).and_return(nil)
|
26
|
+
expect(model_klass.authenticate('blah', 'foo')).to be false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_context 'persistable model' do
|
4
|
+
include_context 'model with class methods'
|
5
|
+
|
6
|
+
describe 'instance methods' do
|
7
|
+
subject(:instance) { model_klass.new }
|
8
|
+
|
9
|
+
include_context 'instance with instance methods'
|
10
|
+
|
11
|
+
describe '#save' do
|
12
|
+
subject(:save) do
|
13
|
+
instance.email = DEFAULT_EMAIL
|
14
|
+
instance.password = DEFAULT_PASSWORD
|
15
|
+
instance.password_confirmation = DEFAULT_PASSWORD
|
16
|
+
instance.save
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not raise an error' do
|
20
|
+
save
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|