omniauth-identity 3.0.1 → 3.0.6

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module OmniAuth
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'couch_potato'
2
4
 
3
5
  module OmniAuth
4
6
  module Identity
5
7
  module Models
6
8
  # can not be named CouchPotato since there is a class with that name
9
+ # NOTE: CouchPotato is based on ActiveModel.
7
10
  module CouchPotatoModule
8
11
  def self.included(base)
9
12
  base.class_eval do
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mongoid'
2
4
 
3
5
  module OmniAuth
4
6
  module Identity
5
7
  module Models
8
+ # NOTE: Mongoid is based on ActiveModel.
6
9
  module Mongoid
7
10
  def self.included(base)
8
11
  base.class_eval do
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nobrainer'
4
+
5
+ module OmniAuth
6
+ module Identity
7
+ module Models
8
+ # http://nobrainer.io/ an ORM for RethinkDB
9
+ # NOTE: NoBrainer is based on ActiveModel.
10
+ module NoBrainer
11
+ def self.included(base)
12
+ base.class_eval do
13
+ include ::OmniAuth::Identity::Model
14
+ include ::OmniAuth::Identity::SecurePassword
15
+
16
+ has_secure_password
17
+
18
+ def self.auth_key=(key)
19
+ super
20
+ validates_uniqueness_of key, case_sensitive: false
21
+ end
22
+
23
+ def self.locate(search_hash)
24
+ where(search_hash).first
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nobrainer'
4
+
5
+ module OmniAuth
6
+ module Identity
7
+ module Models
8
+ # http://sequel.jeremyevans.net/ an SQL ORM
9
+ # NOTE: Sequel is *not* based on ActiveModel, but supports the API we need, except for `persisted`:
10
+ # * create
11
+ # * save, but save is deprecated in favor of `save_changes`
12
+ module Sequel
13
+ def self.included(base)
14
+ base.class_eval do
15
+ # NOTE: Using the deprecated :validations_class_methods because it defines
16
+ # validates_confirmation_of, while current :validation_helpers does not.
17
+ # plugin :validation_helpers
18
+ plugin :validation_class_methods
19
+
20
+ include OmniAuth::Identity::Model
21
+ include ::OmniAuth::Identity::SecurePassword
22
+
23
+ has_secure_password
24
+
25
+ alias_method :persisted?, :valid?
26
+
27
+ def self.auth_key=(key)
28
+ super
29
+ validates_uniqueness_of :key, case_sensitive: false
30
+ end
31
+
32
+ def self.locate(search_hash)
33
+ where(search_hash).first
34
+ end
35
+
36
+ def persisted?
37
+ exists?
38
+ end
39
+
40
+ def save
41
+ save_changes
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bcrypt'
2
4
 
3
5
  module OmniAuth
@@ -1,33 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OmniAuth
2
4
  module Strategies
3
5
  # The identity strategy allows you to provide simple internal
4
6
  # user authentication using the same process flow that you
5
7
  # use for external OmniAuth providers.
6
8
  class Identity
9
+ DEFAULT_REGISTRATION_FIELDS = %i[password password_confirmation].freeze
7
10
  include OmniAuth::Strategy
8
-
9
11
  option :fields, %i[name email]
10
- option :enable_login, true # See #other_phase documentation
11
- option :on_login, nil
12
- option :on_registration, nil
13
- option :on_failed_registration, nil
14
- option :enable_registration, true
12
+
13
+ # Primary Feature Switches:
14
+ option :enable_registration, true # See #other_phase and #request_phase
15
+ option :enable_login, true # See #other_phase
16
+
17
+ # Customization Options:
18
+ option :on_login, nil # See #request_phase
19
+ option :on_validation, nil # See #registration_phase
20
+ option :on_registration, nil # See #registration_phase
21
+ option :on_failed_registration, nil # See #registration_phase
15
22
  option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
23
+ option :create_identity_link_text, 'Create an Identity'
24
+ option :registration_failure_message, 'One or more fields were invalid'
25
+ option :validation_failure_message, 'Validation failed'
26
+ option :title, 'Identity Verification' # Title for Login Form
27
+ option :registration_form_title, 'Register Identity' # Title for Registration Form
16
28
 
17
29
  def request_phase
18
30
  if options[:on_login]
19
31
  options[:on_login].call(env)
20
32
  else
21
- OmniAuth::Form.build(
22
- title: (options[:title] || 'Identity Verification'),
23
- url: callback_path
24
- ) do |f|
25
- f.text_field 'Login', 'auth_key'
26
- f.password_field 'Password', 'password'
27
- if options[:enable_registration]
28
- f.html "<p align='center'><a href='#{registration_path}'>Create an Identity</a></p>"
29
- end
30
- end.to_response
33
+ build_omniauth_login_form.to_response
31
34
  end
32
35
  end
33
36
 
@@ -58,36 +61,32 @@ module OmniAuth
58
61
  end
59
62
  end
60
63
 
61
- def registration_form
64
+ def registration_form(validation_message = nil)
62
65
  if options[:on_registration]
63
66
  options[:on_registration].call(env)
64
67
  else
65
- OmniAuth::Form.build(title: 'Register Identity') do |f|
66
- options[:fields].each do |field|
67
- f.text_field field.to_s.capitalize, field.to_s
68
- end
69
- f.password_field 'Password', 'password'
70
- f.password_field 'Confirm Password', 'password_confirmation'
71
- end.to_response
68
+ build_omniauth_registration_form(validation_message).to_response
72
69
  end
73
70
  end
74
71
 
75
72
  def registration_phase
76
- attributes = (options[:fields] + %i[password password_confirmation]).each_with_object({}) do |k, h|
73
+ attributes = (options[:fields] + DEFAULT_REGISTRATION_FIELDS).each_with_object({}) do |k, h|
77
74
  h[k] = request[k.to_s]
78
75
  end
79
76
  if model.respond_to?(:column_names) && model.column_names.include?('provider')
80
77
  attributes.reverse_merge!(provider: 'identity')
81
78
  end
82
- @identity = model.create(attributes)
83
- if @identity.persisted?
84
- env['PATH_INFO'] = callback_path
85
- callback_phase
86
- elsif options[:on_failed_registration]
79
+ @identity = model.new(attributes)
80
+ if saving_instead_of_creating?
87
81
  env['omniauth.identity'] = @identity
88
- options[:on_failed_registration].call(env)
82
+ if !validating? || valid?
83
+ @identity.save
84
+ registration_result
85
+ else
86
+ registration_failure(options[:validation_failure_message])
87
+ end
89
88
  else
90
- registration_form
89
+ deprecated_registration(attributes)
91
90
  end
92
91
  end
93
92
 
@@ -115,6 +114,80 @@ module OmniAuth
115
114
  def model
116
115
  options[:model] || ::Identity
117
116
  end
117
+
118
+ private
119
+
120
+ def build_omniauth_login_form
121
+ OmniAuth::Form.build(
122
+ title: options[:title],
123
+ url: callback_path
124
+ ) do |f|
125
+ f.text_field 'Login', 'auth_key'
126
+ f.password_field 'Password', 'password'
127
+ if options[:enable_registration]
128
+ f.html "<p align='center'><a href='#{registration_path}'>#{options[:create_identity_link_text]}</a></p>"
129
+ end
130
+ end
131
+ end
132
+
133
+ def build_omniauth_registration_form(validation_message)
134
+ OmniAuth::Form.build(title: options[:registration_form_title]) do |f|
135
+ f.html "<p style='color:red'>#{validation_message}</p>" if validation_message
136
+ options[:fields].each do |field|
137
+ f.text_field field.to_s.capitalize, field.to_s
138
+ end
139
+ f.password_field 'Password', 'password'
140
+ f.password_field 'Confirm Password', 'password_confirmation'
141
+ end
142
+ end
143
+
144
+ def saving_instead_of_creating?
145
+ @identity.respond_to?(:save) && @identity.respond_to?(:persisted?)
146
+ end
147
+
148
+ # Validates the model before it is persisted
149
+ #
150
+ # @return [truthy or falsey] :on_validation option is truthy or falsey
151
+ def validating?
152
+ !!options[:on_validation]
153
+ end
154
+
155
+ # Validates the model before it is persisted
156
+ #
157
+ # @return [true or false] result of :on_validation call
158
+ def valid?
159
+ # on_validation may run a Captcha or other validation mechanism
160
+ # Must return true when validation passes, false otherwise
161
+ !!options[:on_validation].call(env: env)
162
+ end
163
+
164
+ def registration_failure(message)
165
+ if options[:on_failed_registration]
166
+ options[:on_failed_registration].call(env)
167
+ else
168
+ registration_form(message)
169
+ end
170
+ end
171
+
172
+ def registration_result
173
+ if @identity.persisted?
174
+ env['PATH_INFO'] = callback_path
175
+ callback_phase
176
+ else
177
+ registration_failure(options[:registration_failure_message])
178
+ end
179
+ end
180
+
181
+ def deprecated_registration(attributes)
182
+ warn <<~CREATEDEP
183
+ [DEPRECATION] Please define '#{model.class}#save'.
184
+ Behavior based on '#{model.class}.create' will be removed in omniauth-identity v4.0.
185
+ See lib/omniauth/identity/model.rb
186
+ CREATEDEP
187
+ @identity = model.create(attributes)
188
+ env['omniauth.identity'] = @identity
189
+ registration_result
190
+ end
118
191
  end
119
192
  end
120
193
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ExampleModel
2
4
  include OmniAuth::Identity::Model
3
5
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe(OmniAuth::Identity::Models::ActiveRecord, db: true) do
2
4
  describe 'model', type: :model do
3
5
  subject(:model_klass) do
4
6
  AnonymousActiveRecord.generate(
5
7
  parent_klass: 'OmniAuth::Identity::Models::ActiveRecord',
6
- columns: %w[name provider],
8
+ columns: OmniAuth::Identity::Model::SCHEMA_ATTRIBUTES | %w[provider password_digest],
7
9
  connection_params: { adapter: 'sqlite3', encoding: 'utf8', database: ':memory:' }
8
10
  ) do
9
11
  def flower
@@ -1,10 +1,14 @@
1
- RSpec.describe(OmniAuth::Identity::Models::CouchPotatoModule, db: true) do
2
- class CouchPotatoTestIdentity
3
- include CouchPotato::Persistence
4
- include OmniAuth::Identity::Models::CouchPotatoModule
5
- auth_key :ham_sandwich
6
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'couch_potato'
7
4
 
5
+ class CouchPotatoTestIdentity
6
+ include CouchPotato::Persistence
7
+ include OmniAuth::Identity::Models::CouchPotatoModule
8
+ auth_key :ham_sandwich
9
+ end
10
+
11
+ RSpec.describe(OmniAuth::Identity::Models::CouchPotatoModule, db: true) do
8
12
  describe 'model', type: :model do
9
13
  subject { CouchPotatoTestIdentity }
10
14
 
@@ -1,11 +1,15 @@
1
- RSpec.describe(OmniAuth::Identity::Models::Mongoid, db: true) do
2
- class MongoidTestIdentity
3
- include Mongoid::Document
4
- include OmniAuth::Identity::Models::Mongoid
5
- auth_key :ham_sandwich
6
- store_in database: 'db1', collection: 'mongoid_test_identities', client: 'secondary'
7
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid'
8
4
 
5
+ class MongoidTestIdentity
6
+ include Mongoid::Document
7
+ include OmniAuth::Identity::Models::Mongoid
8
+ auth_key :ham_sandwich
9
+ store_in database: 'db1', collection: 'mongoid_test_identities', client: 'secondary'
10
+ end
11
+
12
+ RSpec.describe(OmniAuth::Identity::Models::Mongoid, db: true) do
9
13
  describe 'model', type: :model do
10
14
  subject { MongoidTestIdentity }
11
15
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nobrainer'
4
+
5
+ class NobrainerTestIdentity
6
+ include NoBrainer::Document
7
+ include OmniAuth::Identity::Models::NoBrainer
8
+ auth_key :ham_sandwich
9
+ end
10
+
11
+ RSpec.describe(OmniAuth::Identity::Models::NoBrainer, db: true) do
12
+ it 'delegates locate to the where query method' do
13
+ allow(NobrainerTestIdentity).to receive(:where).with('ham_sandwich' => 'open faced',
14
+ 'category' => 'sandwiches').and_return(['wakka'])
15
+ expect(NobrainerTestIdentity.locate('ham_sandwich' => 'open faced', 'category' => 'sandwiches')).to eq('wakka')
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sequel'
4
+ # Connect to an in-memory sqlite3 database.
5
+ 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
+
12
+ class SequelTestIdentity < Sequel::Model
13
+ include OmniAuth::Identity::Models::Sequel
14
+ auth_key :ham_sandwich
15
+ end
16
+
17
+ RSpec.describe(OmniAuth::Identity::Models::Sequel, db: true) do
18
+ it 'delegates locate to the where query method' do
19
+ allow(SequelTestIdentity).to receive(:where).with('ham_sandwich' => 'open faced',
20
+ 'category' => 'sandwiches').and_return(['wakka'])
21
+ expect(SequelTestIdentity.locate('ham_sandwich' => 'open faced', 'category' => 'sandwiches')).to eq('wakka')
22
+ end
23
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class HasTheMethod
2
4
  def self.has_secure_password; end
3
5
  end
@@ -8,17 +10,17 @@ end
8
10
  RSpec.describe OmniAuth::Identity::SecurePassword do
9
11
  it 'extends with the class methods if it does not have the method' do
10
12
  expect(DoesNotHaveTheMethod).to receive(:extend).with(OmniAuth::Identity::SecurePassword::ClassMethods)
11
- DoesNotHaveTheMethod.include OmniAuth::Identity::SecurePassword
13
+ DoesNotHaveTheMethod.include described_class
12
14
  end
13
15
 
14
16
  it 'does not extend if the method is already defined' do
15
17
  expect(HasTheMethod).not_to receive(:extend)
16
- HasTheMethod.include OmniAuth::Identity::SecurePassword
18
+ HasTheMethod.include described_class
17
19
  end
18
20
 
19
21
  it 'responds to has_secure_password afterwards' do
20
22
  [HasTheMethod, DoesNotHaveTheMethod].each do |klass|
21
- klass.send(:include, OmniAuth::Identity::SecurePassword)
23
+ klass.send(:include, described_class)
22
24
  expect(klass).to be_respond_to(:has_secure_password)
23
25
  end
24
26
  end
@@ -1,12 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.describe OmniAuth::Strategies::Identity do
2
4
  attr_accessor :app
3
5
 
4
- let(:auth_hash) { last_response.headers['env']['omniauth.auth'] }
5
- let(:identity_hash) { last_response.headers['env']['omniauth.identity'] }
6
+ let(:env_hash) { last_response.headers['env'] }
7
+ let(:auth_hash) { env_hash['omniauth.auth'] }
8
+ let(:identity_hash) { env_hash['omniauth.identity'] }
6
9
  let(:identity_options) { {} }
7
10
  let(:anon_ar) do
8
11
  AnonymousActiveRecord.generate(
9
- columns: %w[name provider],
12
+ parent_klass: 'OmniAuth::Identity::Models::ActiveRecord',
13
+ columns: OmniAuth::Identity::Model::SCHEMA_ATTRIBUTES | %w[provider password_digest],
10
14
  connection_params: { adapter: 'sqlite3', encoding: 'utf8', database: ':memory:' }
11
15
  ) do
12
16
  def balloon
@@ -190,7 +194,7 @@ RSpec.describe OmniAuth::Strategies::Identity do
190
194
  end
191
195
  end
192
196
 
193
- context 'with successful creation' do
197
+ context 'with good identity' do
194
198
  let(:properties) do
195
199
  {
196
200
  name: 'Awesome Dude',
@@ -201,20 +205,71 @@ RSpec.describe OmniAuth::Strategies::Identity do
201
205
  }
202
206
  end
203
207
 
204
- before do
205
- allow(anon_ar).to receive('auth_key').and_return('email')
206
- m = double(uid: 'abc', name: 'Awesome Dude', email: 'awesome@example.com',
207
- info: { name: 'DUUUUDE!' }, persisted?: true)
208
- expect(anon_ar).to receive(:create).with(properties).and_return(m)
209
- end
210
-
211
208
  it 'sets the auth hash' do
212
209
  post '/auth/identity/register', properties
213
- expect(auth_hash['uid']).to eq('abc')
210
+ expect(auth_hash['uid']).to match(/\d+/)
211
+ expect(auth_hash['provider']).to eq('identity')
212
+ end
213
+
214
+ context 'with on_validation proc' do
215
+ let(:identity_options) do
216
+ { model: anon_ar, on_validation: on_validation_proc }
217
+ end
218
+ let(:on_validation_proc) do
219
+ lambda { |_env|
220
+ false
221
+ }
222
+ end
223
+
224
+ context 'when validation fails' do
225
+ it 'does not set the env hash' do
226
+ post '/auth/identity/register', properties
227
+ expect(env_hash).to eq(nil)
228
+ end
229
+
230
+ it 'renders registration form' do
231
+ post '/auth/identity/register', properties
232
+ expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
233
+ end
234
+
235
+ it 'displays validation failure message' do
236
+ post '/auth/identity/register', properties
237
+ expect(last_response.body).to be_include(described_class.default_options[:validation_failure_message])
238
+ end
239
+ end
240
+
241
+ context 'when validation succeeds' do
242
+ let(:on_validation_proc) do
243
+ lambda { |_env|
244
+ true
245
+ }
246
+ end
247
+
248
+ it 'sets the auth hash' do
249
+ post '/auth/identity/register', properties
250
+ expect(auth_hash['uid']).to match(/\d+/)
251
+ expect(auth_hash['provider']).to eq('identity')
252
+ end
253
+
254
+ it 'does not render registration form' do
255
+ post '/auth/identity/register', properties
256
+ expect(last_response.body).not_to be_include(described_class.default_options[:registration_form_title])
257
+ end
258
+
259
+ it 'does not display validation failure message' do
260
+ post '/auth/identity/register', properties
261
+ expect(last_response.body).not_to be_include(described_class.default_options[:validation_failure_message])
262
+ end
263
+
264
+ it 'does not display registration failure message' do
265
+ post '/auth/identity/register', properties
266
+ expect(last_response.body).not_to be_include(described_class.default_options[:registration_failure_message])
267
+ end
268
+ end
214
269
  end
215
270
  end
216
271
 
217
- context 'with invalid identity' do
272
+ context 'with bad identity' do
218
273
  let(:properties) do
219
274
  {
220
275
  name: 'Awesome Dude',
@@ -224,16 +279,17 @@ RSpec.describe OmniAuth::Strategies::Identity do
224
279
  provider: 'identity'
225
280
  }
226
281
  end
227
- let(:invalid_identity) { double(persisted?: false) }
282
+ let(:invalid_identity) { double(persisted?: false, save: false) }
228
283
 
229
284
  before do
230
- expect(anon_ar).to receive(:create).with(properties).and_return(invalid_identity)
285
+ expect(anon_ar).to receive(:new).with(properties).and_return(invalid_identity)
231
286
  end
232
287
 
233
288
  context 'default' do
234
289
  it 'shows registration form' do
235
290
  post '/auth/identity/register', properties
236
291
  expect(last_response.body).to be_include('Register Identity')
292
+ expect(last_response.body).to be_include('One or more fields were invalid')
237
293
  end
238
294
  end
239
295
 
@@ -248,6 +304,63 @@ RSpec.describe OmniAuth::Strategies::Identity do
248
304
  post '/auth/identity/register', properties
249
305
  expect(identity_hash).to eq(invalid_identity)
250
306
  expect(last_response.body).to be_include("FAIL'DOH!")
307
+ expect(last_response.body).not_to be_include('One or more fields were invalid')
308
+ end
309
+ end
310
+
311
+ context 'with on_validation proc' do
312
+ let(:identity_options) do
313
+ { model: anon_ar, on_validation: on_validation_proc }
314
+ end
315
+ let(:on_validation_proc) do
316
+ lambda { |_env|
317
+ false
318
+ }
319
+ end
320
+
321
+ context 'when validation fails' do
322
+ it 'does not set the env hash' do
323
+ post '/auth/identity/register', properties
324
+ expect(env_hash).to eq(nil)
325
+ end
326
+
327
+ it 'renders registration form' do
328
+ post '/auth/identity/register', properties
329
+ expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
330
+ end
331
+
332
+ it 'displays validation failure message' do
333
+ post '/auth/identity/register', properties
334
+ expect(last_response.body).to be_include(described_class.default_options[:validation_failure_message])
335
+ end
336
+ end
337
+
338
+ context 'when validation succeeds' do
339
+ let(:on_validation_proc) do
340
+ lambda { |_env|
341
+ true
342
+ }
343
+ end
344
+
345
+ it 'does not set the env hash' do
346
+ post '/auth/identity/register', properties
347
+ expect(env_hash).to eq(nil)
348
+ end
349
+
350
+ it 'renders registration form' do
351
+ post '/auth/identity/register', properties
352
+ expect(last_response.body).to be_include(described_class.default_options[:registration_form_title])
353
+ end
354
+
355
+ it 'does not display validation failure message' do
356
+ post '/auth/identity/register', properties
357
+ expect(last_response.body).not_to be_include(described_class.default_options[:validation_failure_message])
358
+ end
359
+
360
+ it 'display registration failure message' do
361
+ post '/auth/identity/register', properties
362
+ expect(last_response.body).to be_include(described_class.default_options[:registration_failure_message])
363
+ end
251
364
  end
252
365
  end
253
366
  end