authinator 0.0.3 → 0.1.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 +4 -4
- data/Gemfile +3 -0
- data/lib/authinator.rb +3 -1
- data/lib/authinator/auth_code_exchanger.rb +14 -42
- data/lib/authinator/client_token_issuer.rb +222 -0
- data/lib/authinator/configuration.rb +71 -0
- data/lib/authinator/end_point_listener.rb +3 -16
- data/lib/authinator/provider.rb +54 -0
- data/lib/authinator/version.rb +1 -1
- data/spec/authinator/auth_code_exchanger_spec.rb +29 -18
- data/spec/authinator/client_token_issuer_spec.rb +17 -0
- data/spec/authinator/end_point_listener_spec.rb +25 -19
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bab186640c9c1e8c020d88808cea1d05389e743
|
4
|
+
data.tar.gz: fd3636d7972b4bda346d1c9b641d633f2e655053
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84848419144e543347fac7faa7fe32e13b10da1ea8e282a9c32f0977dfad44e161313c310dd3114b85c65667d5764bf9c569fd843448074c67cf7635c1d55cb5
|
7
|
+
data.tar.gz: db8e6561e4032b1b6f7f71648d1f48219ff2b94d260a55fae5f0f237997a7d746904fe5a27e95de0ed9f5846ac60b37444d5763ff3f2d85b1baace23c608826d
|
data/Gemfile
CHANGED
@@ -16,3 +16,6 @@ group :test do
|
|
16
16
|
gem 'simplecov', '>= 0.9'
|
17
17
|
gem 'webmock'
|
18
18
|
end
|
19
|
+
|
20
|
+
# gem 'abstract_google_client', path: '/Users/abradner/foogi/google_client', require: 'google_client'
|
21
|
+
gem 'abstract_google_client', github: 'abradner/google_client', require: 'google_client'
|
data/lib/authinator.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'active_support/all'
|
1
2
|
require 'authinator/version'
|
3
|
+
require 'authinator/provider'
|
4
|
+
require 'authinator/configuration'
|
2
5
|
require 'authinator/auth_code_exchanger'
|
3
6
|
require 'authinator/end_point_listener'
|
4
7
|
require 'authinator/client_token_issuer'
|
5
8
|
|
6
9
|
module Authinator
|
7
|
-
# Your code goes here...
|
8
10
|
end
|
@@ -1,10 +1,7 @@
|
|
1
|
-
# require 'omniauth/strategies/google_oauth2'
|
2
|
-
# require 'omniauth-oauth2'
|
3
1
|
require 'oauth2'
|
4
2
|
|
5
3
|
module Authinator
|
6
4
|
class AuthCodeExchanger
|
7
|
-
VALID_PROVIDERS = [:stub, :google]
|
8
5
|
STUB_SAMPLE_TOKEN = {
|
9
6
|
token: 'ya29.token',
|
10
7
|
refresh_token: '1/refresh',
|
@@ -12,46 +9,25 @@ module Authinator
|
|
12
9
|
}
|
13
10
|
|
14
11
|
attr_reader :provider
|
12
|
+
# attr_reader :client
|
15
13
|
|
16
14
|
def self.valid_providers
|
17
|
-
|
15
|
+
Authinator.configuration.providers
|
18
16
|
end
|
19
17
|
|
20
|
-
|
21
|
-
google: {
|
22
|
-
client_id: 'cl_id',
|
23
|
-
client_secret: 'cl_sec',
|
24
|
-
site: 'https://accounts.google.com',
|
25
|
-
token_url: '/o/oauth2/token',
|
26
|
-
},
|
27
|
-
stub: {
|
28
|
-
client_id: 'cl_id',
|
29
|
-
client_secret: 'cl_sec',
|
30
|
-
site: 'https://example.org',
|
31
|
-
token_url: '/extoken',
|
32
|
-
},
|
33
|
-
}
|
34
|
-
|
35
|
-
def initialize(provider, client_options = {})
|
36
|
-
unless VALID_PROVIDERS.include? provider
|
37
|
-
fail ArgumentError,
|
38
|
-
"Provider #{provider} not in supported parameter list:\n" <<
|
39
|
-
VALID_PROVIDERS.inspect
|
40
|
-
end
|
41
|
-
|
18
|
+
def initialize(provider, _client_options = {})
|
42
19
|
@provider = provider
|
43
|
-
build_provider_hash(client_options)
|
44
20
|
end
|
45
21
|
|
46
22
|
def site_token_url
|
47
|
-
@
|
23
|
+
@provider.site + @provider.token_url
|
48
24
|
end
|
49
25
|
|
50
26
|
def exchange(auth_code)
|
51
27
|
# auth_code = params[:code]
|
52
28
|
return if auth_code.nil? || auth_code.empty?
|
53
29
|
|
54
|
-
case @provider.
|
30
|
+
case @provider.name
|
55
31
|
when :google
|
56
32
|
exchange_with_google(auth_code)
|
57
33
|
when :stub
|
@@ -61,20 +37,16 @@ module Authinator
|
|
61
37
|
|
62
38
|
private
|
63
39
|
|
64
|
-
def build_provider_hash(client_options)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
40
|
+
# def build_provider_hash(client_options)
|
41
|
+
# @provider_hash = Authinator.configuration.provider_for[@provider.to_sym]
|
42
|
+
# @provider_hash[:client_id] = client_options.delete(:client_id) if client_options[:client_id]
|
43
|
+
# @provider_hash[:client_secret] = client_options.delete(:client_secret) if client_options[:client_secret]
|
44
|
+
# end
|
69
45
|
|
70
46
|
def exchange_with_google(code)
|
71
|
-
|
72
|
-
|
73
|
-
client_id = provider_hash.delete(:client_id)
|
74
|
-
client_secret = provider_hash.delete(:client_secret)
|
75
|
-
@client = OAuth2::Client.new(client_id, client_secret, provider_hash)
|
47
|
+
@client = OAuth2::Client.new(@provider.client_id, @provider.client_secret, @provider.to_hash)
|
76
48
|
|
77
|
-
token = @client.auth_code.get_token(code)
|
49
|
+
token = @client.auth_code.get_token(code, redirect_uri: 'http://localhost:4200')
|
78
50
|
|
79
51
|
# response = token.get('/api/resource', :params => { 'query_foo' => 'bar' })
|
80
52
|
# response.class.name
|
@@ -85,8 +57,8 @@ module Authinator
|
|
85
57
|
|
86
58
|
def exchange_with_stub(_code)
|
87
59
|
@client = OAuth2::Client.new(
|
88
|
-
@
|
89
|
-
@
|
60
|
+
@provider.client_id,
|
61
|
+
@provider.client_secret,
|
90
62
|
)
|
91
63
|
|
92
64
|
OAuth2::AccessToken.new(
|
@@ -1,4 +1,226 @@
|
|
1
1
|
module Authinator
|
2
|
+
class EmailNotVerifiedError < ArgumentError
|
3
|
+
end
|
4
|
+
|
2
5
|
class ClientTokenIssuer
|
6
|
+
TEMP_RAW_INFO = {
|
7
|
+
email_verified: true,
|
8
|
+
email: 'a@b.c',
|
9
|
+
given_name: 'first',
|
10
|
+
family_name: 'last',
|
11
|
+
profile: 'xyz',
|
12
|
+
}.with_indifferent_access
|
13
|
+
|
14
|
+
# Options can
|
15
|
+
def initialize(params, options = {})
|
16
|
+
@params = params.with_indifferent_access
|
17
|
+
provider_name = (options.delete :provider if options[:provider]) || (@params[:provider].present? ? @params[:provider].to_sym : nil)
|
18
|
+
unless Authinator.configuration.providers.include? provider_name
|
19
|
+
fail ArgumentError,
|
20
|
+
"Provider #{provider_name} not in supported parameter list:\n" <<
|
21
|
+
Authinator.configuration.providers.inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
@provider = Authinator.configuration.provider_for(provider_name)
|
25
|
+
|
26
|
+
@auth_code = (options.delete :auth_code if options[:auth_code]) || @params['code']
|
27
|
+
@app_name = (options.delete :app_name if options[:app_name]) || application_name
|
28
|
+
@valid_applications = (options.delete :valid_applications if options[:valid_applications]) || Authinator.configuration.valid_applications
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorize!(options = {})
|
32
|
+
return { error: "Invalid Application #{@app_name}" }, :bad_request unless @valid_applications.include? @app_name
|
33
|
+
|
34
|
+
handler = EndPointListener.new(auth_code: @auth_code, provider: @provider)
|
35
|
+
|
36
|
+
if handler.valid?
|
37
|
+
return handle(options)
|
38
|
+
|
39
|
+
else
|
40
|
+
# Doorkeeper's defaut behaviour when the user signs in with login/password.
|
41
|
+
# TODO: kill this block
|
42
|
+
begin
|
43
|
+
response = strategy.authorize
|
44
|
+
headers.merge! response.headers
|
45
|
+
self.response_body = response.body.merge(user_id: (response.token.resource_owner_id && response.token.resource_owner_id.to_s)).to_json
|
46
|
+
self.status = response.status
|
47
|
+
rescue Doorkeeper::Errors::DoorkeeperError => e
|
48
|
+
handle_token_exception e
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle(options = {})
|
55
|
+
application = Doorkeeper::Application.where(name: @app_name)
|
56
|
+
token_issuer = AuthCodeExchanger.new @provider
|
57
|
+
provider_access_token = token_issuer.exchange @auth_code
|
58
|
+
|
59
|
+
begin
|
60
|
+
info = load_info(provider_access_token)
|
61
|
+
rescue EmailNotVerifiedError => _e
|
62
|
+
return { error: 'Cannot create a Foogi account with an unverified email address' }, :bad_request
|
63
|
+
end
|
64
|
+
|
65
|
+
acc = find_or_create_account_from_info(info)
|
66
|
+
user = acc.user
|
67
|
+
expires_in = options.delete(:expires_in) || 2.hours
|
68
|
+
store_provider_credentials!(acc, provider_access_token)
|
69
|
+
|
70
|
+
client_access_token = Doorkeeper::AccessToken.create!(
|
71
|
+
application_id: application,
|
72
|
+
resource_owner_id: user.id,
|
73
|
+
expires_in: expires_in,
|
74
|
+
use_refresh_token: true,
|
75
|
+
)
|
76
|
+
|
77
|
+
token_data = {
|
78
|
+
access_token: client_access_token.token,
|
79
|
+
refresh_token: client_access_token.refresh_token,
|
80
|
+
token_type: 'bearer',
|
81
|
+
expires_in: client_access_token.expires_in,
|
82
|
+
user_id: user.id, # TODO: remove
|
83
|
+
provider_access_token: provider_access_token.token,
|
84
|
+
provider_expires_in: provider_access_token.expires_in,
|
85
|
+
# provider_id_token: provider_access_token.id_token,
|
86
|
+
}
|
87
|
+
|
88
|
+
[token_data.to_json, :ok]
|
89
|
+
end
|
90
|
+
|
91
|
+
def load_info(access_token)
|
92
|
+
# raw_info = provider_access_token.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect').parsed
|
93
|
+
|
94
|
+
raw_info = case @provider.name
|
95
|
+
when :google
|
96
|
+
retrieve_user_from_google access_token
|
97
|
+
when :stub
|
98
|
+
TEMP_RAW_INFO
|
99
|
+
end
|
100
|
+
# raw_info = TEMP_RAW_INFO
|
101
|
+
|
102
|
+
verified_email = raw_info[:email_verified] ? raw_info[:email] : nil
|
103
|
+
fail EmailNotVerifiedError, 'Email not verified' unless verified_email.present?
|
104
|
+
|
105
|
+
# verified_email = raw_info['email']
|
106
|
+
# prune!(
|
107
|
+
# # email_verified: hash['emails'].first['type'].eql?('account'),
|
108
|
+
# email_verified: false,
|
109
|
+
# email: raw_info['emails'].first['value'],
|
110
|
+
# display_name: raw_info['displayName'],
|
111
|
+
# name: raw_info['name'],
|
112
|
+
# picture_url: raw_info['image']['url'],
|
113
|
+
# uid: raw_info['id'],
|
114
|
+
# language: raw_info['language'],
|
115
|
+
# )
|
116
|
+
# prune!(
|
117
|
+
# name: raw_info['name'],
|
118
|
+
# email: verified_email,
|
119
|
+
# first_name: raw_info['given_name'],
|
120
|
+
# last_name: raw_info['family_name'],
|
121
|
+
# image: raw_info['image_url'],
|
122
|
+
# uid: raw_info['sub'] || verified_email,
|
123
|
+
# urls: {
|
124
|
+
# @provider.name => raw_info['profile'],
|
125
|
+
# },
|
126
|
+
# )
|
127
|
+
raw_info
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def find_or_create_account_from_info(info)
|
133
|
+
acc = Account.find_by email: info[:email], provider: @provider.name.to_s
|
134
|
+
if acc.blank?
|
135
|
+
user = User.find_by(primary_email: info[:email]) || User.create!(
|
136
|
+
primary_email: info[:email], display_name: info[:display_name],
|
137
|
+
name: info[:name], picture_url: info[:picture_url],
|
138
|
+
)
|
139
|
+
acc = user.create_account!(
|
140
|
+
email: info[:email],
|
141
|
+
provider: @provider.name.to_s,
|
142
|
+
provider_uid: info[:uid],
|
143
|
+
)
|
144
|
+
end
|
145
|
+
acc
|
146
|
+
end
|
147
|
+
|
148
|
+
def store_provider_credentials!(acc, access_token)
|
149
|
+
acc.credential.destroy unless acc.credential.blank?
|
150
|
+
|
151
|
+
acc.create_credential!(
|
152
|
+
access_token: access_token.token,
|
153
|
+
refresh_token: access_token.refresh_token,
|
154
|
+
expires_at: (Time.now.utc + access_token.expires_in),
|
155
|
+
expires: access_token.expires_in.present? ? true : false,
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
def application_name
|
160
|
+
@params['application_name'].present? ? @params['application_name'].to_sym : nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def prune!(hash)
|
164
|
+
hash.delete_if do |_, v|
|
165
|
+
prune!(v) if v.is_a?(Hash)
|
166
|
+
v.nil? || (v.respond_to?(:empty?) && v.empty?)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def retrieve_user_from_google(access_token)
|
171
|
+
client = GoogleClient::Builder.new('plus', 'v1', 1)
|
172
|
+
result = client.execute access_token, client.service.people.get, userId: 'me'
|
173
|
+
process_google_info JSON.parse(result.body)
|
174
|
+
end
|
175
|
+
|
176
|
+
def process_google_info(hash)
|
177
|
+
{
|
178
|
+
email_verified: hash['emails'].first['type'].eql?('account'),
|
179
|
+
email: hash['emails'].first['value'],
|
180
|
+
display_name: hash['displayName'],
|
181
|
+
name: hash['name'],
|
182
|
+
picture_url: hash['image']['url'],
|
183
|
+
uid: hash['id'],
|
184
|
+
language: hash['language'],
|
185
|
+
}.with_indifferent_access
|
186
|
+
|
187
|
+
# {
|
188
|
+
# "kind" => "plus#person",
|
189
|
+
# "etag" => "\"xyz-something/abc\"",
|
190
|
+
# "gender" => "female",
|
191
|
+
# "emails" => [
|
192
|
+
# {
|
193
|
+
# "value" => "info@foogi.me",
|
194
|
+
# "type" => "account"
|
195
|
+
# }
|
196
|
+
# ],
|
197
|
+
# "objectType" => "person",
|
198
|
+
# "id" => "12345",
|
199
|
+
# "displayName" => "Leading Foogster",
|
200
|
+
# "name" => {
|
201
|
+
# "familyName" => "Foogster",
|
202
|
+
# "givenName" => "Leading"
|
203
|
+
# },
|
204
|
+
# "url" => "https://plus.google.com/+FoogiMe",
|
205
|
+
# "image" => {
|
206
|
+
# "url" => "https://someurl/photo.jpg?sz=50",
|
207
|
+
# "isDefault" => false
|
208
|
+
# },
|
209
|
+
# "placesLived" => [
|
210
|
+
# {
|
211
|
+
# "value" => "Sydney, Australia",
|
212
|
+
# "primary" => true
|
213
|
+
# },
|
214
|
+
# {
|
215
|
+
# "value" => "San Francisco, USA"
|
216
|
+
# }
|
217
|
+
# ],
|
218
|
+
# "isPlusUser" => true,
|
219
|
+
# "language" => "en_GB",
|
220
|
+
# "circledByCount" => 11225,
|
221
|
+
# "verified" => false,
|
222
|
+
# "domain" => "foogi.me"
|
223
|
+
# }
|
224
|
+
end
|
3
225
|
end
|
4
226
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Authinator
|
2
|
+
# Exception to handle a missing initializer
|
3
|
+
# class MissingConfiguration < StandardError
|
4
|
+
# def initialize
|
5
|
+
# super('Configuration for authinator missing. Do you have an Authinator initializer?')
|
6
|
+
# end
|
7
|
+
# end
|
8
|
+
|
9
|
+
# Module level methods
|
10
|
+
class << self
|
11
|
+
attr_accessor :configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configure
|
15
|
+
self.configuration ||= Configuration.new
|
16
|
+
yield(configuration)
|
17
|
+
end
|
18
|
+
|
19
|
+
# def self.configuration
|
20
|
+
# @config || (fail MissingConfiguration.new)
|
21
|
+
# end
|
22
|
+
|
23
|
+
# Configuration class
|
24
|
+
class Configuration
|
25
|
+
attr_reader :providers
|
26
|
+
attr_accessor :valid_applications
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@providers = {}
|
30
|
+
add_provider(
|
31
|
+
:stub,
|
32
|
+
client_id: 'cl_id',
|
33
|
+
client_secret: 'cl_sec',
|
34
|
+
site: 'https://example.org',
|
35
|
+
token_url: '/extoken',
|
36
|
+
api_key: 'api_key',
|
37
|
+
user_info_url: 'http://example.org/info',
|
38
|
+
)
|
39
|
+
add_provider(
|
40
|
+
:google,
|
41
|
+
site: 'https://accounts.google.com',
|
42
|
+
token_url: '/o/oauth2/token',
|
43
|
+
user_info_url: 'https://www.googleapis.com/plus/v1/people/me/openIdConnect',
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def providers
|
48
|
+
@providers.keys
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_provider(provider_name, options = {})
|
52
|
+
@providers[provider_name] = Provider.new(provider_name, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_secrets(provider_name, options = {})
|
56
|
+
fail(
|
57
|
+
ArgumentError,
|
58
|
+
"#{provider_name} is not a configured provider.\n" \
|
59
|
+
"Valid Providers:\n" <<
|
60
|
+
providers.to_s,
|
61
|
+
) if @providers[provider_name].blank?
|
62
|
+
|
63
|
+
@providers[provider_name].add_secrets(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# A more abstracted way of accessing the providers
|
67
|
+
def provider_for(provider_name)
|
68
|
+
@providers[provider_name]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
module Authinator
|
2
2
|
class EndPointListener
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :provider, :auth_code, :options, :errors
|
4
4
|
|
5
5
|
def initialize(hash = {})
|
6
|
-
@email = hash.delete :email
|
7
6
|
@provider = hash.delete :provider
|
8
7
|
@auth_code = hash.delete :auth_code
|
9
8
|
@options = hash
|
@@ -12,36 +11,24 @@ module Authinator
|
|
12
11
|
|
13
12
|
def valid?
|
14
13
|
validator_presence? &&
|
15
|
-
validator_valid_email? &&
|
16
14
|
validator_valid_provider?
|
17
15
|
end
|
18
16
|
|
19
17
|
private
|
20
18
|
|
21
19
|
def validator_presence?
|
22
|
-
email_present = present? @email
|
23
20
|
provider_present = present? @provider
|
24
21
|
auth_code_present = present? @auth_code
|
25
22
|
|
26
|
-
return true if
|
23
|
+
return true if provider_present && auth_code_present
|
27
24
|
errors << 'A required param is missing'
|
28
|
-
errors << '"email" field missing' unless email_present
|
29
25
|
errors << '"provider" field missing' unless provider_present
|
30
26
|
errors << '"auth_code" field missing' unless auth_code_present
|
31
27
|
false
|
32
28
|
end
|
33
29
|
|
34
|
-
# Regexp lovingly borrowed from https://github.com/balexand/email_validator
|
35
|
-
def validator_valid_email?
|
36
|
-
name_validation = @options[:strict_mode] ? '-a-z0-9+._' : '^@\\s'
|
37
|
-
regexp = /\A\s*([#{name_validation}]{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
|
38
|
-
return true if @email =~ regexp
|
39
|
-
errors << "Email '#{@email}' is invalid"
|
40
|
-
false
|
41
|
-
end
|
42
|
-
|
43
30
|
def validator_valid_provider?
|
44
|
-
return true if Authinator
|
31
|
+
return true if Authinator.configuration.providers.include? @provider.name
|
45
32
|
errors << "Provider '#{@provider}' is invalid"
|
46
33
|
false
|
47
34
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Authinator
|
2
|
+
class Provider
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :client_id,
|
5
|
+
:client_secret,
|
6
|
+
:site,
|
7
|
+
:token_url,
|
8
|
+
:api_key,
|
9
|
+
:user_info_url
|
10
|
+
|
11
|
+
def initialize(name, options = {})
|
12
|
+
@name = name
|
13
|
+
@client_id = options.delete :client_id
|
14
|
+
@client_secret = options.delete :client_secret
|
15
|
+
@site = options.delete :site
|
16
|
+
@token_url = options.delete :token_url
|
17
|
+
@api_key = options.delete :api_key
|
18
|
+
@user_info_url = options.delete :user_info_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_secrets(options = {})
|
22
|
+
@client_id = options.delete :client_id if options[:client_id]
|
23
|
+
@client_secret = options.delete :client_secret if options[:client_secret]
|
24
|
+
@api_key = options.delete :api_key if options[:api_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def secrets
|
28
|
+
sec = {}
|
29
|
+
sec[:client_id] = @client_id if @client_id
|
30
|
+
sec[:client_secret] = @client_secret if @client_secret
|
31
|
+
sec[:api_key] = @api_key if @api_key
|
32
|
+
|
33
|
+
sec
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
{
|
38
|
+
name: @name,
|
39
|
+
client_id: @client_id,
|
40
|
+
client_secret: @client_secret,
|
41
|
+
site: @site,
|
42
|
+
token_url: @token_url,
|
43
|
+
api_key: @api_key,
|
44
|
+
user_info_url: @user_info_url,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def empty?
|
49
|
+
to_hash.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :inspect, :to_hash
|
53
|
+
end
|
54
|
+
end
|
data/lib/authinator/version.rb
CHANGED
@@ -3,6 +3,11 @@ require 'json'
|
|
3
3
|
|
4
4
|
describe Authinator::AuthCodeExchanger do
|
5
5
|
before :all do
|
6
|
+
Authinator.configure do |config|
|
7
|
+
config.add_secrets :google,
|
8
|
+
client_id: 'cl_id',
|
9
|
+
client_secret: 'cl_sec'
|
10
|
+
end
|
6
11
|
@token_hash = {
|
7
12
|
access_token: 'ya29.token',
|
8
13
|
refresh_token: '1/refresh',
|
@@ -27,9 +32,29 @@ describe Authinator::AuthCodeExchanger do
|
|
27
32
|
body: @token_hash.to_json,
|
28
33
|
headers: { content_type: 'application/json' },
|
29
34
|
}
|
35
|
+
|
36
|
+
@stub_provider = Authinator::Provider.new(
|
37
|
+
:stub,
|
38
|
+
client_id: 'cl_id',
|
39
|
+
client_secret: 'cl_sec',
|
40
|
+
site: 'https://example.org',
|
41
|
+
token_url: '/extoken',
|
42
|
+
api_key: 'api_key',
|
43
|
+
user_info_url: 'http://example.org/info',
|
44
|
+
)
|
45
|
+
@google_provider = Authinator::Provider.new(
|
46
|
+
:stub,
|
47
|
+
client_id: 'cl_id',
|
48
|
+
client_secret: 'cl_sec',
|
49
|
+
site: 'https://example.org',
|
50
|
+
token_url: '/extoken',
|
51
|
+
api_key: 'api_key',
|
52
|
+
user_info_url: 'http://example.org/info',
|
53
|
+
)
|
30
54
|
end
|
55
|
+
|
31
56
|
it 'should correctly process a generic (stub) token' do
|
32
|
-
ace = Authinator::AuthCodeExchanger.new(
|
57
|
+
ace = Authinator::AuthCodeExchanger.new(@stub_provider)
|
33
58
|
stub_request(:post, ace.site_token_url).
|
34
59
|
with(body: @test_env,
|
35
60
|
headers: @req_headers).
|
@@ -45,7 +70,8 @@ describe Authinator::AuthCodeExchanger do
|
|
45
70
|
it 'should return an AccessToken for each provider' do
|
46
71
|
klass = OAuth2::AccessToken
|
47
72
|
|
48
|
-
Authinator::AuthCodeExchanger.valid_providers.each do |
|
73
|
+
Authinator::AuthCodeExchanger.valid_providers.each do |provider_name|
|
74
|
+
provider = Authinator.configuration.provider_for provider_name
|
49
75
|
ace = Authinator::AuthCodeExchanger.new(provider)
|
50
76
|
stub_request(:post, ace.site_token_url).
|
51
77
|
with(body: @test_env,
|
@@ -57,7 +83,7 @@ describe Authinator::AuthCodeExchanger do
|
|
57
83
|
end
|
58
84
|
|
59
85
|
it 'should correctly process a google token' do
|
60
|
-
ace = Authinator::AuthCodeExchanger.new(
|
86
|
+
ace = Authinator::AuthCodeExchanger.new(@google_provider)
|
61
87
|
stub_request(:post, ace.site_token_url).
|
62
88
|
with(body: @test_env,
|
63
89
|
headers: @req_headers).
|
@@ -69,19 +95,4 @@ describe Authinator::AuthCodeExchanger do
|
|
69
95
|
expect(result.refresh_token).to eq @token_hash[:refresh_token]
|
70
96
|
expect(result.expires_in).to eq @token_hash[:expires_in]
|
71
97
|
end
|
72
|
-
|
73
|
-
it 'should gracefully not allow unsupported providers' do
|
74
|
-
expect do
|
75
|
-
Authinator::AuthCodeExchanger.new(:some_fake_provider)
|
76
|
-
end.to raise_error(ArgumentError)
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'should correctly handle client information provided as a parameter' do
|
80
|
-
ace = Authinator::AuthCodeExchanger.new(:google, client_id: 'new_id', client_secret: 'new_secret')
|
81
|
-
|
82
|
-
stub_request(:post, ace.site_token_url).
|
83
|
-
with(body: @test_env.merge(client_id: 'new_id', client_secret: 'new_secret'),
|
84
|
-
headers: @req_headers).
|
85
|
-
to_return(@req_response)
|
86
|
-
end
|
87
98
|
end
|
@@ -5,4 +5,21 @@ describe Authinator::ClientTokenIssuer do
|
|
5
5
|
it 'should generate our own set of tokens for the client if the provided ones exchanged successfully'
|
6
6
|
|
7
7
|
it 'should all integrate to follow a standard flow to auth the api client'
|
8
|
+
|
9
|
+
it 'should gracefully not allow unsupported providers' do
|
10
|
+
pending
|
11
|
+
expect do
|
12
|
+
Authinator::AuthCodeExchanger.new(:some_fake_provider)
|
13
|
+
end.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should correctly handle client information provided as a parameter' do
|
17
|
+
pending
|
18
|
+
ace = Authinator::AuthCodeExchanger.new(:google, client_id: 'new_id', client_secret: 'new_secret')
|
19
|
+
|
20
|
+
stub_request(:post, ace.site_token_url).
|
21
|
+
with(body: @test_env.merge(client_id: 'new_id', client_secret: 'new_secret'),
|
22
|
+
headers: @req_headers).
|
23
|
+
to_return(@req_response)
|
24
|
+
end
|
8
25
|
end
|
@@ -2,10 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Authinator::EndPointListener do
|
4
4
|
before :each do
|
5
|
+
@stub_provider = Authinator::Provider.new(
|
6
|
+
:stub,
|
7
|
+
client_id: 'cl_id',
|
8
|
+
client_secret: 'cl_sec',
|
9
|
+
site: 'https://example.org',
|
10
|
+
token_url: '/extoken',
|
11
|
+
api_key: 'api_key',
|
12
|
+
user_info_url: 'http://example.org/info',
|
13
|
+
)
|
5
14
|
@creds_hash = {
|
6
|
-
email: 'test@foogi.me',
|
7
15
|
auth_code: '4/auth_code',
|
8
|
-
provider:
|
16
|
+
provider: @stub_provider,
|
9
17
|
}
|
10
18
|
end
|
11
19
|
it 'should accept valid-looking credentials from the client' do
|
@@ -25,31 +33,30 @@ describe Authinator::EndPointListener do
|
|
25
33
|
expect(listener.valid?).to be_falsey
|
26
34
|
end
|
27
35
|
it 'should return an error unless all three fields are provided' do
|
28
|
-
listener1 = Authinator::EndPointListener.new(@creds_hash.dup.tap { |hs| hs.delete(:email) }) # remove email
|
29
36
|
listener2 = Authinator::EndPointListener.new(@creds_hash.dup.tap { |hs| hs.delete(:auth_code) }) # remove ac
|
30
37
|
listener3 = Authinator::EndPointListener.new(@creds_hash.dup.tap { |hs| hs.delete(:provider) }) # remove prov
|
31
38
|
|
32
|
-
expect(listener1.valid?).to be_falsey
|
33
39
|
expect(listener2.valid?).to be_falsey
|
34
40
|
expect(listener3.valid?).to be_falsey
|
35
41
|
end
|
36
|
-
it 'should reject invalid emails' do
|
37
|
-
bad_email_hash1 = @creds_hash.merge(email: 'abc')
|
38
|
-
bad_email_hash2 = @creds_hash.merge(email: '')
|
39
|
-
bad_email_hash3 = @creds_hash.merge(email: 'a@')
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
it 'should reject invalid providers' do
|
44
|
+
bad_provider_1 = Authinator::Provider.new(
|
45
|
+
:bad1,
|
46
|
+
client_id: 'cl_id',
|
47
|
+
client_secret: 'cl_sec',
|
48
|
+
site: 'https://example.org',
|
49
|
+
token_url: '/extoken',
|
50
|
+
api_key: 'api_key',
|
51
|
+
user_info_url: 'http://example.org/info',
|
52
|
+
)
|
44
53
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
54
|
+
bad_provider_2 = Authinator::Provider.new(
|
55
|
+
:bad2,
|
56
|
+
)
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
bad_provider_hash2 = @creds_hash.merge(provider: '')
|
58
|
+
bad_provider_hash1 = @creds_hash.merge(provider: bad_provider_1)
|
59
|
+
bad_provider_hash2 = @creds_hash.merge(provider: bad_provider_2)
|
53
60
|
|
54
61
|
listener1 = Authinator::EndPointListener.new(bad_provider_hash1)
|
55
62
|
listener2 = Authinator::EndPointListener.new(bad_provider_hash2)
|
@@ -63,7 +70,6 @@ describe Authinator::EndPointListener do
|
|
63
70
|
listener.valid?
|
64
71
|
expect(listener.errors).to eq [
|
65
72
|
'A required param is missing',
|
66
|
-
'"email" field missing',
|
67
73
|
'"provider" field missing',
|
68
74
|
'"auth_code" field missing',
|
69
75
|
]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: authinator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Bradner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oauth2
|
@@ -115,7 +115,9 @@ files:
|
|
115
115
|
- lib/authinator.rb
|
116
116
|
- lib/authinator/auth_code_exchanger.rb
|
117
117
|
- lib/authinator/client_token_issuer.rb
|
118
|
+
- lib/authinator/configuration.rb
|
118
119
|
- lib/authinator/end_point_listener.rb
|
120
|
+
- lib/authinator/provider.rb
|
119
121
|
- lib/authinator/version.rb
|
120
122
|
- spec/authinator/auth_code_exchanger_spec.rb
|
121
123
|
- spec/authinator/client_token_issuer_spec.rb
|