authinator 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|