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 +5 -5
- data/.github/workflows/ci.yml +21 -0
- data/.rubocop.yml +2 -5
- data/.travis.yml +7 -5
- data/CHANGELOG.md +101 -0
- data/README.md +79 -11
- data/examples/Gemfile +2 -1
- data/examples/omni_auth.rb +4 -0
- data/lib/omniauth/google_oauth2/version.rb +1 -1
- data/lib/omniauth/strategies/google_oauth2.rb +64 -22
- data/omniauth-google-oauth2.gemspec +4 -3
- data/spec/omniauth/strategies/google_oauth2_spec.rb +164 -6
- metadata +31 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3b1fdc81978e86a4a1b0c493bc1d83f9e6fa1e613bfb69b2feee8af2a6869b99
|
4
|
+
data.tar.gz: 02efca2850b5e053630aa7da102a9162aca87d16d6ab9fb6408654fd98c145c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
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
|
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 `[
|
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
|
-
|
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(
|
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
|
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
|
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
data/examples/omni_auth.rb
CHANGED
@@ -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'
|
@@ -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:
|
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 +
|
110
|
+
options[:redirect_uri] || (full_host + callback_path)
|
101
111
|
end
|
102
112
|
|
103
113
|
def get_access_token(request)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
redirect_uri
|
111
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
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
|
-
|
194
|
-
|
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.
|
21
|
+
gem.required_ruby_version = '>= 2.2'
|
22
22
|
|
23
23
|
gem.add_runtime_dependency 'jwt', '>= 2.0'
|
24
|
-
gem.add_runtime_dependency '
|
25
|
-
gem.add_runtime_dependency 'omniauth
|
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: '
|
181
|
-
expect(subject.authorize_params['scope']).to eq('https://www.googleapis.com/auth/
|
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 '#
|
292
|
+
describe '#callback_url' do
|
293
|
+
let(:base_url) { 'https://example.com' }
|
294
|
+
|
292
295
|
it 'has the correct default callback path' do
|
293
|
-
|
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
|
-
|
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|
|
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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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.
|
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: []
|