googleauth 0.5.1 → 0.11.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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  5. data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
  6. data/.kokoro/build.bat +16 -0
  7. data/.kokoro/build.sh +4 -0
  8. data/.kokoro/continuous/common.cfg +24 -0
  9. data/.kokoro/continuous/linux.cfg +25 -0
  10. data/.kokoro/continuous/osx.cfg +8 -0
  11. data/.kokoro/continuous/post.cfg +30 -0
  12. data/.kokoro/continuous/windows.cfg +29 -0
  13. data/.kokoro/osx.sh +4 -0
  14. data/.kokoro/presubmit/common.cfg +24 -0
  15. data/.kokoro/presubmit/linux.cfg +24 -0
  16. data/.kokoro/presubmit/osx.cfg +8 -0
  17. data/.kokoro/presubmit/windows.cfg +29 -0
  18. data/.kokoro/release.cfg +94 -0
  19. data/.kokoro/trampoline.bat +10 -0
  20. data/.kokoro/trampoline.sh +4 -0
  21. data/.repo-metadata.json +5 -0
  22. data/.rubocop.yml +17 -1
  23. data/CHANGELOG.md +90 -19
  24. data/CODE_OF_CONDUCT.md +43 -0
  25. data/Gemfile +16 -13
  26. data/README.md +58 -18
  27. data/Rakefile +106 -10
  28. data/googleauth.gemspec +27 -25
  29. data/lib/googleauth/application_default.rb +81 -0
  30. data/lib/googleauth/client_id.rb +21 -19
  31. data/lib/googleauth/compute_engine.rb +40 -43
  32. data/lib/googleauth/credentials.rb +375 -0
  33. data/lib/googleauth/credentials_loader.rb +117 -43
  34. data/lib/googleauth/default_credentials.rb +93 -0
  35. data/lib/googleauth/iam.rb +11 -11
  36. data/lib/googleauth/json_key_reader.rb +46 -0
  37. data/lib/googleauth/scope_util.rb +12 -12
  38. data/lib/googleauth/service_account.rb +64 -62
  39. data/lib/googleauth/signet.rb +53 -12
  40. data/lib/googleauth/stores/file_token_store.rb +8 -8
  41. data/lib/googleauth/stores/redis_token_store.rb +22 -22
  42. data/lib/googleauth/token_store.rb +6 -6
  43. data/lib/googleauth/user_authorizer.rb +80 -68
  44. data/lib/googleauth/user_refresh.rb +44 -35
  45. data/lib/googleauth/version.rb +1 -1
  46. data/lib/googleauth/web_user_authorizer.rb +77 -68
  47. data/lib/googleauth.rb +6 -96
  48. data/rakelib/devsite_builder.rb +45 -0
  49. data/rakelib/link_checker.rb +64 -0
  50. data/rakelib/repo_metadata.rb +59 -0
  51. data/spec/googleauth/apply_auth_examples.rb +47 -46
  52. data/spec/googleauth/client_id_spec.rb +75 -55
  53. data/spec/googleauth/compute_engine_spec.rb +60 -43
  54. data/spec/googleauth/credentials_spec.rb +467 -0
  55. data/spec/googleauth/get_application_default_spec.rb +149 -111
  56. data/spec/googleauth/iam_spec.rb +25 -25
  57. data/spec/googleauth/scope_util_spec.rb +26 -24
  58. data/spec/googleauth/service_account_spec.rb +261 -143
  59. data/spec/googleauth/signet_spec.rb +93 -30
  60. data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
  61. data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
  62. data/spec/googleauth/stores/store_examples.rb +16 -16
  63. data/spec/googleauth/user_authorizer_spec.rb +153 -124
  64. data/spec/googleauth/user_refresh_spec.rb +186 -121
  65. data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
  66. data/spec/spec_helper.rb +21 -19
  67. metadata +75 -32
  68. data/.rubocop_todo.yml +0 -32
  69. data/.travis.yml +0 -37
@@ -27,10 +27,10 @@
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
- require 'uri'
31
- require 'multi_json'
32
- require 'googleauth/signet'
33
- require 'googleauth/user_refresh'
30
+ require "uri"
31
+ require "multi_json"
32
+ require "googleauth/signet"
33
+ require "googleauth/user_refresh"
34
34
 
35
35
  module Google
36
36
  module Auth
@@ -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
  #
@@ -72,14 +72,14 @@ module Google
72
72
  # @param [String] callback_uri
73
73
  # URL (either absolute or relative) of the auth callback.
74
74
  # Defaults to '/oauth2callback'
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?
75
+ def initialize client_id, scope, token_store, callback_uri = 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)
81
81
  @token_store = token_store
82
- @callback_uri = callback_uri || '/oauth2callback'
82
+ @callback_uri = callback_uri || "/oauth2callback"
83
83
  end
84
84
 
85
85
  # Build the URL for requesting authorization.
@@ -97,19 +97,20 @@ module Google
97
97
  # nil.
98
98
  # @return [String]
99
99
  # Authorization url
100
- def get_authorization_url(options = {})
100
+ def get_authorization_url options = {}
101
101
  scope = options[:scope] || @scope
102
102
  credentials = UserRefreshCredentials.new(
103
- client_id: @client_id.id,
103
+ client_id: @client_id.id,
104
104
  client_secret: @client_id.secret,
105
- scope: scope)
106
- redirect_uri = redirect_uri_for(options[:base_url])
107
- url = credentials.authorization_uri(access_type: 'offline',
108
- redirect_uri: redirect_uri,
109
- approval_prompt: 'force',
110
- state: options[:state],
105
+ scope: scope
106
+ )
107
+ redirect_uri = redirect_uri_for options[:base_url]
108
+ url = credentials.authorization_uri(access_type: "offline",
109
+ redirect_uri: redirect_uri,
110
+ approval_prompt: "force",
111
+ state: options[:state],
111
112
  include_granted_scopes: true,
112
- login_hint: options[:login_hint])
113
+ login_hint: options[:login_hint])
113
114
  url.to_s
114
115
  end
115
116
 
@@ -122,31 +123,26 @@ module Google
122
123
  # the requested scopes
123
124
  # @return [Google::Auth::UserRefreshCredentials]
124
125
  # Stored credentials, nil if none present
125
- 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)
126
+ def get_credentials user_id, scope = nil
127
+ saved_token = stored_token user_id
131
128
  return nil if saved_token.nil?
132
- data = MultiJson.load(saved_token)
129
+ data = MultiJson.load saved_token
133
130
 
134
- 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)
131
+ if data.fetch("client_id", @client_id.id) != @client_id.id
132
+ raise format(MISMATCHED_CLIENT_ID_ERROR,
133
+ data["client_id"], @client_id.id)
137
134
  end
138
135
 
139
136
  credentials = UserRefreshCredentials.new(
140
- client_id: @client_id.id,
137
+ client_id: @client_id.id,
141
138
  client_secret: @client_id.secret,
142
- scope: data['scope'] || @scope,
143
- access_token: data['access_token'],
144
- refresh_token: data['refresh_token'],
145
- expires_at: data.fetch('expiration_time_millis', 0) / 1000)
146
- if credentials.includes_scope?(scope)
147
- monitor_credentials(user_id, credentials)
148
- return credentials
149
- end
139
+ scope: data["scope"] || @scope,
140
+ access_token: data["access_token"],
141
+ refresh_token: data["refresh_token"],
142
+ expires_at: data.fetch("expiration_time_millis", 0) / 1000
143
+ )
144
+ scope ||= @scope
145
+ return monitor_credentials user_id, credentials if credentials.includes_scope? scope
150
146
  nil
151
147
  end
152
148
 
@@ -165,19 +161,20 @@ module Google
165
161
  # callback uri is a relative.
166
162
  # @return [Google::Auth::UserRefreshCredentials]
167
163
  # Credentials if exchange is successful
168
- def get_credentials_from_code(options = {})
164
+ def get_credentials_from_code options = {}
169
165
  user_id = options[:user_id]
170
166
  code = options[:code]
171
167
  scope = options[:scope] || @scope
172
168
  base_url = options[:base_url]
173
169
  credentials = UserRefreshCredentials.new(
174
- client_id: @client_id.id,
170
+ client_id: @client_id.id,
175
171
  client_secret: @client_id.secret,
176
- redirect_uri: redirect_uri_for(base_url),
177
- scope: scope)
172
+ redirect_uri: redirect_uri_for(base_url),
173
+ scope: scope
174
+ )
178
175
  credentials.code = code
179
176
  credentials.fetch_access_token!({})
180
- monitor_credentials(user_id, credentials)
177
+ monitor_credentials user_id, credentials
181
178
  end
182
179
 
183
180
  # Exchanges an authorization code returned in the oauth callback.
@@ -197,10 +194,9 @@ module Google
197
194
  # callback uri is a relative.
198
195
  # @return [Google::Auth::UserRefreshCredentials]
199
196
  # Credentials if exchange is successful
200
- def get_and_store_credentials_from_code(options = {})
201
- credentials = get_credentials_from_code(options)
202
- monitor_credentials(options[:user_id], credentials)
203
- store_credentials(options[:user_id], credentials)
197
+ def get_and_store_credentials_from_code options = {}
198
+ credentials = get_credentials_from_code options
199
+ store_credentials options[:user_id], credentials
204
200
  end
205
201
 
206
202
  # Revokes a user's credentials. This both revokes the actual
@@ -208,11 +204,11 @@ module Google
208
204
  #
209
205
  # @param [String] user_id
210
206
  # Unique ID of the user for loading/storing credentials.
211
- def revoke_authorization(user_id)
212
- credentials = get_credentials(user_id)
207
+ def revoke_authorization user_id
208
+ credentials = get_credentials user_id
213
209
  if credentials
214
210
  begin
215
- @token_store.delete(user_id)
211
+ @token_store.delete user_id
216
212
  ensure
217
213
  credentials.revoke!
218
214
  end
@@ -228,19 +224,32 @@ module Google
228
224
  # Unique ID of the user for loading/storing credentials.
229
225
  # @param [Google::Auth::UserRefreshCredentials] credentials
230
226
  # Credentials to store.
231
- def store_credentials(user_id, credentials)
227
+ def store_credentials user_id, credentials
232
228
  json = MultiJson.dump(
233
- client_id: credentials.client_id,
234
- access_token: credentials.access_token,
235
- refresh_token: credentials.refresh_token,
236
- scope: credentials.scope,
237
- expiration_time_millis: (credentials.expires_at.to_i) * 1000)
238
- @token_store.store(user_id, json)
229
+ client_id: credentials.client_id,
230
+ access_token: credentials.access_token,
231
+ refresh_token: credentials.refresh_token,
232
+ scope: credentials.scope,
233
+ expiration_time_millis: credentials.expires_at.to_i * 1000
234
+ )
235
+ @token_store.store user_id, json
239
236
  credentials
240
237
  end
241
238
 
242
239
  private
243
240
 
241
+ # @private Fetch stored token with given user_id
242
+ #
243
+ # @param [String] user_id
244
+ # Unique ID of the user for loading/storing credentials.
245
+ # @return [String] The saved token from @token_store
246
+ def stored_token user_id
247
+ raise NIL_USER_ID_ERROR if user_id.nil?
248
+ raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
249
+
250
+ @token_store.load user_id
251
+ end
252
+
244
253
  # Begin watching a credential for refreshes so the access token can be
245
254
  # saved.
246
255
  #
@@ -248,9 +257,9 @@ module Google
248
257
  # Unique ID of the user for loading/storing credentials.
249
258
  # @param [Google::Auth::UserRefreshCredentials] credentials
250
259
  # Credentials to store.
251
- def monitor_credentials(user_id, credentials)
260
+ def monitor_credentials user_id, credentials
252
261
  credentials.on_refresh do |cred|
253
- store_credentials(user_id, cred)
262
+ store_credentials user_id, cred
254
263
  end
255
264
  credentials
256
265
  end
@@ -261,13 +270,16 @@ module Google
261
270
  # Absolute URL to resolve the callback against if necessary.
262
271
  # @return [String]
263
272
  # Redirect URI
264
- def redirect_uri_for(base_url)
265
- 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?
273
+ def redirect_uri_for base_url
274
+ return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
275
+ raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
269
276
  URI.join(base_url, @callback_uri).to_s
270
277
  end
278
+
279
+ # Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed)
280
+ def uri_is_postmessage? uri
281
+ uri.to_s.casecmp("postmessage").zero?
282
+ end
271
283
  end
272
284
  end
273
285
  end
@@ -27,10 +27,10 @@
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
- require 'googleauth/signet'
31
- require 'googleauth/credentials_loader'
32
- require 'googleauth/scope_util'
33
- require 'multi_json'
30
+ require "googleauth/signet"
31
+ require "googleauth/credentials_loader"
32
+ require "googleauth/scope_util"
33
+ require "multi_json"
34
34
 
35
35
  module Google
36
36
  # Module Auth provides classes that provide Google-specific authorization
@@ -44,63 +44,72 @@ module Google
44
44
  # 'gcloud auth login' saves a file with these contents in well known
45
45
  # location
46
46
  #
47
- # cf [Application Default Credentials](http://goo.gl/mkAHpZ)
47
+ # cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
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://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
52
  extend CredentialsLoader
53
+ attr_reader :project_id
53
54
 
54
55
  # Create a UserRefreshCredentials.
55
56
  #
56
57
  # @param json_key_io [IO] an IO from which the JSON key can be read
57
58
  # @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
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
61
62
  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]
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]
65
67
  }
66
68
 
67
69
  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)
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)
72
76
  end
73
77
 
74
78
  # Reads the client_id, client_secret and refresh_token fields from the
75
79
  # JSON key.
76
- def self.read_json_key(json_key_io)
77
- json_key = MultiJson.load(json_key_io.read)
78
- wanted = %w(client_id client_secret refresh_token)
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"]
79
83
  wanted.each do |key|
80
- fail "the json is missing the #{key} field" unless json_key.key?(key)
84
+ raise "the json is missing the #{key} field" unless json_key.key? key
81
85
  end
82
86
  json_key
83
87
  end
84
88
 
85
- def initialize(options = {})
89
+ def initialize options = {}
86
90
  options ||= {}
87
91
  options[:token_credential_uri] ||= TOKEN_CRED_URI
88
92
  options[:authorization_uri] ||= AUTHORIZATION_URI
89
- super(options)
93
+ @project_id = options[:project_id]
94
+ @project_id ||= CredentialsLoader.load_gcloud_project_id
95
+ super options
90
96
  end
91
97
 
92
98
  # Revokes the credential
93
- def revoke!(options = {})
99
+ def revoke! options = {}
94
100
  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}")
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
104
113
  end
105
114
  end
106
115
 
@@ -110,7 +119,7 @@ module Google
110
119
  # Scope to verify
111
120
  # @return [Boolean]
112
121
  # True if scope is granted
113
- def includes_scope?(required_scope)
122
+ def includes_scope? required_scope
114
123
  missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
115
124
  Google::Auth::ScopeUtil.normalize(scope)
116
125
  missing_scope.empty?
@@ -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.11.0".freeze
35
35
  end
36
36
  end
@@ -27,11 +27,11 @@
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
- require 'multi_json'
31
- require 'googleauth/signet'
32
- require 'googleauth/user_authorizer'
33
- require 'googleauth/user_refresh'
34
- require 'securerandom'
30
+ require "multi_json"
31
+ require "googleauth/signet"
32
+ require "googleauth/user_authorizer"
33
+ require "googleauth/user_refresh"
34
+ require "securerandom"
35
35
 
36
36
  module Google
37
37
  module Auth
@@ -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
@@ -96,9 +97,9 @@ module Google
96
97
  #
97
98
  # @param [Rack::Request] request
98
99
  # 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)
100
+ def self.handle_auth_callback_deferred request
101
+ callback_state, redirect_uri = extract_callback_state request
102
+ request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
102
103
  redirect_uri
103
104
  end
104
105
 
@@ -113,8 +114,8 @@ module Google
113
114
  # @param [String] callback_uri
114
115
  # URL (either absolute or relative) of the auth callback. Defaults
115
116
  # to '/oauth2callback'
116
- def initialize(client_id, scope, token_store, callback_uri = nil)
117
- super(client_id, scope, token_store, callback_uri)
117
+ def initialize client_id, scope, token_store, callback_uri = nil
118
+ super client_id, scope, token_store, callback_uri
118
119
  end
119
120
 
120
121
  # Handle the result of the oauth callback. Exchanges the authorization
@@ -126,15 +127,17 @@ module Google
126
127
  # Current request
127
128
  # @return (Google::Auth::UserRefreshCredentials, String)
128
129
  # credentials & next URL to redirect to
129
- def handle_auth_callback(user_id, request)
130
+ def handle_auth_callback user_id, request
130
131
  callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
131
- request)
132
- WebUserAuthorizer.validate_callback_state(callback_state, request)
132
+ request
133
+ )
134
+ WebUserAuthorizer.validate_callback_state callback_state, request
133
135
  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)
136
+ user_id: user_id,
137
+ code: callback_state[AUTH_CODE_KEY],
138
+ scope: callback_state[SCOPE_KEY],
139
+ base_url: request.url
140
+ )
138
141
  [credentials, redirect_uri]
139
142
  end
140
143
 
@@ -151,29 +154,35 @@ module Google
151
154
  # @param [String, Array<String>] scope
152
155
  # Authorization scope to request. Overrides the instance scopes if
153
156
  # not nil.
157
+ # @param [Hash] state
158
+ # Optional key-values to be returned to the oauth callback.
154
159
  # @return [String]
155
160
  # Authorization url
156
- def get_authorization_url(options = {})
161
+ def get_authorization_url options = {}
157
162
  options = options.dup
158
163
  request = options[:request]
159
- fail NIL_REQUEST_ERROR if request.nil?
160
- fail NIL_SESSION_ERROR if request.session.nil?
164
+ raise NIL_REQUEST_ERROR if request.nil?
165
+ raise NIL_SESSION_ERROR if request.session.nil?
166
+
167
+ state = options[:state] || {}
161
168
 
162
169
  redirect_to = options[:redirect_to] || request.url
163
170
  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)
171
+ options[:state] = MultiJson.dump(state.merge(
172
+ SESSION_ID_KEY => request.session[XSRF_KEY],
173
+ CURRENT_URI_KEY => redirect_to
174
+ ))
167
175
  options[:base_url] = request.url
168
- super(options)
176
+ super options
169
177
  end
170
178
 
171
- # Fetch stored credentials for the user.
179
+ # Fetch stored credentials for the user from the given request session.
172
180
  #
173
181
  # @param [String] user_id
174
182
  # Unique ID of the user for loading/storing credentials.
175
183
  # @param [Rack::Request] request
176
- # Current request
184
+ # Current request. Optional. If omitted, this will attempt to fall back
185
+ # on the base class behavior of reading from the token store.
177
186
  # @param [Array<String>, String] scope
178
187
  # If specified, only returns credentials that have all the \
179
188
  # requested scopes
@@ -182,31 +191,32 @@ module Google
182
191
  # @raise [Signet::AuthorizationError]
183
192
  # May raise an error if an authorization code is present in the session
184
193
  # and exchange of the code fails
185
- def get_credentials(user_id, request, scope = nil)
186
- if request.session.key?(CALLBACK_STATE_KEY)
194
+ def get_credentials user_id, request = nil, scope = nil
195
+ if request && request.session.key?(CALLBACK_STATE_KEY)
187
196
  # Note - in theory, no need to check required scope as this is
188
197
  # 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)
198
+ state_json = request.session.delete CALLBACK_STATE_KEY
199
+ callback_state = MultiJson.load state_json
200
+ WebUserAuthorizer.validate_callback_state callback_state, request
192
201
  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)
202
+ user_id: user_id,
203
+ code: callback_state[AUTH_CODE_KEY],
204
+ scope: callback_state[SCOPE_KEY],
205
+ base_url: request.url
206
+ )
197
207
  else
198
- super(user_id, scope)
208
+ super user_id, scope
199
209
  end
200
210
  end
201
211
 
202
- def self.extract_callback_state(request)
203
- state = MultiJson.load(request[STATE_PARAM] || '{}')
212
+ def self.extract_callback_state request
213
+ state = MultiJson.load(request[STATE_PARAM] || "{}")
204
214
  redirect_uri = state[CURRENT_URI_KEY]
205
215
  callback_state = {
206
- AUTH_CODE_KEY => request[AUTH_CODE_KEY],
207
- ERROR_CODE_KEY => request[ERROR_CODE_KEY],
216
+ AUTH_CODE_KEY => request[AUTH_CODE_KEY],
217
+ ERROR_CODE_KEY => request[ERROR_CODE_KEY],
208
218
  SESSION_ID_KEY => state[SESSION_ID_KEY],
209
- SCOPE_KEY => request[SCOPE_KEY]
219
+ SCOPE_KEY => request[SCOPE_KEY]
210
220
  }
211
221
  [callback_state, redirect_uri]
212
222
  end
@@ -221,14 +231,13 @@ module Google
221
231
  # Error message if failed
222
232
  # @param [Rack::Request] request
223
233
  # 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])
234
+ def self.validate_callback_state state, request
235
+ raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
236
+ if state[ERROR_CODE_KEY]
237
+ raise Signet::AuthorizationError,
238
+ format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
230
239
  elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
231
- fail Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
240
+ raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
232
241
  end
233
242
  end
234
243
 
@@ -254,7 +263,7 @@ module Google
254
263
  #
255
264
  # @see {Google::Auth::WebUserAuthorizer}
256
265
  class CallbackApp
257
- LOCATION_HEADER = 'Location'
266
+ LOCATION_HEADER = "Location".freeze
258
267
  REDIR_STATUS = 302
259
268
  ERROR_STATUS = 500
260
269
 
@@ -270,18 +279,18 @@ module Google
270
279
  # Rack environment
271
280
  # @return [Array]
272
281
  # HTTP response
273
- def self.call(env)
274
- request = Rack::Request.new(env)
275
- return_url = WebUserAuthorizer.handle_auth_callback_deferred(request)
282
+ def self.call env
283
+ request = Rack::Request.new env
284
+ return_url = WebUserAuthorizer.handle_auth_callback_deferred request
276
285
  if return_url
277
286
  [REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
278
287
  else
279
- [ERROR_STATUS, {}, ['No return URL is present in the request.']]
288
+ [ERROR_STATUS, {}, ["No return URL is present in the request."]]
280
289
  end
281
290
  end
282
291
 
283
- def call(env)
284
- self.class.call(env)
292
+ def call env
293
+ self.class.call env
285
294
  end
286
295
  end
287
296
  end