omniauth-google-oauth2 0.6.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 81997c7fd03317eea408408d6f18159972b8f3dc
4
- data.tar.gz: d16e88cdee0f4fd599d4b8d8c05ac40ebc761c07
2
+ SHA256:
3
+ metadata.gz: 3b1fdc81978e86a4a1b0c493bc1d83f9e6fa1e613bfb69b2feee8af2a6869b99
4
+ data.tar.gz: 02efca2850b5e053630aa7da102a9162aca87d16d6ab9fb6408654fd98c145c1
5
5
  SHA512:
6
- metadata.gz: eb2b1d82471d000a983728b92676e682d7006b0a3551bcb109cd9216a3114e6d78500bdd3d29ae71b55c95f4b7a2315f4d3930bf017edc7a2506641bb51cfc06
7
- data.tar.gz: 27605544db858360570a109a922689ec100bd70ff4ea63318a67f2ba6bc5a91c1f464e9110088addba9c83fc62a6fcae13ce54f22f53abcaf5933d04cb1538ff
6
+ metadata.gz: 6f2c99c806cc6d08dadcc472474418cfbace26481e1d828aa90571d5bbf77e3e26c9b600fd6fd1d97c8a71d4e349d230f87a5e11a9106874d106a240f3cad9ec
7
+ data.tar.gz: a66acdde9fdda8ec2f00f861a0a1a2ca89f8faeae84388f44dca938db0e9f55ca6d166de2633c4e27ffe7cf44bcfcc5ef0c5f0a4bfbcc3990d13c89ff7ac322c
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby-version: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby ${{ matrix.ruby-version }}
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby-version }}
18
+ bundler-cache: true # 'bundle install' and cache
19
+ - name: Run specs
20
+ run: |
21
+ bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,11 +1,9 @@
1
- ClassLength:
2
- Enabled: false
3
- Layout/IndentHeredoc:
1
+ Metrics/ClassLength:
4
2
  Enabled: false
5
3
  Metrics/AbcSize:
6
4
  Enabled: false
7
5
  Metrics/BlockLength:
8
- ExcludedMethods: ['describe', 'context']
6
+ ExcludedMethods: ['describe', 'context', 'shared_examples']
9
7
  Metrics/CyclomaticComplexity:
10
8
  Enabled: false
11
9
  Metrics/LineLength:
@@ -20,4 +18,3 @@ Style/MutableConstant:
20
18
  Enabled: false
21
19
  Gemspec/RequiredRubyVersion:
22
20
  Enabled: false
23
-
data/.travis.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
+ cache: bundler
2
3
  rvm:
3
- - '2.1.10'
4
- - '2.2.7'
5
- - '2.3.4'
6
- - '2.4.1'
7
- - '2.5.0'
4
+ - '2.3.8'
5
+ - '2.4.10'
6
+ - '2.5.8'
7
+ - '2.6.6'
8
+ - '2.7.2'
9
+ - '3.0.0'
data/CHANGELOG.md CHANGED
@@ -1,6 +1,106 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## 1.0.1 - 2022-03-10
5
+
6
+ ### Added
7
+ - Output granted scopes in credentials block of the auth hash.
8
+ - Migrated to GitHub actions.
9
+
10
+ ### Deprecated
11
+ - Nothing.
12
+
13
+ ### Removed
14
+ - Nothing.
15
+
16
+ ### Fixed
17
+ - Overriding the `redirect_uri` via params or JSON request body.
18
+
19
+ ## 1.0.0 - 2021-03-14
20
+
21
+ ### Added
22
+ - Support for Omniauth 2.x!
23
+
24
+ ### Deprecated
25
+ - Nothing.
26
+
27
+ ### Removed
28
+ - Support for Omniauth 1.x.
29
+
30
+ ### Fixed
31
+ - Nothing.
32
+
33
+ ## 0.8.2 - 2021-03-14
34
+
35
+ ### Added
36
+ - Constrains the version to Omniauth 1.x.
37
+
38
+ ### Deprecated
39
+ - Nothing.
40
+
41
+ ### Removed
42
+ - Nothing.
43
+
44
+ ### Fixed
45
+ - Nothing.
46
+
47
+ ## 0.8.1 - 2020-12-12
48
+
49
+ ### Added
50
+ - Support reading the access token from a json request body.
51
+
52
+ ### Deprecated
53
+ - Nothing.
54
+
55
+ ### Removed
56
+ - No longer verify the iat claim for JWT.
57
+
58
+ ### Fixed
59
+ - A few minor issues with .rubocop.yml.
60
+ - Issues with image resizing code when the image came with size information from Google.
61
+
62
+ ## 0.8.0 - 2019-08-21
63
+
64
+ ### Added
65
+ - Updated omniauth-oauth2 to v1.6.0 for security fixes.
66
+
67
+ ### Deprecated
68
+ - Nothing.
69
+
70
+ ### Removed
71
+ - Ruby 2.1 support.
72
+
73
+ ### Fixed
74
+ - Nothing.
75
+
76
+ ## 0.7.0 - 2019-06-03
77
+
78
+ ### Added
79
+ - Ensure `info[:email]` is always verified, and include `unverified_email`
80
+
81
+ ### Deprecated
82
+ - Nothing.
83
+
84
+ ### Removed
85
+ - Nothing.
86
+
87
+ ### Fixed
88
+ - Nothing.
89
+
90
+ ## 0.6.1 - 2019-03-07
91
+
92
+ ### Added
93
+ - Return `email` and `email_verified` keys in response.
94
+
95
+ ### Deprecated
96
+ - Nothing.
97
+
98
+ ### Removed
99
+ - Nothing.
100
+
101
+ ### Fixed
102
+ - Nothing.
103
+
4
104
  ## 0.6.0 - 2018-12-28
5
105
 
6
106
  ### Added
@@ -12,6 +112,7 @@ All notable changes to this project will be documented in this file.
12
112
  ### Removed
13
113
  - Support for JWT 1.x.
14
114
  - Support for `raw_friend_info` and `raw_image_info`.
115
+ - Stop using Google+ API endpoints.
15
116
 
16
117
  ### Fixed
17
118
  - Nothing.
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/omniauth-google-oauth2.svg)](https://badge.fury.io/rb/omniauth-google-oauth2)
2
- [![Build Status](https://travis-ci.org/zquestz/omniauth-google-oauth2.svg)](https://travis-ci.org/zquestz/omniauth-google-oauth2)
3
2
 
4
3
  # OmniAuth Google OAuth2 Strategy
5
4
 
@@ -34,6 +33,7 @@ Here's an example for adding the middleware to a Rails app in `config/initialize
34
33
  Rails.application.config.middleware.use OmniAuth::Builder do
35
34
  provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
36
35
  end
36
+ OmniAuth.config.allowed_request_methods = %i[get]
37
37
  ```
38
38
 
39
39
  You can now access the OmniAuth Google OAuth2 URL: `/auth/google_oauth2`
@@ -54,10 +54,10 @@ You can configure several options, which you pass in to the `provider` method vi
54
54
 
55
55
  * `prompt`: A space-delimited list of string values that determines whether the user is re-prompted for authentication and/or consent. Possible values are:
56
56
  * `none`: No authentication or consent pages will be displayed; it will return an error if the user is not already authenticated and has not pre-configured consent for the requested scopes. This can be used as a method to check for existing authentication and/or consent.
57
- * `consent`: The user will always be prompted for consent, even if he has previously allowed access a given set of scopes.
57
+ * `consent`: The user will always be prompted for consent, even if they have previously allowed access a given set of scopes.
58
58
  * `select_account`: The user will always be prompted to select a user account. This allows a user who has multiple current account sessions to select one amongst them.
59
59
 
60
- If no value is specified, the user only sees the authentication page if he is not logged in and only sees the consent page the first time he authorizes a given set of scopes.
60
+ If no value is specified, the user only sees the authentication page if they are not logged in and only sees the consent page the first time they authorize a given set of scopes.
61
61
 
62
62
  * `image_aspect_ratio`: The shape of the user's profile picture. Possible values are:
63
63
  * `original`: Picture maintains its original aspect ratio.
@@ -73,7 +73,7 @@ You can configure several options, which you pass in to the `provider` method vi
73
73
 
74
74
  * `hd`: (Optional) Limit sign-in to a particular Google Apps hosted domain. This can be simply string `'domain.com'` or an array `%w(domain.com domain.co)`. More information at: https://developers.google.com/accounts/docs/OpenIDConnect#hd-param
75
75
 
76
- * `jwt_leeway`: Number of seconds passed to the JWT library as leeway. Defaults to 60 seconds.
76
+ * `jwt_leeway`: Number of seconds passed to the JWT library as leeway. Defaults to 60 seconds. Note this only works if you use jwt 2.1, as the leeway option was removed in later versions.
77
77
 
78
78
  * `skip_jwt`: Skip JWT processing. This is for users who are seeing JWT decoding errors with the `iat` field. Always try adjusting the leeway before disabling JWT processing.
79
79
 
@@ -81,9 +81,11 @@ You can configure several options, which you pass in to the `provider` method vi
81
81
 
82
82
  * `include_granted_scopes`: If this is provided with the value true, and the authorization request is granted, the authorization will include any previous authorizations granted to this user/application combination for other scopes. See Google's [Incremental Authorization](https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth) for additional details.
83
83
 
84
- * `openid_realm`: Set the OpenID realm value, to allow upgrading from OpenID based authentication to OAuth 2 based authentication. When this is set correctly an `openid_id` value will be set in `[:extra][:id_info]` in the authentication hash with the value of the user's OpenID ID URL.
84
+ * `openid_realm`: Set the OpenID realm value, to allow upgrading from OpenID based authentication to OAuth 2 based authentication. When this is set correctly an `openid_id` value will be set in `['extra']['id_info']` in the authentication hash with the value of the user's OpenID ID URL.
85
85
 
86
- Here's an example of a possible configuration where the strategy name is changed, the user is asked for extra permissions, the user is always prompted to select his account when logging in and the user's profile picture is returned as a thumbnail:
86
+ * `provider_ignores_state`: You will need to set this to `true` when using the `One-time Code Flow` below. In this flow there is no server side redirect that would set the state.
87
+
88
+ Here's an example of a possible configuration where the strategy name is changed, the user is asked for extra permissions, the user is always prompted to select their account when logging in and the user's profile picture is returned as a thumbnail:
87
89
 
88
90
  ```ruby
89
91
  Rails.application.config.middleware.use OmniAuth::Builder do
@@ -176,6 +178,8 @@ devise :omniauthable, omniauth_providers: [:google_oauth2]
176
178
  Then make sure your callbacks controller is setup.
177
179
 
178
180
  ```ruby
181
+ # app/controllers/users/omniauth_callbacks_controller.rb:
182
+
179
183
  class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
180
184
  def google_oauth2
181
185
  # You need to implement the method below in your model (e.g. app/models/user.rb)
@@ -185,7 +189,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
185
189
  flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'Google'
186
190
  sign_in_and_redirect @user, event: :authentication
187
191
  else
188
- session['devise.google_data'] = request.env['omniauth.auth'].except(:extra) # Removing extra as it can overflow some session stores
192
+ session['devise.google_data'] = request.env['omniauth.auth'].except('extra') # Removing extra as it can overflow some session stores
189
193
  redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
190
194
  end
191
195
  end
@@ -213,6 +217,10 @@ end
213
217
  For your views you can login using:
214
218
 
215
219
  ```erb
220
+ <%# omniauth-google-oauth2 1.0.x uses OmniAuth 2 and requires using HTTP Post to initiate authentication: %>
221
+ <%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, method: :post %>
222
+
223
+ <%# omniauth-google-oauth2 prior 1.0.0: %>
216
224
  <%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path %>
217
225
 
218
226
  <%# Devise prior 4.1.0: %>
@@ -223,7 +231,7 @@ An overview is available at https://github.com/plataformatec/devise/wiki/OmniAut
223
231
 
224
232
  ### One-time Code Flow (Hybrid Authentication)
225
233
 
226
- Google describes the One-time Code Flow [here](https://developers.google.com/+/web/signin/server-side-flow). This hybrid authentication flow has significant functional and security advantages over a pure server-side or pure client-side flow. The following steps occur in this flow:
234
+ Google describes the One-time Code Flow [here](https://developers.google.com/identity/sign-in/web/server-side-flow). This hybrid authentication flow has significant functional and security advantages over a pure server-side or pure client-side flow. The following steps occur in this flow:
227
235
 
228
236
  1. The client (web browser) authenticates the user directly via Google's JS API. During this process assorted modals may be rendered by Google.
229
237
  2. On successful authentication, Google returns a one-time use code, which requires the Google client secret (which is only available server-side).
@@ -232,7 +240,7 @@ Google describes the One-time Code Flow [here](https://developers.google.com/+/w
232
240
 
233
241
  This flow is immune to replay attacks, and conveys no useful information to a man in the middle.
234
242
 
235
- The omniauth-google-oauth2 gem supports this mode of operation out of the box. Implementors simply need to add the appropriate JavaScript to their web page, and they can take advantage of this flow. An example JavaScript snippet follows.
243
+ The omniauth-google-oauth2 gem supports this mode of operation when `provider_ignores_state` is set to `true`. Implementors simply need to add the appropriate JavaScript to their web page, and they can take advantage of this flow. An example JavaScript snippet follows.
236
244
 
237
245
  ```javascript
238
246
  // Basic hybrid auth example following the pattern at:
@@ -247,7 +255,7 @@ function init() {
247
255
  // Ready.
248
256
  $('.google-login-button').click(function(e) {
249
257
  e.preventDefault();
250
-
258
+
251
259
  gapi.auth2.authorize({
252
260
  client_id: 'YOUR_CLIENT_ID',
253
261
  cookie_policy: 'single_host_origin',
@@ -260,7 +268,7 @@ function init() {
260
268
  success: function(data) {
261
269
  // response from server
262
270
  }
263
- });
271
+ });
264
272
  } else {
265
273
  // google authentication failed
266
274
  }
@@ -280,6 +288,66 @@ In that case, ensure to send an additional parameter `redirect_uri=` (empty stri
280
288
 
281
289
  If you're making POST requests to `/auth/google_oauth2/callback` from another domain, then you need to make sure `'X-Requested-With': 'XMLHttpRequest'` header is included with your request, otherwise your server might respond with `OAuth2::Error, : Invalid Value` error.
282
290
 
291
+ #### Getting around the `redirect_uri_mismatch` error (See [Issue #365](https://github.com/zquestz/omniauth-google-oauth2/issues/365))
292
+
293
+ If you are struggling with a persistent `redirect_uri_mismatch`, you can instead pass the `access_token` from [`getAuthResponse`](https://developers.google.com/identity/sign-in/web/reference#googleusergetauthresponseincludeauthorizationdata) directly to the `auth/google_oauth2/callback` endpoint, like so:
294
+
295
+ ```javascript
296
+ // Initialize the GoogleAuth object
297
+ let googleAuth;
298
+ gapi.load('client:auth2', async () => {
299
+ await gapi.client.init({ scope: '...', client_id: '...' });
300
+ googleAuth = gapi.auth2.getAuthInstance();
301
+ });
302
+
303
+ // Call this when the Google Sign In button is clicked
304
+ async function signInGoogle() {
305
+ const googleUser = await googleAuth.signIn(); // wait for the user to authorize through the modal
306
+ const { access_token } = googleUser.getAuthResponse();
307
+
308
+ const data = new FormData();
309
+ data.append('access_token', access_token);
310
+
311
+ const response = await api.post('/auth/google_oauth2/callback', data)
312
+ console.log(response);
313
+ }
314
+ ```
315
+
316
+ #### Using Axios
317
+ If you're making a GET resquests from another domain using `access_token`.
318
+ ```
319
+ axios
320
+ .get(
321
+ 'url(path to your callback}',
322
+ { params: { access_token: 'token' } },
323
+ headers....
324
+ )
325
+ ```
326
+
327
+ If you're making a POST resquests from another domain using `access_token`.
328
+ ```
329
+ axios
330
+ .post(
331
+ 'url(path to your callback}',
332
+ { access_token: 'token' },
333
+ headers....
334
+ )
335
+
336
+ --OR--
337
+
338
+ axios
339
+ .post(
340
+ 'url(path to your callback}',
341
+ null,
342
+ {
343
+ params: {
344
+ access_token: 'token'
345
+ },
346
+ headers....
347
+ }
348
+ )
349
+ ```
350
+
283
351
  ## Fixing Protocol Mismatch for `redirect_uri` in Rails
284
352
 
285
353
  Just set the `full_host` in OmniAuth based on the Rails.env.
data/examples/Gemfile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'omniauth-google-oauth2', '~> 0.5'
5
+ gem 'omniauth-google-oauth2', '~> 0.8.1'
6
6
  gem 'rubocop'
7
7
  gem 'sinatra', '~> 1.4'
8
+ gem 'webrick'
@@ -10,6 +10,10 @@ Rails.application.config.middleware.use OmniAuth::Builder do
10
10
  #
11
11
  provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], scope: 'email,profile'
12
12
 
13
+ # Custom redirect_uri
14
+ #
15
+ # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], scope: 'email,profile', redirect_uri: 'https://localhost:3000/redirect'
16
+
13
17
  # Manual setup for offline access with a refresh token.
14
18
  #
15
19
  # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module GoogleOauth2
5
- VERSION = '0.6.0'
5
+ VERSION = '1.0.1'
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'jwt'
4
+ require 'oauth2'
4
5
  require 'omniauth/strategies/oauth2'
5
6
  require 'uri'
6
7
 
@@ -13,6 +14,7 @@ module OmniAuth
13
14
  BASE_SCOPES = %w[profile email openid].freeze
14
15
  DEFAULT_SCOPE = 'email,profile'
15
16
  USER_INFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
17
+ IMAGE_SIZE_REGEXP = /(s\d+(-c)?)|(w\d+-h\d+(-c)?)|(w\d+(-c)?)|(h\d+(-c)?)|c/
16
18
 
17
19
  option :name, 'google_oauth2'
18
20
  option :skip_friends, true
@@ -47,6 +49,8 @@ module OmniAuth
47
49
  prune!(
48
50
  name: raw_info['name'],
49
51
  email: verified_email,
52
+ unverified_email: raw_info['email'],
53
+ email_verified: raw_info['email_verified'],
50
54
  first_name: raw_info['given_name'],
51
55
  last_name: raw_info['family_name'],
52
56
  image: image_url,
@@ -56,6 +60,11 @@ module OmniAuth
56
60
  )
57
61
  end
58
62
 
63
+ credentials do
64
+ # Tokens and expiration will be used from OAuth2 strategy credentials block
65
+ prune!({ 'scope' => token_info(access_token.token)['scope'] })
66
+ end
67
+
59
68
  extra do
60
69
  hash = {}
61
70
  hash[:id_token] = access_token['id_token']
@@ -72,7 +81,7 @@ module OmniAuth
72
81
  verify_sub: false,
73
82
  verify_expiration: true,
74
83
  verify_not_before: true,
75
- verify_iat: true,
84
+ verify_iat: false,
76
85
  verify_jti: false,
77
86
  leeway: options[:jwt_leeway])
78
87
 
@@ -92,31 +101,51 @@ module OmniAuth
92
101
  verify_hd(access_token)
93
102
  access_token
94
103
  end
104
+
95
105
  alias build_access_token custom_build_access_token
96
106
 
97
107
  private
98
108
 
99
109
  def callback_url
100
- options[:redirect_uri] || (full_host + script_name + callback_path)
110
+ options[:redirect_uri] || (full_host + callback_path)
101
111
  end
102
112
 
103
113
  def get_access_token(request)
104
- if request.xhr? && request.params['code']
105
- verifier = request.params['code']
106
- redirect_uri = request.params['redirect_uri'] || 'postmessage'
107
- client.auth_code.get_token(verifier, get_token_options(redirect_uri), deep_symbolize(options.auth_token_params || {}))
108
- elsif request.params['code'] && request.params['redirect_uri']
109
- verifier = request.params['code']
110
- redirect_uri = request.params['redirect_uri']
111
- client.auth_code.get_token(verifier, get_token_options(redirect_uri), deep_symbolize(options.auth_token_params || {}))
112
- elsif verify_token(request.params['access_token'])
114
+ verifier = request.params['code']
115
+ redirect_uri = request.params['redirect_uri']
116
+ access_token = request.params['access_token']
117
+ if verifier && request.xhr?
118
+ client_get_token(verifier, redirect_uri || 'postmessage')
119
+ elsif verifier
120
+ client_get_token(verifier, redirect_uri || callback_url)
121
+ elsif access_token && verify_token(access_token)
113
122
  ::OAuth2::AccessToken.from_hash(client, request.params.dup)
114
- else
115
- verifier = request.params['code']
116
- client.auth_code.get_token(verifier, get_token_options(callback_url), deep_symbolize(options.auth_token_params))
123
+ elsif request.content_type =~ /json/i
124
+ begin
125
+ body = JSON.parse(request.body.read)
126
+ request.body.rewind # rewind request body for downstream middlewares
127
+ verifier = body && body['code']
128
+ access_token = body && body['access_token']
129
+ redirect_uri ||= body && body['redirect_uri']
130
+ if verifier
131
+ client_get_token(verifier, redirect_uri || 'postmessage')
132
+ elsif verify_token(access_token)
133
+ ::OAuth2::AccessToken.from_hash(client, body.dup)
134
+ end
135
+ rescue JSON::ParserError => e
136
+ warn "[omniauth google-oauth2] JSON parse error=#{e}"
137
+ end
117
138
  end
118
139
  end
119
140
 
141
+ def client_get_token(verifier, redirect_uri)
142
+ client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
143
+ end
144
+
145
+ def get_token_params
146
+ deep_symbolize(options.auth_token_params || {})
147
+ end
148
+
120
149
  def get_scope(params)
121
150
  raw_scope = params[:scope] || DEFAULT_SCOPE
122
151
  scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
@@ -124,7 +153,11 @@ module OmniAuth
124
153
  scope_list.join(' ')
125
154
  end
126
155
 
127
- def get_token_options(redirect_uri)
156
+ def verified_email
157
+ raw_info['email_verified'] ? raw_info['email'] : nil
158
+ end
159
+
160
+ def get_token_options(redirect_uri = '')
128
161
  { redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
129
162
  end
130
163
 
@@ -135,10 +168,6 @@ module OmniAuth
135
168
  end
136
169
  end
137
170
 
138
- def verified_email
139
- raw_info['email_verified'] ? raw_info['email'] : nil
140
- end
141
-
142
171
  def image_url
143
172
  return nil unless raw_info['picture']
144
173
 
@@ -149,6 +178,10 @@ module OmniAuth
149
178
  if path_index && image_size_opts_passed?
150
179
  u.path.insert(path_index, image_params)
151
180
  u.path = u.path.gsub('//', '/')
181
+
182
+ # Check if the image is already sized!
183
+ split_path = u.path.split('/')
184
+ u.path = u.path.sub("/#{split_path[-3]}", '') if split_path[-3] =~ IMAGE_SIZE_REGEXP
152
185
  end
153
186
 
154
187
  u.query = strip_unnecessary_query_parameters(u.query)
@@ -187,12 +220,21 @@ module OmniAuth
187
220
  URI.encode_www_form(stripped_params)
188
221
  end
189
222
 
223
+ def token_info(access_token)
224
+ return nil unless access_token
225
+
226
+ @token_info ||= Hash.new do |h, k|
227
+ h[k] = client.request(:get, 'https://www.googleapis.com/oauth2/v3/tokeninfo', params: { access_token: access_token }).parsed
228
+ end
229
+
230
+ @token_info[access_token]
231
+ end
232
+
190
233
  def verify_token(access_token)
191
234
  return false unless access_token
192
235
 
193
- raw_response = client.request(:get, 'https://www.googleapis.com/oauth2/v3/tokeninfo',
194
- params: { access_token: access_token }).parsed
195
- raw_response['aud'] == options.client_id || options.authorized_client_ids.include?(raw_response['aud'])
236
+ token_info = token_info(access_token)
237
+ token_info['aud'] == options.client_id || options.authorized_client_ids.include?(token_info['aud'])
196
238
  end
197
239
 
198
240
  def verify_hd(access_token)
@@ -18,11 +18,12 @@ Gem::Specification.new do |gem|
18
18
  gem.files = `git ls-files`.split("\n")
19
19
  gem.require_paths = ['lib']
20
20
 
21
- gem.required_ruby_version = '>= 2.1'
21
+ gem.required_ruby_version = '>= 2.2'
22
22
 
23
23
  gem.add_runtime_dependency 'jwt', '>= 2.0'
24
- gem.add_runtime_dependency 'omniauth', '>= 1.1.1'
25
- gem.add_runtime_dependency 'omniauth-oauth2', '>= 1.5'
24
+ gem.add_runtime_dependency 'oauth2', '~> 1.1'
25
+ gem.add_runtime_dependency 'omniauth', '~> 2.0'
26
+ gem.add_runtime_dependency 'omniauth-oauth2', '~> 1.7.1'
26
27
 
27
28
  gem.add_development_dependency 'rake', '~> 12.0'
28
29
  gem.add_development_dependency 'rspec', '~> 3.6'
@@ -3,6 +3,7 @@
3
3
  require 'spec_helper'
4
4
  require 'json'
5
5
  require 'omniauth-google-oauth2'
6
+ require 'stringio'
6
7
 
7
8
  describe OmniAuth::Strategies::GoogleOauth2 do
8
9
  let(:request) { double('Request', params: {}, cookies: {}, env: {}) }
@@ -177,8 +178,8 @@ describe OmniAuth::Strategies::GoogleOauth2 do
177
178
 
178
179
  describe 'scope' do
179
180
  it 'should expand scope shortcuts' do
180
- @options = { scope: 'plus.me' }
181
- expect(subject.authorize_params['scope']).to eq('https://www.googleapis.com/auth/plus.me')
181
+ @options = { scope: 'calendar' }
182
+ expect(subject.authorize_params['scope']).to eq('https://www.googleapis.com/auth/calendar')
182
183
  end
183
184
 
184
185
  it 'should leave base scopes as is' do
@@ -288,14 +289,92 @@ describe OmniAuth::Strategies::GoogleOauth2 do
288
289
  end
289
290
  end
290
291
 
291
- describe '#callback_path' do
292
+ describe '#callback_url' do
293
+ let(:base_url) { 'https://example.com' }
294
+
292
295
  it 'has the correct default callback path' do
293
- expect(subject.callback_path).to eq('/auth/google_oauth2/callback')
296
+ allow(subject).to receive(:full_host) { base_url }
297
+ allow(subject).to receive(:script_name) { '' }
298
+ expect(subject.send(:callback_url)).to eq(base_url + '/auth/google_oauth2/callback')
299
+ end
300
+
301
+ it 'should set the callback path with script_name if present' do
302
+ allow(subject).to receive(:full_host) { base_url }
303
+ allow(subject).to receive(:script_name) { '/v1' }
304
+ expect(subject.send(:callback_url)).to eq(base_url + '/v1/auth/google_oauth2/callback')
294
305
  end
295
306
 
296
307
  it 'should set the callback_path parameter if present' do
297
308
  @options = { callback_path: '/auth/foo/callback' }
298
- expect(subject.callback_path).to eq('/auth/foo/callback')
309
+ allow(subject).to receive(:full_host) { base_url }
310
+ allow(subject).to receive(:script_name) { '' }
311
+ expect(subject.send(:callback_url)).to eq(base_url + '/auth/foo/callback')
312
+ end
313
+ end
314
+
315
+ describe '#info' do
316
+ let(:client) do
317
+ OAuth2::Client.new('abc', 'def') do |builder|
318
+ builder.request :url_encoded
319
+ builder.adapter :test do |stub|
320
+ stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, response_hash.to_json] }
321
+ end
322
+ end
323
+ end
324
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
325
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
326
+
327
+ context 'with verified email' do
328
+ let(:response_hash) do
329
+ { email: 'something@domain.invalid', email_verified: true }
330
+ end
331
+
332
+ it 'should return equal email and unverified_email' do
333
+ expect(subject.info[:email]).to eq('something@domain.invalid')
334
+ expect(subject.info[:unverified_email]).to eq('something@domain.invalid')
335
+ end
336
+ end
337
+
338
+ context 'with unverified email' do
339
+ let(:response_hash) do
340
+ { email: 'something@domain.invalid', email_verified: false }
341
+ end
342
+
343
+ it 'should return nil email, and correct unverified email' do
344
+ expect(subject.info[:email]).to eq(nil)
345
+ expect(subject.info[:unverified_email]).to eq('something@domain.invalid')
346
+ end
347
+ end
348
+ end
349
+
350
+ describe '#credentials' do
351
+ let(:client) { OAuth2::Client.new('abc', 'def') }
352
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, access_token: 'valid_access_token', expires_at: 123_456_789, refresh_token: 'valid_refresh_token') }
353
+ before(:each) do
354
+ allow(subject).to receive(:access_token).and_return(access_token)
355
+ subject.options.client_options[:connection_build] = proc do |builder|
356
+ builder.request :url_encoded
357
+ builder.adapter :test do |stub|
358
+ stub.get('/oauth2/v3/tokeninfo?access_token=valid_access_token') do
359
+ [200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
360
+ aud: '000000000000.apps.googleusercontent.com',
361
+ sub: '123456789',
362
+ scope: 'profile email'
363
+ )]
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ it 'should return access token and (optionally) refresh token' do
370
+ expect(subject.credentials.to_h).to \
371
+ match(hash_including(
372
+ 'token' => 'valid_access_token',
373
+ 'refresh_token' => 'valid_refresh_token',
374
+ 'scope' => 'profile email',
375
+ 'expires_at' => 123_456_789,
376
+ 'expires' => true
377
+ ))
299
378
  end
300
379
  end
301
380
 
@@ -313,7 +392,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
313
392
  before { allow(subject).to receive(:access_token).and_return(access_token) }
314
393
 
315
394
  describe 'id_token' do
316
- shared_examples 'id_token issued by valid issuer' do |issuer| # rubocop:disable Metrics/BlockLength
395
+ shared_examples 'id_token issued by valid issuer' do |issuer|
317
396
  context 'when the id_token is passed into the access token' do
318
397
  let(:token_info) do
319
398
  {
@@ -426,6 +505,12 @@ describe OmniAuth::Strategies::GoogleOauth2 do
426
505
  expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50/photo.jpg')
427
506
  end
428
507
 
508
+ it 'should return the image with size specified in the `image_size` option when sizing is in the picture' do
509
+ @options = { image_size: 50 }
510
+ allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh4.googleusercontent.com/url/s96-c/photo.jpg' } }
511
+ expect(subject.info[:image]).to eq('https://lh4.googleusercontent.com/url/s50/photo.jpg')
512
+ end
513
+
429
514
  it 'should handle a picture with too many slashes correctly' do
430
515
  @options = { image_size: 50 }
431
516
  allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url//photo.jpg' } }
@@ -456,24 +541,48 @@ describe OmniAuth::Strategies::GoogleOauth2 do
456
541
  expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40/photo.jpg')
457
542
  end
458
543
 
544
+ it 'should return the image with width and height specified in the `image_size` option when sizing is in the picture' do
545
+ @options = { image_size: { width: 50, height: 40 } }
546
+ allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/w100-h80-c/photo.jpg' } }
547
+ expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40/photo.jpg')
548
+ end
549
+
459
550
  it 'should return square image when `image_aspect_ratio` is specified' do
460
551
  @options = { image_aspect_ratio: 'square' }
461
552
  allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
462
553
  expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/c/photo.jpg')
463
554
  end
464
555
 
556
+ it 'should return square image when `image_aspect_ratio` is specified and sizing is in the picture' do
557
+ @options = { image_aspect_ratio: 'square' }
558
+ allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/c/photo.jpg' } }
559
+ expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/c/photo.jpg')
560
+ end
561
+
465
562
  it 'should return square sized image when `image_aspect_ratio` and `image_size` is set' do
466
563
  @options = { image_aspect_ratio: 'square', image_size: 50 }
467
564
  allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
468
565
  expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50-c/photo.jpg')
469
566
  end
470
567
 
568
+ it 'should return square sized image when `image_aspect_ratio` and `image_size` is set and sizing is in the picture' do
569
+ @options = { image_aspect_ratio: 'square', image_size: 50 }
570
+ allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/s90/photo.jpg' } }
571
+ expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50-c/photo.jpg')
572
+ end
573
+
471
574
  it 'should return square sized image when `image_aspect_ratio` and `image_size` has height and width' do
472
575
  @options = { image_aspect_ratio: 'square', image_size: { width: 50, height: 40 } }
473
576
  allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
474
577
  expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40-c/photo.jpg')
475
578
  end
476
579
 
580
+ it 'should return square sized image when `image_aspect_ratio` and `image_size` has height and width and sizing is in the picture' do
581
+ @options = { image_aspect_ratio: 'square', image_size: { width: 50, height: 40 } }
582
+ allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/w100-h80/photo.jpg' } }
583
+ expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40-c/photo.jpg')
584
+ end
585
+
477
586
  it 'should return original image if image url does not end in `photo.jpg`' do
478
587
  @options = { image_size: 50 }
479
588
  allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photograph.jpg' } }
@@ -547,9 +656,58 @@ describe OmniAuth::Strategies::GoogleOauth2 do
547
656
  expect(token.client).to eq(:client)
548
657
  end
549
658
 
659
+ it 'reads the code from a json request body' do
660
+ body = StringIO.new(%({"code":"json_access_token"}))
661
+ client = double(:client)
662
+ auth_code = double(:auth_code)
663
+
664
+ allow(request).to receive(:xhr?).and_return(false)
665
+ allow(request).to receive(:content_type).and_return('application/json')
666
+ allow(request).to receive(:body).and_return(body)
667
+ allow(client).to receive(:auth_code).and_return(auth_code)
668
+ expect(subject).to receive(:client).and_return(client)
669
+
670
+ expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'postmessage' }, {})
671
+
672
+ subject.build_access_token
673
+ end
674
+
675
+ it 'reads the redirect uri from a json request body' do
676
+ body = StringIO.new(%({"code":"json_access_token", "redirect_uri":"sample"}))
677
+ client = double(:client)
678
+ auth_code = double(:auth_code)
679
+
680
+ allow(request).to receive(:xhr?).and_return(false)
681
+ allow(request).to receive(:content_type).and_return('application/json')
682
+ allow(request).to receive(:body).and_return(body)
683
+ allow(client).to receive(:auth_code).and_return(auth_code)
684
+ expect(subject).to receive(:client).and_return(client)
685
+
686
+ expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'sample' }, {})
687
+
688
+ subject.build_access_token
689
+ end
690
+
691
+ it 'reads the access token from a json request body' do
692
+ body = StringIO.new(%({"access_token":"valid_access_token"}))
693
+
694
+ allow(request).to receive(:xhr?).and_return(false)
695
+ allow(request).to receive(:content_type).and_return('application/json')
696
+ allow(request).to receive(:body).and_return(body)
697
+ expect(subject).to receive(:client).and_return(:client)
698
+
699
+ expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
700
+
701
+ token = subject.build_access_token
702
+ expect(token).to be_instance_of(::OAuth2::AccessToken)
703
+ expect(token.token).to eq('valid_access_token')
704
+ expect(token.client).to eq(:client)
705
+ end
706
+
550
707
  it 'should use callback_url without query_string if this is not an AJAX request' do
551
708
  allow(request).to receive(:xhr?).and_return(false)
552
709
  allow(request).to receive(:params).and_return('code' => 'valid_code')
710
+ allow(request).to receive(:content_type).and_return('application/x-www-form-urlencoded')
553
711
 
554
712
  client = double(:client)
555
713
  auth_code = double(:auth_code)
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-google-oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Ellithorpe
8
8
  - Yury Korolev
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-12-29 00:00:00.000000000 Z
12
+ date: 2022-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: jwt
@@ -25,34 +25,48 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '2.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: oauth2
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.1'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: omniauth
30
44
  requirement: !ruby/object:Gem::Requirement
31
45
  requirements:
32
- - - ">="
46
+ - - "~>"
33
47
  - !ruby/object:Gem::Version
34
- version: 1.1.1
48
+ version: '2.0'
35
49
  type: :runtime
36
50
  prerelease: false
37
51
  version_requirements: !ruby/object:Gem::Requirement
38
52
  requirements:
39
- - - ">="
53
+ - - "~>"
40
54
  - !ruby/object:Gem::Version
41
- version: 1.1.1
55
+ version: '2.0'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: omniauth-oauth2
44
58
  requirement: !ruby/object:Gem::Requirement
45
59
  requirements:
46
- - - ">="
60
+ - - "~>"
47
61
  - !ruby/object:Gem::Version
48
- version: '1.5'
62
+ version: 1.7.1
49
63
  type: :runtime
50
64
  prerelease: false
51
65
  version_requirements: !ruby/object:Gem::Requirement
52
66
  requirements:
53
- - - ">="
67
+ - - "~>"
54
68
  - !ruby/object:Gem::Version
55
- version: '1.5'
69
+ version: 1.7.1
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: rake
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +117,7 @@ executables: []
103
117
  extensions: []
104
118
  extra_rdoc_files: []
105
119
  files:
120
+ - ".github/workflows/ci.yml"
106
121
  - ".gitignore"
107
122
  - ".rubocop.yml"
108
123
  - ".travis.yml"
@@ -125,7 +140,7 @@ homepage: https://github.com/zquestz/omniauth-google-oauth2
125
140
  licenses:
126
141
  - MIT
127
142
  metadata: {}
128
- post_install_message:
143
+ post_install_message:
129
144
  rdoc_options: []
130
145
  require_paths:
131
146
  - lib
@@ -133,16 +148,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
148
  requirements:
134
149
  - - ">="
135
150
  - !ruby/object:Gem::Version
136
- version: '2.1'
151
+ version: '2.2'
137
152
  required_rubygems_version: !ruby/object:Gem::Requirement
138
153
  requirements:
139
154
  - - ">="
140
155
  - !ruby/object:Gem::Version
141
156
  version: '0'
142
157
  requirements: []
143
- rubyforge_project:
144
- rubygems_version: 2.6.11
145
- signing_key:
158
+ rubyforge_project:
159
+ rubygems_version: 2.7.9
160
+ signing_key:
146
161
  specification_version: 4
147
162
  summary: A Google OAuth2 strategy for OmniAuth 1.x
148
163
  test_files: []