googleauth 0.5.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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