googleauth 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -46,7 +46,7 @@ module Google
46
46
  #
47
47
  # cf [Application Default Credentials](http://goo.gl/mkAHpZ)
48
48
  class ServiceAccountCredentials < Signet::OAuth2::Client
49
- TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
49
+ TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze
50
50
  extend CredentialsLoader
51
51
 
52
52
  # Creates a ServiceAccountCredentials.
@@ -73,8 +73,8 @@ module Google
73
73
  # JSON key.
74
74
  def self.read_json_key(json_key_io)
75
75
  json_key = MultiJson.load(json_key_io.read)
76
- fail 'missing client_email' unless json_key.key?('client_email')
77
- fail 'missing private_key' unless json_key.key?('private_key')
76
+ raise 'missing client_email' unless json_key.key?('client_email')
77
+ raise 'missing private_key' unless json_key.key?('private_key')
78
78
  [json_key['private_key'], json_key['client_email']]
79
79
  end
80
80
 
@@ -119,8 +119,8 @@ module Google
119
119
  class ServiceAccountJwtHeaderCredentials
120
120
  JWT_AUD_URI_KEY = :jwt_aud_uri
121
121
  AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY
122
- TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token'
123
- SIGNING_ALGORITHM = 'RS256'
122
+ TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze
123
+ SIGNING_ALGORITHM = 'RS256'.freeze
124
124
  EXPIRY = 60
125
125
  extend CredentialsLoader
126
126
 
@@ -139,8 +139,8 @@ module Google
139
139
  # JSON key.
140
140
  def self.read_json_key(json_key_io)
141
141
  json_key = MultiJson.load(json_key_io.read)
142
- fail 'missing client_email' unless json_key.key?('client_email')
143
- fail 'missing private_key' unless json_key.key?('private_key')
142
+ raise 'missing client_email' unless json_key.key?('client_email')
143
+ raise 'missing private_key' unless json_key.key?('private_key')
144
144
  [json_key['private_key'], json_key['client_email']]
145
145
  end
146
146
 
@@ -64,7 +64,7 @@ module Signet
64
64
  @refresh_listeners << block
65
65
  end
66
66
 
67
- alias_method :orig_fetch_access_token!, :fetch_access_token!
67
+ alias orig_fetch_access_token! fetch_access_token!
68
68
  def fetch_access_token!(options = {})
69
69
  info = orig_fetch_access_token!(options)
70
70
  notify_refresh_listeners
@@ -77,6 +77,27 @@ module Signet
77
77
  block.call(self)
78
78
  end
79
79
  end
80
+
81
+ def retry_with_error(max_retry_count = 5)
82
+ retry_count = 0
83
+
84
+ begin
85
+ yield
86
+ rescue => e
87
+ if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
88
+ raise e
89
+ end
90
+
91
+ if retry_count < max_retry_count
92
+ retry_count += 1
93
+ sleep retry_count * 0.3
94
+ retry
95
+ else
96
+ msg = "Unexpected error: #{e.inspect}"
97
+ raise(Signet::AuthorizationError, msg)
98
+ end
99
+ end
100
+ end
80
101
  end
81
102
  end
82
103
  end
@@ -37,7 +37,7 @@ module Google
37
37
  # are stored as JSON using the supplied key, prefixed with
38
38
  # `g-user-token:`
39
39
  class RedisTokenStore < Google::Auth::TokenStore
40
- DEFAULT_KEY_PREFIX = 'g-user-token:'
40
+ DEFAULT_KEY_PREFIX = 'g-user-token:'.freeze
41
41
 
42
42
  # Create a new store with the supplied redis client.
43
43
  #
@@ -51,12 +51,12 @@ module Google
51
51
  def initialize(options = {})
52
52
  redis = options.delete(:redis)
53
53
  prefix = options.delete(:prefix)
54
- case redis
55
- when Redis
56
- @redis = redis
57
- else
58
- @redis = Redis.new(options)
59
- end
54
+ @redis = case redis
55
+ when Redis
56
+ redis
57
+ else
58
+ Redis.new(options)
59
+ end
60
60
  @prefix = prefix || DEFAULT_KEY_PREFIX
61
61
  end
62
62
 
@@ -44,7 +44,7 @@ module Google
44
44
  # @return [String]
45
45
  # The loaded token data.
46
46
  def load(_id)
47
- fail 'Not implemented'
47
+ raise 'Not implemented'
48
48
  end
49
49
 
50
50
  # Put the token data into storage for the given ID.
@@ -54,7 +54,7 @@ module Google
54
54
  # @param [String] token
55
55
  # The token data to store.
56
56
  def store(_id, _token)
57
- fail 'Not implemented'
57
+ raise 'Not implemented'
58
58
  end
59
59
 
60
60
  # Remove the token data from storage for the given ID.
@@ -62,7 +62,7 @@ module Google
62
62
  # @param [String] id
63
63
  # ID of the token data to delete
64
64
  def delete(_id)
65
- fail 'Not implemented'
65
+ raise 'Not implemented'
66
66
  end
67
67
  end
68
68
  end
@@ -53,13 +53,13 @@ module Google
53
53
  # ...
54
54
  class UserAuthorizer
55
55
  MISMATCHED_CLIENT_ID_ERROR =
56
- 'Token client ID of %s does not match configured client id %s'
57
- NIL_CLIENT_ID_ERROR = 'Client id can not be nil.'
58
- NIL_SCOPE_ERROR = 'Scope can not be nil.'
59
- NIL_USER_ID_ERROR = 'User ID can not be nil.'
60
- NIL_TOKEN_STORE_ERROR = 'Can not call method if token store is nil'
56
+ 'Token client ID of %s does not match configured client id %s'.freeze
57
+ NIL_CLIENT_ID_ERROR = 'Client id can not be nil.'.freeze
58
+ NIL_SCOPE_ERROR = 'Scope can not be nil.'.freeze
59
+ NIL_USER_ID_ERROR = 'User ID can not be nil.'.freeze
60
+ NIL_TOKEN_STORE_ERROR = 'Can not call method if token store is nil'.freeze
61
61
  MISSING_ABSOLUTE_URL_ERROR =
62
- 'Absolute base url required for relative callback url "%s"'
62
+ 'Absolute base url required for relative callback url "%s"'.freeze
63
63
 
64
64
  # Initialize the authorizer
65
65
  #
@@ -73,8 +73,8 @@ module Google
73
73
  # URL (either absolute or relative) of the auth callback.
74
74
  # Defaults to '/oauth2callback'
75
75
  def initialize(client_id, scope, token_store, callback_uri = nil)
76
- fail NIL_CLIENT_ID_ERROR if client_id.nil?
77
- fail NIL_SCOPE_ERROR if scope.nil?
76
+ raise NIL_CLIENT_ID_ERROR if client_id.nil?
77
+ raise NIL_SCOPE_ERROR if scope.nil?
78
78
 
79
79
  @client_id = client_id
80
80
  @scope = Array(scope)
@@ -102,7 +102,8 @@ module Google
102
102
  credentials = UserRefreshCredentials.new(
103
103
  client_id: @client_id.id,
104
104
  client_secret: @client_id.secret,
105
- scope: scope)
105
+ scope: scope
106
+ )
106
107
  redirect_uri = redirect_uri_for(options[:base_url])
107
108
  url = credentials.authorization_uri(access_type: 'offline',
108
109
  redirect_uri: redirect_uri,
@@ -123,17 +124,13 @@ module Google
123
124
  # @return [Google::Auth::UserRefreshCredentials]
124
125
  # Stored credentials, nil if none present
125
126
  def get_credentials(user_id, scope = nil)
126
- fail NIL_USER_ID_ERROR if user_id.nil?
127
- fail NIL_TOKEN_STORE_ERROR if @token_store.nil?
128
-
129
- scope ||= @scope
130
- saved_token = @token_store.load(user_id)
127
+ saved_token = stored_token(user_id)
131
128
  return nil if saved_token.nil?
132
129
  data = MultiJson.load(saved_token)
133
130
 
134
131
  if data.fetch('client_id', @client_id.id) != @client_id.id
135
- fail sprintf(MISMATCHED_CLIENT_ID_ERROR,
136
- data['client_id'], @client_id.id)
132
+ raise sprintf(MISMATCHED_CLIENT_ID_ERROR,
133
+ data['client_id'], @client_id.id)
137
134
  end
138
135
 
139
136
  credentials = UserRefreshCredentials.new(
@@ -142,10 +139,11 @@ module Google
142
139
  scope: data['scope'] || @scope,
143
140
  access_token: data['access_token'],
144
141
  refresh_token: data['refresh_token'],
145
- expires_at: data.fetch('expiration_time_millis', 0) / 1000)
142
+ expires_at: data.fetch('expiration_time_millis', 0) / 1000
143
+ )
144
+ scope ||= @scope
146
145
  if credentials.includes_scope?(scope)
147
- monitor_credentials(user_id, credentials)
148
- return credentials
146
+ return monitor_credentials(user_id, credentials)
149
147
  end
150
148
  nil
151
149
  end
@@ -174,7 +172,8 @@ module Google
174
172
  client_id: @client_id.id,
175
173
  client_secret: @client_id.secret,
176
174
  redirect_uri: redirect_uri_for(base_url),
177
- scope: scope)
175
+ scope: scope
176
+ )
178
177
  credentials.code = code
179
178
  credentials.fetch_access_token!({})
180
179
  monitor_credentials(user_id, credentials)
@@ -234,13 +233,26 @@ module Google
234
233
  access_token: credentials.access_token,
235
234
  refresh_token: credentials.refresh_token,
236
235
  scope: credentials.scope,
237
- expiration_time_millis: (credentials.expires_at.to_i) * 1000)
236
+ expiration_time_millis: credentials.expires_at.to_i * 1000
237
+ )
238
238
  @token_store.store(user_id, json)
239
239
  credentials
240
240
  end
241
241
 
242
242
  private
243
243
 
244
+ # @private Fetch stored token with given user_id
245
+ #
246
+ # @param [String] user_id
247
+ # Unique ID of the user for loading/storing credentials.
248
+ # @return [String] The saved token from @token_store
249
+ def stored_token(user_id)
250
+ raise NIL_USER_ID_ERROR if user_id.nil?
251
+ raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
252
+
253
+ @token_store.load(user_id)
254
+ end
255
+
244
256
  # Begin watching a credential for refreshes so the access token can be
245
257
  # saved.
246
258
  #
@@ -263,9 +275,9 @@ module Google
263
275
  # Redirect URI
264
276
  def redirect_uri_for(base_url)
265
277
  return @callback_uri unless URI(@callback_uri).scheme.nil?
266
- fail sprintf(
267
- MISSING_ABSOLUTE_URL_ERROR,
268
- @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
278
+ if base_url.nil? || URI(base_url).scheme.nil?
279
+ raise sprintf(ISSING_ABSOLUTE_URL_ERROR, @callback_uri)
280
+ end
269
281
  URI.join(base_url, @callback_uri).to_s
270
282
  end
271
283
  end
@@ -46,9 +46,9 @@ module Google
46
46
  #
47
47
  # cf [Application Default Credentials](http://goo.gl/mkAHpZ)
48
48
  class UserRefreshCredentials < Signet::OAuth2::Client
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
+ TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v4/token'.freeze
50
+ AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/auth'.freeze
51
+ REVOKE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/revoke'.freeze
52
52
  extend CredentialsLoader
53
53
 
54
54
  # Create a UserRefreshCredentials.
@@ -77,7 +77,7 @@ module Google
77
77
  json_key = MultiJson.load(json_key_io.read)
78
78
  wanted = %w(client_id client_secret refresh_token)
79
79
  wanted.each do |key|
80
- fail "the json is missing the #{key} field" unless json_key.key?(key)
80
+ raise "the json is missing the #{key} field" unless json_key.key?(key)
81
81
  end
82
82
  json_key
83
83
  end
@@ -92,15 +92,18 @@ module Google
92
92
  # Revokes the credential
93
93
  def revoke!(options = {})
94
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}")
95
+
96
+ retry_with_error do
97
+ resp = c.get(REVOKE_TOKEN_URI, token: refresh_token || access_token)
98
+ case resp.status
99
+ when 200
100
+ self.access_token = nil
101
+ self.refresh_token = nil
102
+ self.expires_at = 0
103
+ else
104
+ raise(Signet::AuthorizationError,
105
+ "Unexpected error code #{resp.status}")
106
+ end
104
107
  end
105
108
  end
106
109
 
@@ -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.5.1'
34
+ VERSION = '0.5.2'.freeze
35
35
  end
36
36
  end
@@ -66,20 +66,21 @@ module Google
66
66
  # @see {Google::Auth::ControllerHelpers}
67
67
  # @note Requires sessions are enabled
68
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'
69
+ STATE_PARAM = 'state'.freeze
70
+ AUTH_CODE_KEY = 'code'.freeze
71
+ ERROR_CODE_KEY = 'error'.freeze
72
+ SESSION_ID_KEY = 'session_id'.freeze
73
+ CALLBACK_STATE_KEY = 'g-auth-callback'.freeze
74
+ CURRENT_URI_KEY = 'current_uri'.freeze
75
+ XSRF_KEY = 'g-xsrf-token'.freeze
76
+ SCOPE_KEY = 'scope'.freeze
77
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'
78
+ NIL_REQUEST_ERROR = 'Request is required.'.freeze
79
+ NIL_SESSION_ERROR = 'Sessions must be enabled'.freeze
80
+ MISSING_AUTH_CODE_ERROR = 'Missing authorization code in request'.freeze
81
+ AUTHORIZATION_ERROR = 'Authorization error: %s'.freeze
82
+ INVALID_STATE_TOKEN_ERROR =
83
+ 'State token does not match expected value'.freeze
83
84
 
84
85
  class << self
85
86
  attr_accessor :default
@@ -128,13 +129,15 @@ module Google
128
129
  # credentials & next URL to redirect to
129
130
  def handle_auth_callback(user_id, request)
130
131
  callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
131
- request)
132
+ request
133
+ )
132
134
  WebUserAuthorizer.validate_callback_state(callback_state, request)
133
135
  credentials = get_and_store_credentials_from_code(
134
136
  user_id: user_id,
135
137
  code: callback_state[AUTH_CODE_KEY],
136
138
  scope: callback_state[SCOPE_KEY],
137
- base_url: request.url)
139
+ base_url: request.url
140
+ )
138
141
  [credentials, redirect_uri]
139
142
  end
140
143
 
@@ -156,14 +159,15 @@ module Google
156
159
  def get_authorization_url(options = {})
157
160
  options = options.dup
158
161
  request = options[:request]
159
- fail NIL_REQUEST_ERROR if request.nil?
160
- fail NIL_SESSION_ERROR if request.session.nil?
162
+ raise NIL_REQUEST_ERROR if request.nil?
163
+ raise NIL_SESSION_ERROR if request.session.nil?
161
164
 
162
165
  redirect_to = options[:redirect_to] || request.url
163
166
  request.session[XSRF_KEY] = SecureRandom.base64
164
167
  options[:state] = MultiJson.dump(
165
168
  SESSION_ID_KEY => request.session[XSRF_KEY],
166
- CURRENT_URI_KEY => redirect_to)
169
+ CURRENT_URI_KEY => redirect_to
170
+ )
167
171
  options[:base_url] = request.url
168
172
  super(options)
169
173
  end
@@ -193,7 +197,8 @@ module Google
193
197
  user_id: user_id,
194
198
  code: callback_state[AUTH_CODE_KEY],
195
199
  scope: callback_state[SCOPE_KEY],
196
- base_url: request.url)
200
+ base_url: request.url
201
+ )
197
202
  else
198
203
  super(user_id, scope)
199
204
  end
@@ -204,7 +209,7 @@ module Google
204
209
  redirect_uri = state[CURRENT_URI_KEY]
205
210
  callback_state = {
206
211
  AUTH_CODE_KEY => request[AUTH_CODE_KEY],
207
- ERROR_CODE_KEY => request[ERROR_CODE_KEY],
212
+ ERROR_CODE_KEY => request[ERROR_CODE_KEY],
208
213
  SESSION_ID_KEY => state[SESSION_ID_KEY],
209
214
  SCOPE_KEY => request[SCOPE_KEY]
210
215
  }
@@ -223,12 +228,12 @@ module Google
223
228
  # Current request
224
229
  def self.validate_callback_state(state, request)
225
230
  if state[AUTH_CODE_KEY].nil?
226
- fail Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR
231
+ raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR
227
232
  elsif state[ERROR_CODE_KEY]
228
- fail Signet::AuthorizationError,
229
- sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
233
+ raise Signet::AuthorizationError,
234
+ sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
230
235
  elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
231
- fail Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
236
+ raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
232
237
  end
233
238
  end
234
239
 
@@ -254,7 +259,7 @@ module Google
254
259
  #
255
260
  # @see {Google::Auth::WebUserAuthorizer}
256
261
  class CallbackApp
257
- LOCATION_HEADER = 'Location'
262
+ LOCATION_HEADER = 'Location'.freeze
258
263
  REDIR_STATUS = 302
259
264
  ERROR_STATUS = 500
260
265
 
@@ -62,7 +62,8 @@ shared_examples 'apply/apply! are OK' do
62
62
  @client.on_refresh(&b)
63
63
  @client.fetch_access_token!
64
64
  end.to yield_with_args(have_attributes(
65
- access_token: '1/abcdef1234567890'))
65
+ access_token: '1/abcdef1234567890'
66
+ ))
66
67
  expect(stub).to have_been_requested
67
68
  end
68
69
  end
@@ -131,10 +132,10 @@ shared_examples 'apply/apply! are OK' do
131
132
  end
132
133
 
133
134
  it 'should fetch a new token if the current one is expired' do
134
- token_1 = '1/abcdef1234567890'
135
- token_2 = '2/abcdef1234567891'
135
+ token1 = '1/abcdef1234567890'
136
+ token2 = '2/abcdef1234567891'
136
137
 
137
- [token_1, token_2].each do |t|
138
+ [token1, token2].each do |t|
138
139
  make_auth_stubs access_token: t
139
140
  md = { foo: 'bar' }
140
141
  got = @client.apply(md)