googleauth 0.1.0 → 0.16.2

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.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/CONTRIBUTING.md +74 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  6. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  7. data/.github/renovate.json +6 -0
  8. data/.github/sync-repo-settings.yaml +18 -0
  9. data/.github/workflows/ci.yml +55 -0
  10. data/.github/workflows/release-please.yml +39 -0
  11. data/.gitignore +3 -0
  12. data/.kokoro/populate-secrets.sh +76 -0
  13. data/.kokoro/release.cfg +52 -0
  14. data/.kokoro/release.sh +18 -0
  15. data/.kokoro/trampoline_v2.sh +489 -0
  16. data/.repo-metadata.json +5 -0
  17. data/.rubocop.yml +17 -0
  18. data/.toys/.toys.rb +45 -0
  19. data/.toys/ci.rb +43 -0
  20. data/.toys/kokoro/.toys.rb +66 -0
  21. data/.toys/kokoro/publish-docs.rb +67 -0
  22. data/.toys/kokoro/publish-gem.rb +53 -0
  23. data/.toys/linkinator.rb +43 -0
  24. data/.trampolinerc +48 -0
  25. data/CHANGELOG.md +199 -0
  26. data/CODE_OF_CONDUCT.md +43 -0
  27. data/Gemfile +22 -1
  28. data/{COPYING → LICENSE} +0 -0
  29. data/README.md +140 -17
  30. data/googleauth.gemspec +28 -28
  31. data/integration/helper.rb +31 -0
  32. data/integration/id_tokens/key_source_test.rb +74 -0
  33. data/lib/googleauth.rb +7 -37
  34. data/lib/googleauth/application_default.rb +81 -0
  35. data/lib/googleauth/client_id.rb +104 -0
  36. data/lib/googleauth/compute_engine.rb +73 -26
  37. data/lib/googleauth/credentials.rb +561 -0
  38. data/lib/googleauth/credentials_loader.rb +207 -0
  39. data/lib/googleauth/default_credentials.rb +93 -0
  40. data/lib/googleauth/iam.rb +75 -0
  41. data/lib/googleauth/id_tokens.rb +233 -0
  42. data/lib/googleauth/id_tokens/errors.rb +71 -0
  43. data/lib/googleauth/id_tokens/key_sources.rb +396 -0
  44. data/lib/googleauth/id_tokens/verifier.rb +142 -0
  45. data/lib/googleauth/json_key_reader.rb +50 -0
  46. data/lib/googleauth/scope_util.rb +61 -0
  47. data/lib/googleauth/service_account.rb +177 -67
  48. data/lib/googleauth/signet.rb +69 -8
  49. data/lib/googleauth/stores/file_token_store.rb +65 -0
  50. data/lib/googleauth/stores/redis_token_store.rb +96 -0
  51. data/lib/googleauth/token_store.rb +69 -0
  52. data/lib/googleauth/user_authorizer.rb +285 -0
  53. data/lib/googleauth/user_refresh.rb +129 -0
  54. data/lib/googleauth/version.rb +1 -1
  55. data/lib/googleauth/web_user_authorizer.rb +295 -0
  56. data/spec/googleauth/apply_auth_examples.rb +96 -94
  57. data/spec/googleauth/client_id_spec.rb +160 -0
  58. data/spec/googleauth/compute_engine_spec.rb +125 -55
  59. data/spec/googleauth/credentials_spec.rb +600 -0
  60. data/spec/googleauth/get_application_default_spec.rb +232 -80
  61. data/spec/googleauth/iam_spec.rb +80 -0
  62. data/spec/googleauth/scope_util_spec.rb +77 -0
  63. data/spec/googleauth/service_account_spec.rb +422 -68
  64. data/spec/googleauth/signet_spec.rb +101 -25
  65. data/spec/googleauth/stores/file_token_store_spec.rb +57 -0
  66. data/spec/googleauth/stores/redis_token_store_spec.rb +50 -0
  67. data/spec/googleauth/stores/store_examples.rb +58 -0
  68. data/spec/googleauth/user_authorizer_spec.rb +343 -0
  69. data/spec/googleauth/user_refresh_spec.rb +359 -0
  70. data/spec/googleauth/web_user_authorizer_spec.rb +172 -0
  71. data/spec/spec_helper.rb +51 -10
  72. data/test/helper.rb +33 -0
  73. data/test/id_tokens/key_sources_test.rb +240 -0
  74. data/test/id_tokens/verifier_test.rb +269 -0
  75. metadata +114 -75
  76. data/.travis.yml +0 -18
  77. data/CONTRIBUTING.md +0 -32
  78. data/Rakefile +0 -15
@@ -0,0 +1,129 @@
1
+ # Copyright 2015, 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 "googleauth/signet"
31
+ require "googleauth/credentials_loader"
32
+ require "googleauth/scope_util"
33
+ require "multi_json"
34
+
35
+ module Google
36
+ # Module Auth provides classes that provide Google-specific authorization
37
+ # used to access Google APIs.
38
+ module Auth
39
+ # Authenticates requests using User Refresh credentials.
40
+ #
41
+ # This class allows authorizing requests from user refresh tokens.
42
+ #
43
+ # This the end of the result of a 3LO flow. E.g, the end result of
44
+ # 'gcloud auth login' saves a file with these contents in well known
45
+ # location
46
+ #
47
+ # cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
48
+ class UserRefreshCredentials < Signet::OAuth2::Client
49
+ TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze
50
+ AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/auth".freeze
51
+ REVOKE_TOKEN_URI = "https://oauth2.googleapis.com/revoke".freeze
52
+ extend CredentialsLoader
53
+ attr_reader :project_id
54
+
55
+ # Create a UserRefreshCredentials.
56
+ #
57
+ # @param json_key_io [IO] an IO from which the JSON key can be read
58
+ # @param scope [string|array|nil] the scope(s) to access
59
+ def self.make_creds options = {}
60
+ json_key_io, scope = options.values_at :json_key_io, :scope
61
+ user_creds = read_json_key json_key_io if json_key_io
62
+ user_creds ||= {
63
+ "client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR],
64
+ "client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
65
+ "refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
66
+ "project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR]
67
+ }
68
+
69
+ new(token_credential_uri: TOKEN_CRED_URI,
70
+ client_id: user_creds["client_id"],
71
+ client_secret: user_creds["client_secret"],
72
+ refresh_token: user_creds["refresh_token"],
73
+ project_id: user_creds["project_id"],
74
+ scope: scope)
75
+ .configure_connection(options)
76
+ end
77
+
78
+ # Reads the client_id, client_secret and refresh_token fields from the
79
+ # JSON key.
80
+ def self.read_json_key json_key_io
81
+ json_key = MultiJson.load json_key_io.read
82
+ wanted = ["client_id", "client_secret", "refresh_token"]
83
+ wanted.each do |key|
84
+ raise "the json is missing the #{key} field" unless json_key.key? key
85
+ end
86
+ json_key
87
+ end
88
+
89
+ def initialize options = {}
90
+ options ||= {}
91
+ options[:token_credential_uri] ||= TOKEN_CRED_URI
92
+ options[:authorization_uri] ||= AUTHORIZATION_URI
93
+ @project_id = options[:project_id]
94
+ @project_id ||= CredentialsLoader.load_gcloud_project_id
95
+ super options
96
+ end
97
+
98
+ # Revokes the credential
99
+ def revoke! options = {}
100
+ c = options[:connection] || Faraday.default_connection
101
+
102
+ retry_with_error do
103
+ resp = c.post(REVOKE_TOKEN_URI, token: refresh_token || access_token)
104
+ case resp.status
105
+ when 200
106
+ self.access_token = nil
107
+ self.refresh_token = nil
108
+ self.expires_at = 0
109
+ else
110
+ raise(Signet::AuthorizationError,
111
+ "Unexpected error code #{resp.status}")
112
+ end
113
+ end
114
+ end
115
+
116
+ # Verifies that a credential grants the requested scope
117
+ #
118
+ # @param [Array<String>, String] required_scope
119
+ # Scope to verify
120
+ # @return [Boolean]
121
+ # True if scope is granted
122
+ def includes_scope? required_scope
123
+ missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
124
+ Google::Auth::ScopeUtil.normalize(scope)
125
+ missing_scope.empty?
126
+ end
127
+ end
128
+ end
129
+ end
@@ -31,6 +31,6 @@ module Google
31
31
  # Module Auth provides classes that provide Google-specific authorization
32
32
  # used to access Google APIs.
33
33
  module Auth
34
- VERSION = '0.1.0'
34
+ VERSION = "0.16.2".freeze
35
35
  end
36
36
  end
@@ -0,0 +1,295 @@
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::WebUserAuthorizer::CallbackApp} instead.
62
+ #
63
+ # @see CallbackApp
64
+ # @note Requires sessions are enabled
65
+ class WebUserAuthorizer < Google::Auth::UserAuthorizer
66
+ STATE_PARAM = "state".freeze
67
+ AUTH_CODE_KEY = "code".freeze
68
+ ERROR_CODE_KEY = "error".freeze
69
+ SESSION_ID_KEY = "session_id".freeze
70
+ CALLBACK_STATE_KEY = "g-auth-callback".freeze
71
+ CURRENT_URI_KEY = "current_uri".freeze
72
+ XSRF_KEY = "g-xsrf-token".freeze
73
+ SCOPE_KEY = "scope".freeze
74
+
75
+ NIL_REQUEST_ERROR = "Request is required.".freeze
76
+ NIL_SESSION_ERROR = "Sessions must be enabled".freeze
77
+ MISSING_AUTH_CODE_ERROR = "Missing authorization code in request".freeze
78
+ AUTHORIZATION_ERROR = "Authorization error: %s".freeze
79
+ INVALID_STATE_TOKEN_ERROR =
80
+ "State token does not match expected value".freeze
81
+
82
+ class << self
83
+ attr_accessor :default
84
+ end
85
+
86
+ # Handle the result of the oauth callback. This version defers the
87
+ # exchange of the code by temporarily stashing the results in the user's
88
+ # session. This allows apps to use the generic
89
+ # {Google::Auth::WebUserAuthorizer::CallbackApp} handler for the callback
90
+ # without any additional customization.
91
+ #
92
+ # Apps that wish to handle the callback directly should use
93
+ # {#handle_auth_callback} instead.
94
+ #
95
+ # @param [Rack::Request] request
96
+ # Current request
97
+ def self.handle_auth_callback_deferred request
98
+ callback_state, redirect_uri = extract_callback_state request
99
+ request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
100
+ redirect_uri
101
+ end
102
+
103
+ # Initialize the authorizer
104
+ #
105
+ # @param [Google::Auth::ClientID] client_id
106
+ # Configured ID & secret for this application
107
+ # @param [String, Array<String>] scope
108
+ # Authorization scope to request
109
+ # @param [Google::Auth::Stores::TokenStore] token_store
110
+ # Backing storage for persisting user credentials
111
+ # @param [String] callback_uri
112
+ # URL (either absolute or relative) of the auth callback. Defaults
113
+ # to '/oauth2callback'
114
+ def initialize client_id, scope, token_store, callback_uri = nil
115
+ super client_id, scope, token_store, callback_uri
116
+ end
117
+
118
+ # Handle the result of the oauth callback. Exchanges the authorization
119
+ # code from the request and persists to storage.
120
+ #
121
+ # @param [String] user_id
122
+ # Unique ID of the user for loading/storing credentials.
123
+ # @param [Rack::Request] request
124
+ # Current request
125
+ # @return (Google::Auth::UserRefreshCredentials, String)
126
+ # credentials & next URL to redirect to
127
+ def handle_auth_callback user_id, request
128
+ callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
129
+ request
130
+ )
131
+ WebUserAuthorizer.validate_callback_state callback_state, request
132
+ credentials = get_and_store_credentials_from_code(
133
+ user_id: user_id,
134
+ code: callback_state[AUTH_CODE_KEY],
135
+ scope: callback_state[SCOPE_KEY],
136
+ base_url: request.url
137
+ )
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
+ # @param [Hash] state
155
+ # Optional key-values to be returned to the oauth callback.
156
+ # @return [String]
157
+ # Authorization url
158
+ def get_authorization_url options = {}
159
+ options = options.dup
160
+ request = options[:request]
161
+ raise NIL_REQUEST_ERROR if request.nil?
162
+ raise NIL_SESSION_ERROR if request.session.nil?
163
+
164
+ state = options[:state] || {}
165
+
166
+ redirect_to = options[:redirect_to] || request.url
167
+ request.session[XSRF_KEY] = SecureRandom.base64
168
+ options[:state] = MultiJson.dump(state.merge(
169
+ SESSION_ID_KEY => request.session[XSRF_KEY],
170
+ CURRENT_URI_KEY => redirect_to
171
+ ))
172
+ options[:base_url] = request.url
173
+ super options
174
+ end
175
+
176
+ # Fetch stored credentials for the user from the given request session.
177
+ #
178
+ # @param [String] user_id
179
+ # Unique ID of the user for loading/storing credentials.
180
+ # @param [Rack::Request] request
181
+ # Current request. Optional. If omitted, this will attempt to fall back
182
+ # on the base class behavior of reading from the token store.
183
+ # @param [Array<String>, String] scope
184
+ # If specified, only returns credentials that have all the \
185
+ # requested scopes
186
+ # @return [Google::Auth::UserRefreshCredentials]
187
+ # Stored credentials, nil if none present
188
+ # @raise [Signet::AuthorizationError]
189
+ # May raise an error if an authorization code is present in the session
190
+ # and exchange of the code fails
191
+ def get_credentials user_id, request = nil, scope = nil
192
+ if request&.session&.key? CALLBACK_STATE_KEY
193
+ # Note - in theory, no need to check required scope as this is
194
+ # expected to be called immediately after a return from authorization
195
+ state_json = request.session.delete CALLBACK_STATE_KEY
196
+ callback_state = MultiJson.load state_json
197
+ WebUserAuthorizer.validate_callback_state callback_state, request
198
+ get_and_store_credentials_from_code(
199
+ user_id: user_id,
200
+ code: callback_state[AUTH_CODE_KEY],
201
+ scope: callback_state[SCOPE_KEY],
202
+ base_url: request.url
203
+ )
204
+ else
205
+ super user_id, scope
206
+ end
207
+ end
208
+
209
+ def self.extract_callback_state request
210
+ state = MultiJson.load(request[STATE_PARAM] || "{}")
211
+ redirect_uri = state[CURRENT_URI_KEY]
212
+ callback_state = {
213
+ AUTH_CODE_KEY => request[AUTH_CODE_KEY],
214
+ ERROR_CODE_KEY => request[ERROR_CODE_KEY],
215
+ SESSION_ID_KEY => state[SESSION_ID_KEY],
216
+ SCOPE_KEY => request[SCOPE_KEY]
217
+ }
218
+ [callback_state, redirect_uri]
219
+ end
220
+
221
+ # Verifies the results of an authorization callback
222
+ #
223
+ # @param [Hash] state
224
+ # Callback state
225
+ # @option state [String] AUTH_CODE_KEY
226
+ # The authorization code
227
+ # @option state [String] ERROR_CODE_KEY
228
+ # Error message if failed
229
+ # @param [Rack::Request] request
230
+ # Current request
231
+ def self.validate_callback_state state, request
232
+ raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
233
+ if state[ERROR_CODE_KEY]
234
+ raise Signet::AuthorizationError,
235
+ format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
236
+ elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
237
+ raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
238
+ end
239
+ end
240
+
241
+ # Small Rack app which acts as the default callback handler for the app.
242
+ #
243
+ # To configure in Rails, add to routes.rb:
244
+ #
245
+ # match '/oauth2callback',
246
+ # to: Google::Auth::WebUserAuthorizer::CallbackApp,
247
+ # via: :all
248
+ #
249
+ # With Rackup, add to config.ru:
250
+ #
251
+ # map '/oauth2callback' do
252
+ # run Google::Auth::WebUserAuthorizer::CallbackApp
253
+ # end
254
+ #
255
+ # Or in a classic Sinatra app:
256
+ #
257
+ # get('/oauth2callback') do
258
+ # Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
259
+ # end
260
+ #
261
+ # @see Google::Auth::WebUserAuthorizer
262
+ class CallbackApp
263
+ LOCATION_HEADER = "Location".freeze
264
+ REDIR_STATUS = 302
265
+ ERROR_STATUS = 500
266
+
267
+ # Handle a rack request. Simply stores the results the authorization
268
+ # in the session temporarily and redirects back to to the previously
269
+ # saved redirect URL. Credentials can be later retrieved by calling.
270
+ # {Google::Auth::Web::WebUserAuthorizer#get_credentials}
271
+ #
272
+ # See {Google::Auth::Web::WebUserAuthorizer#get_authorization_uri}
273
+ # for how to initiate authorization requests.
274
+ #
275
+ # @param [Hash] env
276
+ # Rack environment
277
+ # @return [Array]
278
+ # HTTP response
279
+ def self.call env
280
+ request = Rack::Request.new env
281
+ return_url = WebUserAuthorizer.handle_auth_callback_deferred request
282
+ if return_url
283
+ [REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
284
+ else
285
+ [ERROR_STATUS, {}, ["No return URL is present in the request."]]
286
+ end
287
+ end
288
+
289
+ def call env
290
+ self.class.call env
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -27,141 +27,143 @@
27
27
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
 
30
- spec_dir = File.expand_path(File.join(File.dirname(__FILE__)))
31
- $LOAD_PATH.unshift(spec_dir)
30
+ spec_dir = File.expand_path File.join(File.dirname(__FILE__))
31
+ $LOAD_PATH.unshift spec_dir
32
32
  $LOAD_PATH.uniq!
33
33
 
34
- require 'faraday'
35
- require 'spec_helper'
34
+ require "faraday"
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
37
+ shared_examples "apply/apply! are OK" do
38
+ let(:auth_key) { :authorization }
48
39
 
49
- WANTED_AUTH_KEY = :Authorization
50
-
51
- shared_examples 'apply/apply! are OK' do
52
40
  # tests that use these examples need to define
53
41
  #
54
42
  # @client which should be an auth client
55
43
  #
56
44
  # @make_auth_stubs, which should stub out the expected http behaviour of the
57
45
  # auth client
58
- describe '#fetch_access_token' do
59
- it 'should set access_token to the fetched value' do
60
- token = '1/abcdef1234567890'
61
- stubs = make_auth_stubs access_token: token
62
- c = Faraday.new do |b|
63
- b.adapter(:test, stubs)
64
- end
46
+ describe "#fetch_access_token" do
47
+ let(:token) { "1/abcdef1234567890" }
48
+ let :access_stub do
49
+ make_auth_stubs access_token: token
50
+ end
51
+ let :id_stub do
52
+ make_auth_stubs id_token: token
53
+ end
65
54
 
66
- @client.fetch_access_token!(connection: c)
55
+ it "should set access_token to the fetched value" do
56
+ access_stub
57
+ @client.fetch_access_token!
67
58
  expect(@client.access_token).to eq(token)
68
- stubs.verify_stubbed_calls
59
+ expect(access_stub).to have_been_requested
60
+ end
61
+
62
+ it "should set id_token to the fetched value" do
63
+ skip unless @id_client
64
+ id_stub
65
+ @id_client.fetch_access_token!
66
+ expect(@id_client.id_token).to eq(token)
67
+ expect(id_stub).to have_been_requested
68
+ end
69
+
70
+ it "should notify refresh listeners after updating" do
71
+ access_stub
72
+ expect do |b|
73
+ @client.on_refresh(&b)
74
+ @client.fetch_access_token!
75
+ end.to yield_with_args(have_attributes(
76
+ access_token: "1/abcdef1234567890"
77
+ ))
78
+ expect(access_stub).to have_been_requested
69
79
  end
70
80
  end
71
81
 
72
- describe '#apply!' do
73
- it 'should update the target hash with fetched access token' do
74
- token = '1/abcdef1234567890'
75
- stubs = make_auth_stubs access_token: token
76
- c = Faraday.new do |b|
77
- b.adapter(:test, stubs)
78
- end
82
+ describe "#apply!" do
83
+ it "should update the target hash with fetched access token" do
84
+ token = "1/abcdef1234567890"
85
+ stub = make_auth_stubs access_token: token
79
86
 
80
- md = { foo: 'bar' }
81
- @client.apply!(md, connection: c)
82
- want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
87
+ md = { foo: "bar" }
88
+ @client.apply! md
89
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
83
90
  expect(md).to eq(want)
84
- stubs.verify_stubbed_calls
91
+ expect(stub).to have_been_requested
85
92
  end
86
- end
87
93
 
88
- describe 'updater_proc' do
89
- it 'should provide a proc that updates a hash with the access token' do
90
- token = '1/abcdef1234567890'
91
- stubs = make_auth_stubs access_token: token
92
- c = Faraday.new do |b|
93
- b.adapter(:test, stubs)
94
- end
94
+ it "should update the target hash with fetched ID token" do
95
+ skip unless @id_client
96
+ token = "1/abcdef1234567890"
97
+ stub = make_auth_stubs id_token: token
95
98
 
96
- md = { foo: 'bar' }
99
+ md = { foo: "bar" }
100
+ @id_client.apply! md
101
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
102
+ expect(md).to eq(want)
103
+ expect(stub).to have_been_requested
104
+ end
105
+ end
106
+
107
+ describe "updater_proc" do
108
+ it "should provide a proc that updates a hash with the access token" do
109
+ token = "1/abcdef1234567890"
110
+ stub = make_auth_stubs access_token: token
111
+ md = { foo: "bar" }
97
112
  the_proc = @client.updater_proc
98
- got = the_proc.call(md, connection: c)
99
- want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
113
+ got = the_proc.call md
114
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
100
115
  expect(got).to eq(want)
101
- stubs.verify_stubbed_calls
116
+ expect(stub).to have_been_requested
102
117
  end
103
118
  end
104
119
 
105
- describe '#apply' do
106
- it 'should not update the original hash with the access token' do
107
- token = '1/abcdef1234567890'
108
- stubs = make_auth_stubs access_token: token
109
- c = Faraday.new do |b|
110
- b.adapter(:test, stubs)
111
- end
120
+ describe "#apply" do
121
+ it "should not update the original hash with the access token" do
122
+ token = "1/abcdef1234567890"
123
+ stub = make_auth_stubs access_token: token
112
124
 
113
- md = { foo: 'bar' }
114
- @client.apply(md, connection: c)
115
- want = { foo: 'bar' }
125
+ md = { foo: "bar" }
126
+ @client.apply md
127
+ want = { foo: "bar" }
116
128
  expect(md).to eq(want)
117
- stubs.verify_stubbed_calls
129
+ expect(stub).to have_been_requested
118
130
  end
119
131
 
120
- it 'should add the token to the returned hash' do
121
- token = '1/abcdef1234567890'
122
- stubs = make_auth_stubs access_token: token
123
- c = Faraday.new do |b|
124
- b.adapter(:test, stubs)
125
- end
132
+ it "should add the token to the returned hash" do
133
+ token = "1/abcdef1234567890"
134
+ stub = make_auth_stubs access_token: token
126
135
 
127
- md = { foo: 'bar' }
128
- got = @client.apply(md, connection: c)
129
- want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
136
+ md = { foo: "bar" }
137
+ got = @client.apply md
138
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
130
139
  expect(got).to eq(want)
131
- stubs.verify_stubbed_calls
140
+ expect(stub).to have_been_requested
132
141
  end
133
142
 
134
- it 'should not fetch a new token if the current is not expired' do
135
- token = '1/abcdef1234567890'
136
- stubs = make_auth_stubs access_token: token
137
- c = Faraday.new do |b|
138
- b.adapter(:test, stubs)
139
- end
143
+ it "should not fetch a new token if the current is not expired" do
144
+ token = "1/abcdef1234567890"
145
+ stub = make_auth_stubs access_token: token
140
146
 
141
147
  n = 5 # arbitrary
142
148
  n.times do |_t|
143
- md = { foo: 'bar' }
144
- got = @client.apply(md, connection: c)
145
- want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" }
149
+ md = { foo: "bar" }
150
+ got = @client.apply md
151
+ want = { :foo => "bar", auth_key => "Bearer #{token}" }
146
152
  expect(got).to eq(want)
147
153
  end
148
- stubs.verify_stubbed_calls
154
+ expect(stub).to have_been_requested
149
155
  end
150
156
 
151
- it 'should fetch a new token if the current one is expired' do
152
- token_1 = '1/abcdef1234567890'
153
- token_2 = '2/abcdef1234567890'
154
-
155
- [token_1, token_2].each do |t|
156
- stubs = make_auth_stubs access_token: t
157
- c = Faraday.new do |b|
158
- b.adapter(:test, stubs)
159
- end
160
- md = { foo: 'bar' }
161
- got = @client.apply(md, connection: c)
162
- want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{t}" }
157
+ it "should fetch a new token if the current one is expired" do
158
+ token1 = "1/abcdef1234567890"
159
+ token2 = "2/abcdef1234567891"
160
+
161
+ [token1, token2].each do |t|
162
+ make_auth_stubs access_token: t
163
+ md = { foo: "bar" }
164
+ got = @client.apply md
165
+ want = { :foo => "bar", auth_key => "Bearer #{t}" }
163
166
  expect(got).to eq(want)
164
- stubs.verify_stubbed_calls
165
167
  @client.expires_at -= 3601 # default is to expire in 1hr
166
168
  end
167
169
  end