Skip to content

Define API for classes utilizing OmniAuth::Identity::Model #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require:
- 'rubocop-performance'
- 'rubocop-rake'
- 'rubocop-rspec'
- 'rubocop-sequel'

AllCops:
NewCops: enable
Expand All @@ -25,3 +26,9 @@ Metrics/BlockLength:
- shared_examples_for
- namespace
- draw

Sequel/SaveChanges:
Enabled: false

Lint/UselessMethodDefinition:
Enabled: false
4 changes: 2 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ Metrics/AbcSize:
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
# IgnoredMethods: refine
Metrics/BlockLength:
Max: 27
Max: 28

# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 117
Max: 140

# Offense count: 1
# Configuration parameters: IgnoredMethods.
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.0.5] - 2021-03-19

### Fixed

- Fix breaking changes introduced by [#86's](https://github.com/omniauth/omniauth-identity/pull/86) introduction of `:on_validation`

### Added

- Define `#save`, `#persisted?` and `::create` on `Omniauth::Identity::Model`
- Add `@since` YARD tags to interface methods
- Refactor `Omniauth::Strategies::Identity.registration_phase` to support `Omniauth::Identity::Model`-inheriting classes that do not define `#save`.
- This support will be dropped in v4.0.

## [3.0.4] - 2021-02-14

### Added
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ group :development, :test do
gem 'rubocop-performance', platform: :mri
gem 'rubocop-rake', platform: :mri
gem 'rubocop-rspec', platform: :mri
gem 'rubocop-sequel', platform: :mri

gem 'simplecov', '~> 0.21', platform: :mri
end
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,21 +249,21 @@ always break things!

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!?
```
option :fields, %i[name email]
option :fields, %i[name email]

# Primary Feature Switches:
option :enable_registration, true # See #other_phase and #request_phase
option :enable_login, true # See #other_phase
option :enable_registration, true # See #other_phase and #request_phase
option :enable_login, true # See #other_phase

# Customization Options:
option :on_login, nil # See #request_phase
option :on_validation, nil # See #registration_phase
option :on_registration, nil # See #registration_phase
option :on_failed_registration, nil # See #registration_phase
option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
option :on_login, nil # See #request_phase
option :on_validation, nil # See #registration_phase
option :on_registration, nil # See #registration_phase
option :on_failed_registration, nil # See #registration_phase
option :locate_conditions, ->(req) { { model.auth_key => req['auth_key'] } }
```

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. 😭
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. 😭

## License

Expand Down
2 changes: 1 addition & 1 deletion lib/omniauth-identity/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module OmniAuth
module Identity
VERSION = '3.0.4'
VERSION = '3.0.5'
end
end
108 changes: 81 additions & 27 deletions lib/omniauth/identity/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,31 @@ module OmniAuth
module Identity
# This module provides an includable interface for implementing the
# necessary API for OmniAuth Identity to properly locate identities
# and provide all necessary information. All methods marked as
# abstract must be implemented in the including class for things to
# work properly.
# and provide all necessary information.
#
# All methods marked as abstract must be implemented in the
# including class for things to work properly.
#
### Singleton API
#
# * locate(key)
# * create(*args) - Deprecated in v3.0.5; Will be removed in v4.0
#
### Instance API
#
# * save
# * persisted?
# * authenticate(password)
#
module Model
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze

def self.included(base)
base.extend ClassMethods
end

module ClassMethods
# Locate an identity given its unique login key.
#
# @abstract
# @param [String] key The unique login key.
# @return [Model] An instance of the identity model class.
def locate(key)
raise NotImplementedError
end
extend Gem::Deprecate

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

@auth_key || 'email'
end

# Persists a new Identity object to the ORM.
# Defaults to calling super. Override as needed per ORM.
#
# @deprecated v4.0 will begin using {#new} with {#save} instead.
# @abstract
# @param [Hash] args Attributes of the new instance.
# @return [Model] An instance of the identity model class.
# @since 3.0.5
def create(*args)
raise NotImplementedError unless defined?(super)

super
end

# Locate an identity given its unique login key.
#
# @abstract
# @param [String] key The unique login key.
# @return [Model] An instance of the identity model class.
def locate(key)
raise NotImplementedError
end
end

# Persists a new Identity object to the ORM.
# Default raises an error. Override as needed per ORM.
#
# @abstract
# @return [Model] An instance of the identity model class.
# @since 3.0.5
def save
raise NotImplementedError unless defined?(super)

super
end

# Checks if the Identity object is persisted in the ORM.
# Defaults to calling super. Override as needed per ORM.
#
# @abstract
# @return [true or false] true if object exists, false if not.
# @since 3.0.5
def persisted?
raise NotImplementedError unless defined?(super)

super
end

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

SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
# A hash of as much of the standard OmniAuth schema as is stored
# in this particular model. By default, this will call instance
# methods for each of the attributes it needs in turn, ignoring
# any for which `#respond_to?` is `false`.
#
# If `first_name`, `nickname`, and/or `last_name` is provided but
# `name` is not, it will be automatically calculated.
#
# @return [Hash] A string-keyed hash of user information.
def info
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
hash[attribute] = send(attribute) if respond_to?(attribute)
end
end

# An identifying string that must be globally unique to the
# application. Defaults to stringifying the `id` method.
#
Expand Down Expand Up @@ -113,6 +152,21 @@ def auth_key=(value)
raise NotImplementedError
end
end

# A hash of as much of the standard OmniAuth schema as is stored
# in this particular model. By default, this will call instance
# methods for each of the attributes it needs in turn, ignoring
# any for which `#respond_to?` is `false`.
#
# If `first_name`, `nickname`, and/or `last_name` is provided but
# `name` is not, it will be automatically calculated.
#
# @return [Hash] A string-keyed hash of user information.
def info
SCHEMA_ATTRIBUTES.each_with_object({}) do |attribute, hash|
hash[attribute] = send(attribute) if respond_to?(attribute)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/omniauth/identity/models/couch_potato.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module OmniAuth
module Identity
module Models
# can not be named CouchPotato since there is a class with that name
# NOTE: CouchPotato is based on ActiveModel.
module CouchPotatoModule
def self.included(base)
base.class_eval do
Expand Down
1 change: 1 addition & 0 deletions lib/omniauth/identity/models/mongoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module OmniAuth
module Identity
module Models
# NOTE: Mongoid is based on ActiveModel.
module Mongoid
def self.included(base)
base.class_eval do
Expand Down
1 change: 1 addition & 0 deletions lib/omniauth/identity/models/no_brainer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module OmniAuth
module Identity
module Models
# http://nobrainer.io/ an ORM for RethinkDB
# NOTE: NoBrainer is based on ActiveModel.
module NoBrainer
def self.included(base)
base.class_eval do
Expand Down
11 changes: 11 additions & 0 deletions lib/omniauth/identity/models/sequel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ module OmniAuth
module Identity
module Models
# http://sequel.jeremyevans.net/ an SQL ORM
# NOTE: Sequel is *not* based on ActiveModel, but supports the API we need, except for `persisted`:
# * create
# * save, but save is deprecated in favor of `save_changes`
module Sequel
def self.included(base)
base.class_eval do
Expand All @@ -29,6 +32,14 @@ def self.auth_key=(key)
def self.locate(search_hash)
where(search_hash).first
end

def persisted?
exists?
end

def save
save_changes
end
end
end
end
Expand Down
72 changes: 52 additions & 20 deletions lib/omniauth/strategies/identity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ module Strategies
# user authentication using the same process flow that you
# use for external OmniAuth providers.
class Identity
DEFAULT_REGISTRATION_FIELDS = %i[password password_confirmation].freeze
include OmniAuth::Strategy

option :fields, %i[name email]

# Primary Feature Switches:
Expand Down Expand Up @@ -65,29 +65,23 @@ def registration_form(validation_message = nil)
end

def registration_phase
attributes = (options[:fields] + %i[password password_confirmation]).each_with_object({}) do |k, h|
attributes = (options[:fields] + DEFAULT_REGISTRATION_FIELDS).each_with_object({}) do |k, h|
h[k] = request[k.to_s]
end
if model.respond_to?(:column_names) && model.column_names.include?('provider')
attributes.reverse_merge!(provider: 'identity')
end
@identity = model.new(attributes)

# on_validation may run a Captcha or other validation mechanism
# Must return true when validation passes, false otherwise
if options[:on_validation] && !options[:on_validation].call(env: env)
if options[:on_failed_registration]
env['omniauth.identity'] = @identity
options[:on_failed_registration].call(env)
if saving_instead_of_creating?
@identity = model.new(attributes)
env['omniauth.identity'] = @identity
if !validating? || valid?
@identity.save
registration_result
else
validation_message = 'Validation failed'
registration_form(validation_message)
registration_failure('Validation failed')
end
elsif @identity.save && @identity.persisted?
env['PATH_INFO'] = callback_path
callback_phase
else
show_custom_options_or_default
deprecated_registration(attributes)
end
end

Expand Down Expand Up @@ -142,15 +136,53 @@ def build_omniauth_registration_form(validation_message)
end
end

def show_custom_options_or_default
def saving_instead_of_creating?
model.respond_to?(:save) && model.respond_to?(:persisted?)
end

# Validates the model before it is persisted
#
# @return [truthy or falsey] :on_validation option is truthy or falsey
def validating?
options[:on_validation]
end

# Validates the model before it is persisted
#
# @return [true or false] result of :on_validation call
def valid?
# on_validation may run a Captcha or other validation mechanism
# Must return true when validation passes, false otherwise
!!options[:on_validation].call(env: env)
end

def registration_failure(message)
if options[:on_failed_registration]
env['omniauth.identity'] = @identity
options[:on_failed_registration].call(env)
else
validation_message = 'One or more fields were invalid'
registration_form(validation_message)
registration_form(message)
end
end

def registration_result
if @identity.persisted?
env['PATH_INFO'] = callback_path
callback_phase
else
registration_failure('One or more fields were invalid')
end
end

def deprecated_registration(attributes)
warn <<~CREATEDEP
[DEPRECATION] Please define '#{model.class}#save'.
Behavior based on '#{model.class}.create' will be removed in omniauth-identity v4.0.
See lib/omniauth/identity/model.rb
CREATEDEP
@identity = model.create(attributes)
env['omniauth.identity'] = @identity
registration_result
end
end
end
end