googleauth 0.4.2 → 0.5.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/.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
|