googleauth 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +23 -6
- data/.travis.yml +3 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile +20 -0
- data/README.md +80 -1
- data/googleauth.gemspec +1 -9
- data/lib/googleauth.rb +6 -3
- data/lib/googleauth/client_id.rb +102 -0
- data/lib/googleauth/scope_util.rb +61 -0
- data/lib/googleauth/service_account.rb +23 -18
- data/lib/googleauth/signet.rb +20 -1
- data/lib/googleauth/stores/file_token_store.rb +64 -0
- data/lib/googleauth/stores/redis_token_store.rb +95 -0
- data/lib/googleauth/token_store.rb +69 -0
- data/lib/googleauth/user_authorizer.rb +273 -0
- data/lib/googleauth/user_refresh.rb +53 -16
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +289 -0
- data/spec/googleauth/apply_auth_examples.rb +36 -58
- data/spec/googleauth/client_id_spec.rb +140 -0
- data/spec/googleauth/compute_engine_spec.rb +34 -71
- data/spec/googleauth/get_application_default_spec.rb +26 -35
- data/spec/googleauth/scope_util_spec.rb +75 -0
- data/spec/googleauth/service_account_spec.rb +16 -11
- data/spec/googleauth/signet_spec.rb +14 -9
- data/spec/googleauth/stores/file_token_store_spec.rb +58 -0
- data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
- data/spec/googleauth/stores/store_examples.rb +58 -0
- data/spec/googleauth/user_authorizer_spec.rb +314 -0
- data/spec/googleauth/user_refresh_spec.rb +77 -13
- data/spec/googleauth/web_user_authorizer_spec.rb +159 -0
- data/spec/spec_helper.rb +33 -1
- metadata +37 -113
@@ -29,6 +29,7 @@
|
|
29
29
|
|
30
30
|
require 'googleauth/signet'
|
31
31
|
require 'googleauth/credentials_loader'
|
32
|
+
require 'googleauth/scope_util'
|
32
33
|
require 'multi_json'
|
33
34
|
|
34
35
|
module Google
|
@@ -46,8 +47,30 @@ module Google
|
|
46
47
|
# cf [Application Default Credentials](http://goo.gl/mkAHpZ)
|
47
48
|
class UserRefreshCredentials < Signet::OAuth2::Client
|
48
49
|
TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
|
50
|
+
AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/auth'
|
51
|
+
REVOKE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/revoke'
|
49
52
|
extend CredentialsLoader
|
50
53
|
|
54
|
+
# Create a UserRefreshCredentials.
|
55
|
+
#
|
56
|
+
# @param json_key_io [IO] an IO from which the JSON key can be read
|
57
|
+
# @param scope [string|array|nil] the scope(s) to access
|
58
|
+
def self.make_creds(options = {})
|
59
|
+
json_key_io, scope = options.values_at(:json_key_io, :scope)
|
60
|
+
user_creds = read_json_key(json_key_io) if json_key_io
|
61
|
+
user_creds ||= {
|
62
|
+
'client_id' => ENV[CredentialsLoader::CLIENT_ID_VAR],
|
63
|
+
'client_secret' => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
|
64
|
+
'refresh_token' => ENV[CredentialsLoader::REFRESH_TOKEN_VAR]
|
65
|
+
}
|
66
|
+
|
67
|
+
new(token_credential_uri: TOKEN_CRED_URI,
|
68
|
+
client_id: user_creds['client_id'],
|
69
|
+
client_secret: user_creds['client_secret'],
|
70
|
+
refresh_token: user_creds['refresh_token'],
|
71
|
+
scope: scope)
|
72
|
+
end
|
73
|
+
|
51
74
|
# Reads the client_id, client_secret and refresh_token fields from the
|
52
75
|
# JSON key.
|
53
76
|
def self.read_json_key(json_key_io)
|
@@ -59,24 +82,38 @@ module Google
|
|
59
82
|
json_key
|
60
83
|
end
|
61
84
|
|
62
|
-
# Initializes a UserRefreshCredentials.
|
63
|
-
#
|
64
|
-
# @param json_key_io [IO] an IO from which the JSON key can be read
|
65
|
-
# @param scope [string|array|nil] the scope(s) to access
|
66
85
|
def initialize(options = {})
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
86
|
+
options ||= {}
|
87
|
+
options[:token_credential_uri] ||= TOKEN_CRED_URI
|
88
|
+
options[:authorization_uri] ||= AUTHORIZATION_URI
|
89
|
+
super(options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Revokes the credential
|
93
|
+
def revoke!(options = {})
|
94
|
+
c = options[:connection] || Faraday.default_connection
|
95
|
+
resp = c.get(REVOKE_TOKEN_URI, token: refresh_token || access_token)
|
96
|
+
case resp.status
|
97
|
+
when 200
|
98
|
+
self.access_token = nil
|
99
|
+
self.refresh_token = nil
|
100
|
+
self.expires_at = 0
|
101
|
+
else
|
102
|
+
fail(Signet::AuthorizationError,
|
103
|
+
"Unexpected error code #{resp.status}")
|
104
|
+
end
|
105
|
+
end
|
74
106
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
107
|
+
# Verifies that a credential grants the requested scope
|
108
|
+
#
|
109
|
+
# @param [Array<String>, String] required_scope
|
110
|
+
# Scope to verify
|
111
|
+
# @return [Boolean]
|
112
|
+
# True if scope is granted
|
113
|
+
def includes_scope?(required_scope)
|
114
|
+
missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
|
115
|
+
Google::Auth::ScopeUtil.normalize(scope)
|
116
|
+
missing_scope.empty?
|
80
117
|
end
|
81
118
|
end
|
82
119
|
end
|
data/lib/googleauth/version.rb
CHANGED
@@ -0,0 +1,289 @@
|
|
1
|
+
# Copyright 2014, Google Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are
|
6
|
+
# met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above
|
11
|
+
# copyright notice, this list of conditions and the following disclaimer
|
12
|
+
# in the documentation and/or other materials provided with the
|
13
|
+
# distribution.
|
14
|
+
# * Neither the name of Google Inc. nor the names of its
|
15
|
+
# contributors may be used to endorse or promote products derived from
|
16
|
+
# this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
19
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
20
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
21
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
22
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
23
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
24
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
25
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
26
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'multi_json'
|
31
|
+
require 'googleauth/signet'
|
32
|
+
require 'googleauth/user_authorizer'
|
33
|
+
require 'googleauth/user_refresh'
|
34
|
+
require 'securerandom'
|
35
|
+
|
36
|
+
module Google
|
37
|
+
module Auth
|
38
|
+
# Varation on {Google::Auth::UserAuthorizer} adapted for Rack based
|
39
|
+
# web applications.
|
40
|
+
#
|
41
|
+
# Example usage:
|
42
|
+
#
|
43
|
+
# get('/') do
|
44
|
+
# user_id = request.session['user_email']
|
45
|
+
# credentials = authorizer.get_credentials(user_id, request)
|
46
|
+
# if credentials.nil?
|
47
|
+
# redirect authorizer.get_authorization_url(user_id: user_id,
|
48
|
+
# request: request)
|
49
|
+
# end
|
50
|
+
# # Credentials are valid, can call APIs
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# get('/oauth2callback') do
|
55
|
+
# url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(
|
56
|
+
# request)
|
57
|
+
# redirect url
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# Instead of implementing the callback directly, applications are
|
61
|
+
# encouraged to use {Google::Auth::Web::AuthCallbackApp} instead.
|
62
|
+
#
|
63
|
+
# For rails apps, see {Google::Auth::ControllerHelpers}
|
64
|
+
#
|
65
|
+
# @see {Google::Auth::AuthCallbackApp}
|
66
|
+
# @see {Google::Auth::ControllerHelpers}
|
67
|
+
# @note Requires sessions are enabled
|
68
|
+
class WebUserAuthorizer < Google::Auth::UserAuthorizer
|
69
|
+
STATE_PARAM = 'state'
|
70
|
+
AUTH_CODE_KEY = 'code'
|
71
|
+
ERROR_CODE_KEY = 'error'
|
72
|
+
SESSION_ID_KEY = 'session_id'
|
73
|
+
CALLBACK_STATE_KEY = 'g-auth-callback'
|
74
|
+
CURRENT_URI_KEY = 'current_uri'
|
75
|
+
XSRF_KEY = 'g-xsrf-token'
|
76
|
+
SCOPE_KEY = 'scope'
|
77
|
+
|
78
|
+
NIL_REQUEST_ERROR = 'Request is required.'
|
79
|
+
NIL_SESSION_ERROR = 'Sessions must be enabled'
|
80
|
+
MISSING_AUTH_CODE_ERROR = 'Missing authorization code in request'
|
81
|
+
AUTHORIZATION_ERROR = 'Authorization error: %s'
|
82
|
+
INVALID_STATE_TOKEN_ERROR = 'State token does not match expected value'
|
83
|
+
|
84
|
+
class << self
|
85
|
+
attr_accessor :default
|
86
|
+
end
|
87
|
+
|
88
|
+
# Handle the result of the oauth callback. This version defers the
|
89
|
+
# exchange of the code by temporarily stashing the results in the user's
|
90
|
+
# session. This allows apps to use the generic
|
91
|
+
# {Google::Auth::WebUserAuthorizer::CallbackApp} handler for the callback
|
92
|
+
# without any additional customization.
|
93
|
+
#
|
94
|
+
# Apps that wish to handle the callback directly should use
|
95
|
+
# {#handle_auth_callback} instead.
|
96
|
+
#
|
97
|
+
# @param [Rack::Request] request
|
98
|
+
# Current request
|
99
|
+
def self.handle_auth_callback_deferred(request)
|
100
|
+
callback_state, redirect_uri = extract_callback_state(request)
|
101
|
+
request.session[CALLBACK_STATE_KEY] = MultiJson.dump(callback_state)
|
102
|
+
redirect_uri
|
103
|
+
end
|
104
|
+
|
105
|
+
# Initialize the authorizer
|
106
|
+
#
|
107
|
+
# @param [Google::Auth::ClientID] client_id
|
108
|
+
# Configured ID & secret for this application
|
109
|
+
# @param [String, Array<String>] scope
|
110
|
+
# Authorization scope to request
|
111
|
+
# @param [Google::Auth::Stores::TokenStore] token_store
|
112
|
+
# Backing storage for persisting user credentials
|
113
|
+
# @param [String] callback_uri
|
114
|
+
# URL (either absolute or relative) of the auth callback. Defaults
|
115
|
+
# to '/oauth2callback'
|
116
|
+
def initialize(client_id, scope, token_store, callback_uri = nil)
|
117
|
+
super(client_id, scope, token_store, callback_uri)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handle the result of the oauth callback. Exchanges the authorization
|
121
|
+
# code from the request and persists to storage.
|
122
|
+
#
|
123
|
+
# @param [String] user_id
|
124
|
+
# Unique ID of the user for loading/storing credentials.
|
125
|
+
# @param [Rack::Request] request
|
126
|
+
# Current request
|
127
|
+
# @return (Google::Auth::UserRefreshCredentials, String)
|
128
|
+
# credentials & next URL to redirect to
|
129
|
+
def handle_auth_callback(user_id, request)
|
130
|
+
callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
|
131
|
+
request)
|
132
|
+
WebUserAuthorizer.validate_callback_state(callback_state, request)
|
133
|
+
credentials = get_and_store_credentials_from_code(
|
134
|
+
user_id: user_id,
|
135
|
+
code: callback_state[AUTH_CODE_KEY],
|
136
|
+
scope: callback_state[SCOPE_KEY],
|
137
|
+
base_url: request.url)
|
138
|
+
[credentials, redirect_uri]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Build the URL for requesting authorization.
|
142
|
+
#
|
143
|
+
# @param [String] login_hint
|
144
|
+
# Login hint if need to authorize a specific account. Should be a
|
145
|
+
# user's email address or unique profile ID.
|
146
|
+
# @param [Rack::Request] request
|
147
|
+
# Current request
|
148
|
+
# @param [String] redirect_to
|
149
|
+
# Optional URL to proceed to after authorization complete. Defaults to
|
150
|
+
# the current URL.
|
151
|
+
# @param [String, Array<String>] scope
|
152
|
+
# Authorization scope to request. Overrides the instance scopes if
|
153
|
+
# not nil.
|
154
|
+
# @return [String]
|
155
|
+
# Authorization url
|
156
|
+
def get_authorization_url(options = {})
|
157
|
+
options = options.dup
|
158
|
+
request = options[:request]
|
159
|
+
fail NIL_REQUEST_ERROR if request.nil?
|
160
|
+
fail NIL_SESSION_ERROR if request.session.nil?
|
161
|
+
|
162
|
+
redirect_to = options[:redirect_to] || request.url
|
163
|
+
request.session[XSRF_KEY] = SecureRandom.base64
|
164
|
+
options[:state] = MultiJson.dump(
|
165
|
+
SESSION_ID_KEY => request.session[XSRF_KEY],
|
166
|
+
CURRENT_URI_KEY => redirect_to)
|
167
|
+
options[:base_url] = request.url
|
168
|
+
super(options)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Fetch stored credentials for the user.
|
172
|
+
#
|
173
|
+
# @param [String] user_id
|
174
|
+
# Unique ID of the user for loading/storing credentials.
|
175
|
+
# @param [Rack::Request] request
|
176
|
+
# Current request
|
177
|
+
# @param [Array<String>, String] scope
|
178
|
+
# If specified, only returns credentials that have all the \
|
179
|
+
# requested scopes
|
180
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
181
|
+
# Stored credentials, nil if none present
|
182
|
+
# @raise [Signet::AuthorizationError]
|
183
|
+
# May raise an error if an authorization code is present in the session
|
184
|
+
# and exchange of the code fails
|
185
|
+
def get_credentials(user_id, request, scope = nil)
|
186
|
+
if request.session.key?(CALLBACK_STATE_KEY)
|
187
|
+
# Note - in theory, no need to check required scope as this is
|
188
|
+
# expected to be called immediately after a return from authorization
|
189
|
+
state_json = request.session.delete(CALLBACK_STATE_KEY)
|
190
|
+
callback_state = MultiJson.load(state_json)
|
191
|
+
WebUserAuthorizer.validate_callback_state(callback_state, request)
|
192
|
+
get_and_store_credentials_from_code(
|
193
|
+
user_id: user_id,
|
194
|
+
code: callback_state[AUTH_CODE_KEY],
|
195
|
+
scope: callback_state[SCOPE_KEY],
|
196
|
+
base_url: request.url)
|
197
|
+
else
|
198
|
+
super(user_id, scope)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.extract_callback_state(request)
|
203
|
+
state = MultiJson.load(request[STATE_PARAM] || '{}')
|
204
|
+
redirect_uri = state[CURRENT_URI_KEY]
|
205
|
+
callback_state = {
|
206
|
+
AUTH_CODE_KEY => request[AUTH_CODE_KEY],
|
207
|
+
ERROR_CODE_KEY => request[ERROR_CODE_KEY],
|
208
|
+
SESSION_ID_KEY => state[SESSION_ID_KEY],
|
209
|
+
SCOPE_KEY => request[SCOPE_KEY]
|
210
|
+
}
|
211
|
+
[callback_state, redirect_uri]
|
212
|
+
end
|
213
|
+
|
214
|
+
# Verifies the results of an authorization callback
|
215
|
+
#
|
216
|
+
# @param [Hash] state
|
217
|
+
# Callback state
|
218
|
+
# @option state [String] AUTH_CODE_KEY
|
219
|
+
# The authorization code
|
220
|
+
# @option state [String] ERROR_CODE_KEY
|
221
|
+
# Error message if failed
|
222
|
+
# @param [Rack::Request] request
|
223
|
+
# Current request
|
224
|
+
def self.validate_callback_state(state, request)
|
225
|
+
if state[AUTH_CODE_KEY].nil?
|
226
|
+
fail Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR
|
227
|
+
elsif state[ERROR_CODE_KEY]
|
228
|
+
fail Signet::AuthorizationError,
|
229
|
+
sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
|
230
|
+
elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
|
231
|
+
fail Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Small Rack app which acts as the default callback handler for the app.
|
236
|
+
#
|
237
|
+
# To configure in Rails, add to routes.rb:
|
238
|
+
#
|
239
|
+
# match '/oauth2callback',
|
240
|
+
# to: Google::Auth::WebUserAuthorizer::CallbackApp,
|
241
|
+
# via: :all
|
242
|
+
#
|
243
|
+
# With Rackup, add to config.ru:
|
244
|
+
#
|
245
|
+
# map '/oauth2callback' do
|
246
|
+
# run Google::Auth::WebUserAuthorizer::CallbackApp
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# Or in a classic Sinatra app:
|
250
|
+
#
|
251
|
+
# get('/oauth2callback') do
|
252
|
+
# Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
|
253
|
+
# end
|
254
|
+
#
|
255
|
+
# @see {Google::Auth::WebUserAuthorizer}
|
256
|
+
class CallbackApp
|
257
|
+
LOCATION_HEADER = 'Location'
|
258
|
+
REDIR_STATUS = 302
|
259
|
+
ERROR_STATUS = 500
|
260
|
+
|
261
|
+
# Handle a rack request. Simply stores the results the authorization
|
262
|
+
# in the session temporarily and redirects back to to the previously
|
263
|
+
# saved redirect URL. Credentials can be later retrieved by calling.
|
264
|
+
# {Google::Auth::Web::WebUserAuthorizer#get_credentials}
|
265
|
+
#
|
266
|
+
# See {Google::Auth::Web::WebUserAuthorizer#get_authorization_uri}
|
267
|
+
# for how to initiate authorization requests.
|
268
|
+
#
|
269
|
+
# @param [Hash] env
|
270
|
+
# Rack environment
|
271
|
+
# @return [Array]
|
272
|
+
# HTTP response
|
273
|
+
def self.call(env)
|
274
|
+
request = Rack::Request.new(env)
|
275
|
+
return_url = WebUserAuthorizer.handle_auth_callback_deferred(request)
|
276
|
+
if return_url
|
277
|
+
[REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
|
278
|
+
else
|
279
|
+
[ERROR_STATUS, {}, ['No return URL is present in the request.']]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def call(env)
|
284
|
+
self.class.call(env)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -34,18 +34,6 @@ $LOAD_PATH.uniq!
|
|
34
34
|
require 'faraday'
|
35
35
|
require 'spec_helper'
|
36
36
|
|
37
|
-
def build_json_response(payload)
|
38
|
-
[200,
|
39
|
-
{ 'Content-Type' => 'application/json; charset=utf-8' },
|
40
|
-
MultiJson.dump(payload)]
|
41
|
-
end
|
42
|
-
|
43
|
-
def build_access_token_json(token)
|
44
|
-
build_json_response('access_token' => token,
|
45
|
-
'token_type' => 'Bearer',
|
46
|
-
'expires_in' => 3600)
|
47
|
-
end
|
48
|
-
|
49
37
|
shared_examples 'apply/apply! are OK' do
|
50
38
|
let(:auth_key) { :Authorization }
|
51
39
|
|
@@ -56,112 +44,102 @@ shared_examples 'apply/apply! are OK' do
|
|
56
44
|
# @make_auth_stubs, which should stub out the expected http behaviour of the
|
57
45
|
# auth client
|
58
46
|
describe '#fetch_access_token' do
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
b.adapter(:test, stubs)
|
64
|
-
end
|
47
|
+
let(:token) { '1/abcdef1234567890' }
|
48
|
+
let(:stub) do
|
49
|
+
make_auth_stubs access_token: token
|
50
|
+
end
|
65
51
|
|
66
|
-
|
52
|
+
it 'should set access_token to the fetched value' do
|
53
|
+
stub
|
54
|
+
@client.fetch_access_token!
|
67
55
|
expect(@client.access_token).to eq(token)
|
68
|
-
|
56
|
+
expect(stub).to have_been_requested
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should notify refresh listeners after updating' do
|
60
|
+
stub
|
61
|
+
expect do |b|
|
62
|
+
@client.on_refresh(&b)
|
63
|
+
@client.fetch_access_token!
|
64
|
+
end.to yield_with_args(have_attributes(
|
65
|
+
access_token: '1/abcdef1234567890'))
|
66
|
+
expect(stub).to have_been_requested
|
69
67
|
end
|
70
68
|
end
|
71
69
|
|
72
70
|
describe '#apply!' do
|
73
71
|
it 'should update the target hash with fetched access token' do
|
74
72
|
token = '1/abcdef1234567890'
|
75
|
-
|
76
|
-
c = Faraday.new do |b|
|
77
|
-
b.adapter(:test, stubs)
|
78
|
-
end
|
73
|
+
stub = make_auth_stubs access_token: token
|
79
74
|
|
80
75
|
md = { foo: 'bar' }
|
81
|
-
@client.apply!(md
|
76
|
+
@client.apply!(md)
|
82
77
|
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
|
83
78
|
expect(md).to eq(want)
|
84
|
-
|
79
|
+
expect(stub).to have_been_requested
|
85
80
|
end
|
86
81
|
end
|
87
82
|
|
88
83
|
describe 'updater_proc' do
|
89
84
|
it 'should provide a proc that updates a hash with the access token' do
|
90
85
|
token = '1/abcdef1234567890'
|
91
|
-
|
92
|
-
c = Faraday.new do |b|
|
93
|
-
b.adapter(:test, stubs)
|
94
|
-
end
|
95
|
-
|
86
|
+
stub = make_auth_stubs access_token: token
|
96
87
|
md = { foo: 'bar' }
|
97
88
|
the_proc = @client.updater_proc
|
98
|
-
got = the_proc.call(md
|
89
|
+
got = the_proc.call(md)
|
99
90
|
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
|
100
91
|
expect(got).to eq(want)
|
101
|
-
|
92
|
+
expect(stub).to have_been_requested
|
102
93
|
end
|
103
94
|
end
|
104
95
|
|
105
96
|
describe '#apply' do
|
106
97
|
it 'should not update the original hash with the access token' do
|
107
98
|
token = '1/abcdef1234567890'
|
108
|
-
|
109
|
-
c = Faraday.new do |b|
|
110
|
-
b.adapter(:test, stubs)
|
111
|
-
end
|
99
|
+
stub = make_auth_stubs access_token: token
|
112
100
|
|
113
101
|
md = { foo: 'bar' }
|
114
|
-
@client.apply(md
|
102
|
+
@client.apply(md)
|
115
103
|
want = { foo: 'bar' }
|
116
104
|
expect(md).to eq(want)
|
117
|
-
|
105
|
+
expect(stub).to have_been_requested
|
118
106
|
end
|
119
107
|
|
120
108
|
it 'should add the token to the returned hash' do
|
121
109
|
token = '1/abcdef1234567890'
|
122
|
-
|
123
|
-
c = Faraday.new do |b|
|
124
|
-
b.adapter(:test, stubs)
|
125
|
-
end
|
110
|
+
stub = make_auth_stubs access_token: token
|
126
111
|
|
127
112
|
md = { foo: 'bar' }
|
128
|
-
got = @client.apply(md
|
113
|
+
got = @client.apply(md)
|
129
114
|
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
|
130
115
|
expect(got).to eq(want)
|
131
|
-
|
116
|
+
expect(stub).to have_been_requested
|
132
117
|
end
|
133
118
|
|
134
119
|
it 'should not fetch a new token if the current is not expired' do
|
135
120
|
token = '1/abcdef1234567890'
|
136
|
-
|
137
|
-
c = Faraday.new do |b|
|
138
|
-
b.adapter(:test, stubs)
|
139
|
-
end
|
121
|
+
stub = make_auth_stubs access_token: token
|
140
122
|
|
141
123
|
n = 5 # arbitrary
|
142
124
|
n.times do |_t|
|
143
125
|
md = { foo: 'bar' }
|
144
|
-
got = @client.apply(md
|
126
|
+
got = @client.apply(md)
|
145
127
|
want = { :foo => 'bar', auth_key => "Bearer #{token}" }
|
146
128
|
expect(got).to eq(want)
|
147
129
|
end
|
148
|
-
|
130
|
+
expect(stub).to have_been_requested
|
149
131
|
end
|
150
132
|
|
151
133
|
it 'should fetch a new token if the current one is expired' do
|
152
134
|
token_1 = '1/abcdef1234567890'
|
153
|
-
token_2 = '2/
|
135
|
+
token_2 = '2/abcdef1234567891'
|
154
136
|
|
155
137
|
[token_1, token_2].each do |t|
|
156
|
-
|
157
|
-
c = Faraday.new do |b|
|
158
|
-
b.adapter(:test, stubs)
|
159
|
-
end
|
138
|
+
make_auth_stubs access_token: t
|
160
139
|
md = { foo: 'bar' }
|
161
|
-
got = @client.apply(md
|
140
|
+
got = @client.apply(md)
|
162
141
|
want = { :foo => 'bar', auth_key => "Bearer #{t}" }
|
163
142
|
expect(got).to eq(want)
|
164
|
-
stubs.verify_stubbed_calls
|
165
143
|
@client.expires_at -= 3601 # default is to expire in 1hr
|
166
144
|
end
|
167
145
|
end
|