Skip to content

Commit 38fa977

Browse files
authored
Merge pull request #108 from omniauth/define-api
Define API for classes utilizing OmniAuth::Identity::Model
2 parents dd2c7b3 + a972ef9 commit 38fa977

File tree

12 files changed

+180
-59
lines changed

12 files changed

+180
-59
lines changed

.rubocop.yml

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require:
77
- 'rubocop-performance'
88
- 'rubocop-rake'
99
- 'rubocop-rspec'
10+
- 'rubocop-sequel'
1011

1112
AllCops:
1213
NewCops: enable
@@ -25,3 +26,9 @@ Metrics/BlockLength:
2526
- shared_examples_for
2627
- namespace
2728
- draw
29+
30+
Sequel/SaveChanges:
31+
Enabled: false
32+
33+
Lint/UselessMethodDefinition:
34+
Enabled: false

.rubocop_todo.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ Metrics/AbcSize:
4040
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
4141
# IgnoredMethods: refine
4242
Metrics/BlockLength:
43-
Max: 27
43+
Max: 28
4444

4545
# Offense count: 1
4646
# Configuration parameters: CountComments, CountAsOne.
4747
Metrics/ClassLength:
48-
Max: 117
48+
Max: 140
4949

5050
# Offense count: 1
5151
# Configuration parameters: IgnoredMethods.

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
## [3.0.5] - 2021-03-19
12+
13+
### Fixed
14+
15+
- Fix breaking changes introduced by [#86's](https://github.com/omniauth/omniauth-identity/pull/86) introduction of `:on_validation`
16+
17+
### Added
18+
19+
- Define `#save`, `#persisted?` and `::create` on `Omniauth::Identity::Model`
20+
- Add `@since` YARD tags to interface methods
21+
- Refactor `Omniauth::Strategies::Identity.registration_phase` to support `Omniauth::Identity::Model`-inheriting classes that do not define `#save`.
22+
- This support will be dropped in v4.0.
23+
1124
## [3.0.4] - 2021-02-14
1225

1326
### Added

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ group :development, :test do
3939
gem 'rubocop-performance', platform: :mri
4040
gem 'rubocop-rake', platform: :mri
4141
gem 'rubocop-rspec', platform: :mri
42+
gem 'rubocop-sequel', platform: :mri
4243

4344
gem 'simplecov', '~> 0.21', platform: :mri
4445
end

README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -249,21 +249,21 @@ always break things!
249249

250250
From the code - here are the options we have for you, a couple of which are documented above, and the rest are documented... in the specs we hope!?
251251
```
252-
option :fields, %i[name email]
252+
option :fields, %i[name email]
253253
254254
# Primary Feature Switches:
255-
option :enable_registration, true # See #other_phase and #request_phase
256-
option :enable_login, true # See #other_phase
255+
option :enable_registration, true # See #other_phase and #request_phase
256+
option :enable_login, true # See #other_phase
257257
258258
# Customization Options:
259-
option :on_login, nil # See #request_phase
260-
option :on_validation, nil # See #registration_phase
261-
option :on_registration, nil # See #registration_phase
262-
option :on_failed_registration, nil # See #registration_phase
263-
option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
259+
option :on_login, nil # See #request_phase
260+
option :on_validation, nil # See #registration_phase
261+
option :on_registration, nil # See #registration_phase
262+
option :on_failed_registration, nil # See #registration_phase
263+
option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
264264
```
265265

266-
Please contribute some documentation if you have the gumption! The maintainer's time is limited, and sometimes the authors of PRs with new options don't update the _this_ readme. 😭
266+
Please contribute some documentation if you have the gumption! The maintainer's time is limited, and sometimes the authors of PRs with new options don't update the _this_ readme. 😭
267267

268268
## License
269269

lib/omniauth-identity/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
module OmniAuth
44
module Identity
5-
VERSION = '3.0.4'
5+
VERSION = '3.0.5'
66
end
77
end

lib/omniauth/identity/model.rb

+81-27
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,31 @@ module OmniAuth
44
module Identity
55
# This module provides an includable interface for implementing the
66
# necessary API for OmniAuth Identity to properly locate identities
7-
# and provide all necessary information. All methods marked as
8-
# abstract must be implemented in the including class for things to
9-
# work properly.
7+
# and provide all necessary information.
8+
#
9+
# All methods marked as abstract must be implemented in the
10+
# including class for things to work properly.
11+
#
12+
### Singleton API
13+
#
14+
# * locate(key)
15+
# * create(*args) - Deprecated in v3.0.5; Will be removed in v4.0
16+
#
17+
### Instance API
18+
#
19+
# * save
20+
# * persisted?
21+
# * authenticate(password)
22+
#
1023
module Model
24+
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
25+
1126
def self.included(base)
1227
base.extend ClassMethods
1328
end
1429

1530
module ClassMethods
16-
# Locate an identity given its unique login key.
17-
#
18-
# @abstract
19-
# @param [String] key The unique login key.
20-
# @return [Model] An instance of the identity model class.
21-
def locate(key)
22-
raise NotImplementedError
23-
end
31+
extend Gem::Deprecate
2432

2533
# Authenticate a user with the given key and password.
2634
#
@@ -43,6 +51,53 @@ def auth_key(method = false)
4351

4452
@auth_key || 'email'
4553
end
54+
55+
# Persists a new Identity object to the ORM.
56+
# Defaults to calling super. Override as needed per ORM.
57+
#
58+
# @deprecated v4.0 will begin using {#new} with {#save} instead.
59+
# @abstract
60+
# @param [Hash] args Attributes of the new instance.
61+
# @return [Model] An instance of the identity model class.
62+
# @since 3.0.5
63+
def create(*args)
64+
raise NotImplementedError unless defined?(super)
65+
66+
super
67+
end
68+
69+
# Locate an identity given its unique login key.
70+
#
71+
# @abstract
72+
# @param [String] key The unique login key.
73+
# @return [Model] An instance of the identity model class.
74+
def locate(key)
75+
raise NotImplementedError
76+
end
77+
end
78+
79+
# Persists a new Identity object to the ORM.
80+
# Default raises an error. Override as needed per ORM.
81+
#
82+
# @abstract
83+
# @return [Model] An instance of the identity model class.
84+
# @since 3.0.5
85+
def save
86+
raise NotImplementedError unless defined?(super)
87+
88+
super
89+
end
90+
91+
# Checks if the Identity object is persisted in the ORM.
92+
# Defaults to calling super. Override as needed per ORM.
93+
#
94+
# @abstract
95+
# @return [true or false] true if object exists, false if not.
96+
# @since 3.0.5
97+
def persisted?
98+
raise NotImplementedError unless defined?(super)
99+
100+
super
46101
end
47102

48103
# Returns self if the provided password is correct, false
@@ -55,22 +110,6 @@ def authenticate(password)
55110
raise NotImplementedError
56111
end
57112

58-
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
59-
# A hash of as much of the standard OmniAuth schema as is stored
60-
# in this particular model. By default, this will call instance
61-
# methods for each of the attributes it needs in turn, ignoring
62-
# any for which `#respond_to?` is `false`.
63-
#
64-
# If `first_name`, `nickname`, and/or `last_name` is provided but
65-
# `name` is not, it will be automatically calculated.
66-
#
67-
# @return [Hash] A string-keyed hash of user information.
68-
def info
69-
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
70-
hash[attribute] = send(attribute) if respond_to?(attribute)
71-
end
72-
end
73-
74113
# An identifying string that must be globally unique to the
75114
# application. Defaults to stringifying the `id` method.
76115
#
@@ -113,6 +152,21 @@ def auth_key=(value)
113152
raise NotImplementedError
114153
end
115154
end
155+
156+
# A hash of as much of the standard OmniAuth schema as is stored
157+
# in this particular model. By default, this will call instance
158+
# methods for each of the attributes it needs in turn, ignoring
159+
# any for which `#respond_to?` is `false`.
160+
#
161+
# If `first_name`, `nickname`, and/or `last_name` is provided but
162+
# `name` is not, it will be automatically calculated.
163+
#
164+
# @return [Hash] A string-keyed hash of user information.
165+
def info
166+
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
167+
hash[attribute] = send(attribute) if respond_to?(attribute)
168+
end
169+
end
116170
end
117171
end
118172
end

lib/omniauth/identity/models/couch_potato.rb

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module OmniAuth
66
module Identity
77
module Models
88
# can not be named CouchPotato since there is a class with that name
9+
# NOTE: CouchPotato is based on ActiveModel.
910
module CouchPotatoModule
1011
def self.included(base)
1112
base.class_eval do

lib/omniauth/identity/models/mongoid.rb

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
module OmniAuth
66
module Identity
77
module Models
8+
# NOTE: Mongoid is based on ActiveModel.
89
module Mongoid
910
def self.included(base)
1011
base.class_eval do

lib/omniauth/identity/models/no_brainer.rb

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module OmniAuth
66
module Identity
77
module Models
88
# http://nobrainer.io/ an ORM for RethinkDB
9+
# NOTE: NoBrainer is based on ActiveModel.
910
module NoBrainer
1011
def self.included(base)
1112
base.class_eval do

lib/omniauth/identity/models/sequel.rb

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module OmniAuth
66
module Identity
77
module Models
88
# 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`
912
module Sequel
1013
def self.included(base)
1114
base.class_eval do
@@ -29,6 +32,14 @@ def self.auth_key=(key)
2932
def self.locate(search_hash)
3033
where(search_hash).first
3134
end
35+
36+
def persisted?
37+
exists?
38+
end
39+
40+
def save
41+
save_changes
42+
end
3243
end
3344
end
3445
end

lib/omniauth/strategies/identity.rb

+52-20
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ module Strategies
66
# user authentication using the same process flow that you
77
# use for external OmniAuth providers.
88
class Identity
9+
DEFAULT_REGISTRATION_FIELDS = %i[password password_confirmation].freeze
910
include OmniAuth::Strategy
10-
1111
option :fields, %i[name email]
1212

1313
# Primary Feature Switches:
@@ -65,29 +65,23 @@ def registration_form(validation_message = nil)
6565
end
6666

6767
def registration_phase
68-
attributes = (options[:fields] + %i[password password_confirmation]).each_with_object({}) do |k, h|
68+
attributes = (options[:fields] + DEFAULT_REGISTRATION_FIELDS).each_with_object({}) do |k, h|
6969
h[k] = request[k.to_s]
7070
end
7171
if model.respond_to?(:column_names) && model.column_names.include?('provider')
7272
attributes.reverse_merge!(provider: 'identity')
7373
end
74-
@identity = model.new(attributes)
75-
76-
# on_validation may run a Captcha or other validation mechanism
77-
# Must return true when validation passes, false otherwise
78-
if options[:on_validation] && !options[:on_validation].call(env: env)
79-
if options[:on_failed_registration]
80-
env['omniauth.identity'] = @identity
81-
options[:on_failed_registration].call(env)
74+
if saving_instead_of_creating?
75+
@identity = model.new(attributes)
76+
env['omniauth.identity'] = @identity
77+
if !validating? || valid?
78+
@identity.save
79+
registration_result
8280
else
83-
validation_message = 'Validation failed'
84-
registration_form(validation_message)
81+
registration_failure('Validation failed')
8582
end
86-
elsif @identity.save && @identity.persisted?
87-
env['PATH_INFO'] = callback_path
88-
callback_phase
8983
else
90-
show_custom_options_or_default
84+
deprecated_registration(attributes)
9185
end
9286
end
9387

@@ -142,15 +136,53 @@ def build_omniauth_registration_form(validation_message)
142136
end
143137
end
144138

145-
def show_custom_options_or_default
139+
def saving_instead_of_creating?
140+
model.respond_to?(:save) && model.respond_to?(:persisted?)
141+
end
142+
143+
# Validates the model before it is persisted
144+
#
145+
# @return [truthy or falsey] :on_validation option is truthy or falsey
146+
def validating?
147+
options[:on_validation]
148+
end
149+
150+
# Validates the model before it is persisted
151+
#
152+
# @return [true or false] result of :on_validation call
153+
def valid?
154+
# on_validation may run a Captcha or other validation mechanism
155+
# Must return true when validation passes, false otherwise
156+
!!options[:on_validation].call(env: env)
157+
end
158+
159+
def registration_failure(message)
146160
if options[:on_failed_registration]
147-
env['omniauth.identity'] = @identity
148161
options[:on_failed_registration].call(env)
149162
else
150-
validation_message = 'One or more fields were invalid'
151-
registration_form(validation_message)
163+
registration_form(message)
164+
end
165+
end
166+
167+
def registration_result
168+
if @identity.persisted?
169+
env['PATH_INFO'] = callback_path
170+
callback_phase
171+
else
172+
registration_failure('One or more fields were invalid')
152173
end
153174
end
175+
176+
def deprecated_registration(attributes)
177+
warn <<~CREATEDEP
178+
[DEPRECATION] Please define '#{model.class}#save'.
179+
Behavior based on '#{model.class}.create' will be removed in omniauth-identity v4.0.
180+
See lib/omniauth/identity/model.rb
181+
CREATEDEP
182+
@identity = model.create(attributes)
183+
env['omniauth.identity'] = @identity
184+
registration_result
185+
end
154186
end
155187
end
156188
end

0 commit comments

Comments
 (0)