omniauth-identity2 2.0
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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile +13 -0
- data/Guardfile +10 -0
- data/LICENSE +21 -0
- data/README.markdown +240 -0
- data/Rakefile +9 -0
- data/lib/omniauth-identity.rb +2 -0
- data/lib/omniauth-identity/version.rb +5 -0
- data/lib/omniauth/identity.rb +18 -0
- data/lib/omniauth/identity/model.rb +116 -0
- data/lib/omniauth/identity/models/active_record.rb +24 -0
- data/lib/omniauth/identity/models/couch_potato.rb +31 -0
- data/lib/omniauth/identity/models/data_mapper.rb +32 -0
- data/lib/omniauth/identity/models/mongoid.rb +33 -0
- data/lib/omniauth/identity/secure_password.rb +78 -0
- data/lib/omniauth/strategies/identity.rb +103 -0
- data/omniauth-identity.gemspec +33 -0
- data/spec/omniauth/identity/model_spec.rb +121 -0
- data/spec/omniauth/identity/models/active_record_spec.rb +16 -0
- data/spec/omniauth/identity/models/couch_potato_spec.rb +16 -0
- data/spec/omniauth/identity/models/data_mapper_spec.rb +24 -0
- data/spec/omniauth/identity/models/mongoid_spec.rb +23 -0
- data/spec/omniauth/identity/secure_password_spec.rb +25 -0
- data/spec/omniauth/strategies/identity_spec.rb +141 -0
- data/spec/spec_helper.rb +20 -0
- metadata +264 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Identity
|
5
|
+
module Models
|
6
|
+
class ActiveRecord < ::ActiveRecord::Base
|
7
|
+
include OmniAuth::Identity::Model
|
8
|
+
include OmniAuth::Identity::SecurePassword
|
9
|
+
|
10
|
+
self.abstract_class = true
|
11
|
+
has_secure_password
|
12
|
+
|
13
|
+
def self.auth_key=(key)
|
14
|
+
super
|
15
|
+
validates_uniqueness_of key, :case_sensitive => false
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.locate(search_hash)
|
19
|
+
where(search_hash).first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'couch_potato'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Identity
|
5
|
+
module Models
|
6
|
+
# can not be named CouchPotato since there is a class with that name
|
7
|
+
module CouchPotatoModule
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
|
11
|
+
base.class_eval do
|
12
|
+
|
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,32 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-validations'
|
3
|
+
|
4
|
+
module OmniAuth
|
5
|
+
module Identity
|
6
|
+
module Models
|
7
|
+
module DataMapper
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
include OmniAuth::Identity::Model
|
11
|
+
include OmniAuth::Identity::SecurePassword
|
12
|
+
|
13
|
+
# http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-persisted-3F
|
14
|
+
# http://rubydoc.info/github/mongoid/mongoid/master/Mongoid/State#persisted%3F-instance_method
|
15
|
+
alias persisted? valid?
|
16
|
+
|
17
|
+
has_secure_password
|
18
|
+
|
19
|
+
def self.auth_key=(key)
|
20
|
+
super
|
21
|
+
validates_uniqueness_of :key
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.locate(search_hash)
|
25
|
+
all(search_hash).first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # DataMapper
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Identity
|
5
|
+
module Models
|
6
|
+
module Mongoid
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
|
10
|
+
base.class_eval do
|
11
|
+
|
12
|
+
include ::OmniAuth::Identity::Model
|
13
|
+
include ::OmniAuth::Identity::SecurePassword
|
14
|
+
|
15
|
+
has_secure_password
|
16
|
+
|
17
|
+
def self.auth_key=(key)
|
18
|
+
super
|
19
|
+
validates_uniqueness_of key, :case_sensitive => false
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.locate(search_hash)
|
23
|
+
where(search_hash).first
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'bcrypt'
|
2
|
+
|
3
|
+
module OmniAuth
|
4
|
+
module Identity
|
5
|
+
# This is taken directly from Rails 3.1 code and is used if
|
6
|
+
# the version of ActiveModel that's being used does not
|
7
|
+
# include SecurePassword. The only difference is that instead of
|
8
|
+
# using ActiveSupport::Concern, it checks to see if there is already
|
9
|
+
# a has_secure_password method.
|
10
|
+
module SecurePassword
|
11
|
+
def self.included(base)
|
12
|
+
unless base.respond_to?(:has_secure_password)
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Adds methods to set and authenticate against a BCrypt password.
|
19
|
+
# This mechanism requires you to have a password_digest attribute.
|
20
|
+
#
|
21
|
+
# Validations for presence of password, confirmation of password (using
|
22
|
+
# a "password_confirmation" attribute) are automatically added.
|
23
|
+
# You can add more validations by hand if need be.
|
24
|
+
#
|
25
|
+
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
|
26
|
+
#
|
27
|
+
# # Schema: User(name:string, password_digest:string)
|
28
|
+
# class User < ActiveRecord::Base
|
29
|
+
# has_secure_password
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
|
33
|
+
# user.save # => false, password required
|
34
|
+
# user.password = "mUc3m00RsqyRe"
|
35
|
+
# user.save # => false, confirmation doesn't match
|
36
|
+
# user.password_confirmation = "mUc3m00RsqyRe"
|
37
|
+
# user.save # => true
|
38
|
+
# user.authenticate("notright") # => false
|
39
|
+
# user.authenticate("mUc3m00RsqyRe") # => user
|
40
|
+
# User.find_by_name("david").try(:authenticate, "notright") # => nil
|
41
|
+
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
|
42
|
+
def has_secure_password
|
43
|
+
attr_reader :password
|
44
|
+
|
45
|
+
validates_confirmation_of :password
|
46
|
+
validates_presence_of :password_digest
|
47
|
+
|
48
|
+
include InstanceMethodsOnActivation
|
49
|
+
|
50
|
+
if respond_to?(:attributes_protected_by_default)
|
51
|
+
def self.attributes_protected_by_default
|
52
|
+
super + ['password_digest']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethodsOnActivation
|
59
|
+
# Returns self if the password is correct, otherwise false.
|
60
|
+
def authenticate(unencrypted_password)
|
61
|
+
if BCrypt::Password.new(password_digest) == unencrypted_password
|
62
|
+
self
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Encrypts the password into the password_digest attribute.
|
69
|
+
def password=(unencrypted_password)
|
70
|
+
@password = unencrypted_password
|
71
|
+
if unencrypted_password && !unencrypted_password.empty?
|
72
|
+
self.password_digest = BCrypt::Password.create(unencrypted_password)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module OmniAuth
|
2
|
+
module Strategies
|
3
|
+
# The identity strategy allows you to provide simple internal
|
4
|
+
# user authentication using the same process flow that you
|
5
|
+
# use for external OmniAuth providers.
|
6
|
+
class Identity
|
7
|
+
include OmniAuth::Strategy
|
8
|
+
|
9
|
+
option :fields, [:name, :email]
|
10
|
+
option :on_login, nil
|
11
|
+
option :on_registration, nil
|
12
|
+
option :on_failed_registration, nil
|
13
|
+
option :locate_conditions, lambda{|req| {model.auth_key => req['auth_key']} }
|
14
|
+
|
15
|
+
def request_phase
|
16
|
+
if options[:on_login]
|
17
|
+
options[:on_login].call(self.env)
|
18
|
+
else
|
19
|
+
OmniAuth::Form.build(
|
20
|
+
:title => (options[:title] || "Identity Verification"),
|
21
|
+
:url => callback_path
|
22
|
+
) do |f|
|
23
|
+
f.text_field 'Login', 'auth_key'
|
24
|
+
f.password_field 'Password', 'password'
|
25
|
+
f.html "<p align='center'><a href='#{registration_path}'>Create an Identity</a></p>"
|
26
|
+
end.to_response
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def callback_phase
|
31
|
+
return fail!(:invalid_credentials) unless identity
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def other_phase
|
36
|
+
if on_registration_path?
|
37
|
+
if request.get?
|
38
|
+
registration_form
|
39
|
+
elsif request.post?
|
40
|
+
registration_phase
|
41
|
+
end
|
42
|
+
else
|
43
|
+
call_app!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def registration_form
|
48
|
+
if options[:on_registration]
|
49
|
+
options[:on_registration].call(self.env)
|
50
|
+
else
|
51
|
+
OmniAuth::Form.build(:title => 'Register Identity') do |f|
|
52
|
+
options[:fields].each do |field|
|
53
|
+
f.text_field field.to_s.capitalize, field.to_s
|
54
|
+
end
|
55
|
+
f.password_field 'Password', 'password'
|
56
|
+
f.password_field 'Confirm Password', 'password_confirmation'
|
57
|
+
end.to_response
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def registration_phase
|
62
|
+
attributes = (options[:fields] + [:password, :password_confirmation]).inject({}){|h,k| h[k] = request[k.to_s]; h}
|
63
|
+
@identity = model.create(attributes)
|
64
|
+
if @identity.persisted?
|
65
|
+
env['PATH_INFO'] = callback_path
|
66
|
+
callback_phase
|
67
|
+
else
|
68
|
+
if options[:on_failed_registration]
|
69
|
+
self.env['omniauth.identity'] = @identity
|
70
|
+
options[:on_failed_registration].call(self.env)
|
71
|
+
else
|
72
|
+
registration_form
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
uid{ identity.uid }
|
78
|
+
info{ identity.info }
|
79
|
+
|
80
|
+
def registration_path
|
81
|
+
options[:registration_path] || "#{path_prefix}/#{name}/register"
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_registration_path?
|
85
|
+
on_path?(registration_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
def identity
|
89
|
+
if options.locate_conditions.is_a? Proc
|
90
|
+
conditions = instance_exec(request, &options.locate_conditions)
|
91
|
+
conditions.to_hash
|
92
|
+
else
|
93
|
+
conditions = options.locate_conditions.to_hash
|
94
|
+
end
|
95
|
+
@identity ||= model.authenticate(conditions, request['password'] )
|
96
|
+
end
|
97
|
+
|
98
|
+
def model
|
99
|
+
options[:model] || ::Identity
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/lib/omniauth-identity/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.add_runtime_dependency 'omniauth'
|
6
|
+
gem.add_runtime_dependency 'bcrypt'
|
7
|
+
|
8
|
+
gem.add_development_dependency 'maruku'
|
9
|
+
gem.add_development_dependency 'simplecov'
|
10
|
+
gem.add_development_dependency 'rack-test'
|
11
|
+
gem.add_development_dependency 'rake'
|
12
|
+
gem.add_development_dependency 'rspec', '~> 3'
|
13
|
+
gem.add_development_dependency 'activerecord'
|
14
|
+
gem.add_development_dependency 'mongoid'
|
15
|
+
gem.add_development_dependency 'datamapper'
|
16
|
+
gem.add_development_dependency 'bson_ext'
|
17
|
+
gem.add_development_dependency 'byebug'
|
18
|
+
gem.add_development_dependency 'couch_potato'
|
19
|
+
|
20
|
+
gem.name = 'omniauth-identity2'
|
21
|
+
gem.version = OmniAuth::Identity::VERSION
|
22
|
+
gem.description = %q{Internal authentication handlers for OmniAuth. A modern version of the Omniauth Identity strategy.}
|
23
|
+
gem.summary = gem.description
|
24
|
+
gem.email = ['andy.roberts.uk@gmail.com']
|
25
|
+
gem.homepage = 'https://github.com/Jellybooks/omniauth-identity2'
|
26
|
+
gem.authors = ['Andrew Roberts', 'Michael Bleigh']
|
27
|
+
gem.license = 'MIT'
|
28
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
29
|
+
gem.files = `git ls-files`.split("\n")
|
30
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
|
+
gem.require_paths = ['lib']
|
32
|
+
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
|
33
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class ExampleModel
|
2
|
+
include OmniAuth::Identity::Model
|
3
|
+
end
|
4
|
+
|
5
|
+
describe OmniAuth::Identity::Model do
|
6
|
+
context 'Class Methods' do
|
7
|
+
subject{ ExampleModel }
|
8
|
+
|
9
|
+
describe '.locate' do
|
10
|
+
it('should be abstract'){ expect{ subject.locate('abc') }.to raise_error(NotImplementedError) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.authenticate' do
|
14
|
+
it 'should call locate and then authenticate' do
|
15
|
+
mocked_instance = double('ExampleModel', :authenticate => 'abbadoo')
|
16
|
+
allow(subject).to receive(:locate).with('email' => 'example').and_return(mocked_instance)
|
17
|
+
expect(subject.authenticate({'email' => 'example'},'pass')).to eq('abbadoo')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should call locate with additional scopes when provided' do
|
21
|
+
mocked_instance = double('ExampleModel', :authenticate => 'abbadoo')
|
22
|
+
allow(subject).to receive(:locate).with('email' => 'example', 'user_type' => 'admin').and_return(mocked_instance)
|
23
|
+
expect(subject.authenticate({'email' => 'example', 'user_type' => 'admin'}, 'pass')).to eq('abbadoo')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should recover gracefully if locate is nil' do
|
27
|
+
allow(subject).to receive(:locate).and_return(nil)
|
28
|
+
expect(subject.authenticate('blah','foo')).to be false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'Instance Methods' do
|
34
|
+
subject{ ExampleModel.new }
|
35
|
+
|
36
|
+
describe '#authenticate' do
|
37
|
+
it('should be abstract'){ expect{ subject.authenticate('abc') }.to raise_error(NotImplementedError) }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#uid' do
|
41
|
+
it 'should default to #id' do
|
42
|
+
allow(subject).to receive(:respond_to?).with(:id).and_return(true)
|
43
|
+
allow(subject).to receive(:id).and_return 'wakka-do'
|
44
|
+
expect(subject.uid).to eq('wakka-do')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should stringify it' do
|
48
|
+
allow(subject).to receive(:id).and_return 123
|
49
|
+
expect(subject.uid).to eq('123')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should raise NotImplementedError if #id is not defined' do
|
53
|
+
allow(subject).to receive(:respond_to?).with(:id).and_return(false)
|
54
|
+
expect{ subject.uid }.to raise_error(NotImplementedError)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#auth_key' do
|
59
|
+
it 'should default to #email' do
|
60
|
+
allow(subject).to receive(:respond_to?).with(:email).and_return(true)
|
61
|
+
allow(subject).to receive(:email).and_return('bob@bob.com')
|
62
|
+
expect(subject.auth_key).to eq('bob@bob.com')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should use the class .auth_key' do
|
66
|
+
subject.class.auth_key 'login'
|
67
|
+
allow(subject).to receive(:login).and_return 'bob'
|
68
|
+
expect(subject.auth_key).to eq('bob')
|
69
|
+
subject.class.auth_key nil
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should raise a NotImplementedError if the auth_key method is not defined' do
|
73
|
+
expect{ subject.auth_key }.to raise_error(NotImplementedError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#auth_key=' do
|
78
|
+
it 'should default to setting email' do
|
79
|
+
allow(subject).to receive(:respond_to?).with(:email=).and_return(true)
|
80
|
+
expect(subject).to receive(:email=).with 'abc'
|
81
|
+
|
82
|
+
subject.auth_key = 'abc'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should use a custom .auth_key if one is provided' do
|
86
|
+
subject.class.auth_key 'login'
|
87
|
+
allow(subject).to receive(:respond_to?).with(:login=).and_return(true)
|
88
|
+
expect(subject).to receive(:login=).with('abc')
|
89
|
+
|
90
|
+
subject.auth_key = 'abc'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should raise a NotImplementedError if the autH_key method is not defined' do
|
94
|
+
expect{ subject.auth_key = 'broken' }.to raise_error(NotImplementedError)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#info' do
|
99
|
+
it 'should include attributes that are set' do
|
100
|
+
allow(subject).to receive(:name).and_return('Bob Bobson')
|
101
|
+
allow(subject).to receive(:nickname).and_return('bob')
|
102
|
+
|
103
|
+
expect(subject.info).to eq({
|
104
|
+
'name' => 'Bob Bobson',
|
105
|
+
'nickname' => 'bob'
|
106
|
+
})
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should automatically set name off of nickname' do
|
110
|
+
allow(subject).to receive(:nickname).and_return('bob')
|
111
|
+
subject.info['name'] == 'bob'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should not overwrite a provided name' do
|
115
|
+
allow(subject).to receive(:name).and_return('Awesome Dude')
|
116
|
+
allow(subject).to receive(:first_name).and_return('Frank')
|
117
|
+
expect(subject.info['name']).to eq('Awesome Dude')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|