omniauth-google-oauth2 0.6.0 → 1.1.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 +130 -0
- data/README.md +82 -12
- data/examples/Gemfile +2 -1
- data/examples/config.ru +19 -9
- data/examples/omni_auth.rb +4 -0
- data/lib/omniauth/google_oauth2/version.rb +1 -1
- data/lib/omniauth/strategies/google_oauth2.rb +76 -27
- data/omniauth-google-oauth2.gemspec +4 -3
- data/spec/omniauth/strategies/google_oauth2_spec.rb +221 -18
- metadata +30 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '0532842d8362fc36a8797376dc54e09b1d11fa178d462114225eecbe87274785'
|
4
|
+
data.tar.gz: 63b1d0a5a3a6249b77b58cbfa4e696ae18748b59280cb73f36cb98f88abd98c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d6f34a8629057f1f89d613155567d0bb50917648794e64527976ba7ca4c372ed8806fd57bbe5cec55d2d60545ef2e9bd038da80e1101b96642018f1d8226951
|
7
|
+
data.tar.gz: b00da9be3e3f97af0cdfe9a15be3d99126176a26bc21337febb893be88949da0573b4f6a843fbedac3dc982a4fda29ca532b63d3a1272fc8544468b3a42d32aa
|
@@ -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,135 @@
|
|
1
1
|
# Changelog
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## 1.1.1 - 2022-09-05
|
5
|
+
|
6
|
+
### Added
|
7
|
+
- Nothing.
|
8
|
+
|
9
|
+
### Deprecated
|
10
|
+
- Nothing.
|
11
|
+
|
12
|
+
### Removed
|
13
|
+
- Nothing.
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
- Fixed JWT decoding issue. (Invalid segment encoding) [#431](https://github.com/zquestz/omniauth-google-oauth2/pull/431)
|
17
|
+
|
18
|
+
## 1.1.0 - 2022-09-03
|
19
|
+
|
20
|
+
### Added
|
21
|
+
- `overridable_authorize_options` has been added to restrict overriding authorize_options by request params. [#423](https://github.com/zquestz/omniauth-google-oauth2/pull/423)
|
22
|
+
- Support for oauth2 2.0.x. [#429](https://github.com/zquestz/omniauth-google-oauth2/pull/429)
|
23
|
+
|
24
|
+
### Deprecated
|
25
|
+
- Nothing.
|
26
|
+
|
27
|
+
### Removed
|
28
|
+
- Nothing.
|
29
|
+
|
30
|
+
### Fixed
|
31
|
+
- Nothing.
|
32
|
+
|
33
|
+
## 1.0.1 - 2022-03-10
|
34
|
+
|
35
|
+
### Added
|
36
|
+
- Output granted scopes in credentials block of the auth hash.
|
37
|
+
- Migrated to GitHub actions.
|
38
|
+
|
39
|
+
### Deprecated
|
40
|
+
- Nothing.
|
41
|
+
|
42
|
+
### Removed
|
43
|
+
- Nothing.
|
44
|
+
|
45
|
+
### Fixed
|
46
|
+
- Overriding the `redirect_uri` via params or JSON request body.
|
47
|
+
|
48
|
+
## 1.0.0 - 2021-03-14
|
49
|
+
|
50
|
+
### Added
|
51
|
+
- Support for Omniauth 2.x!
|
52
|
+
|
53
|
+
### Deprecated
|
54
|
+
- Nothing.
|
55
|
+
|
56
|
+
### Removed
|
57
|
+
- Support for Omniauth 1.x.
|
58
|
+
|
59
|
+
### Fixed
|
60
|
+
- Nothing.
|
61
|
+
|
62
|
+
## 0.8.2 - 2021-03-14
|
63
|
+
|
64
|
+
### Added
|
65
|
+
- Constrains the version to Omniauth 1.x.
|
66
|
+
|
67
|
+
### Deprecated
|
68
|
+
- Nothing.
|
69
|
+
|
70
|
+
### Removed
|
71
|
+
- Nothing.
|
72
|
+
|
73
|
+
### Fixed
|
74
|
+
- Nothing.
|
75
|
+
|
76
|
+
## 0.8.1 - 2020-12-12
|
77
|
+
|
78
|
+
### Added
|
79
|
+
- Support reading the access token from a json request body.
|
80
|
+
|
81
|
+
### Deprecated
|
82
|
+
- Nothing.
|
83
|
+
|
84
|
+
### Removed
|
85
|
+
- No longer verify the iat claim for JWT.
|
86
|
+
|
87
|
+
### Fixed
|
88
|
+
- A few minor issues with .rubocop.yml.
|
89
|
+
- Issues with image resizing code when the image came with size information from Google.
|
90
|
+
|
91
|
+
## 0.8.0 - 2019-08-21
|
92
|
+
|
93
|
+
### Added
|
94
|
+
- Updated omniauth-oauth2 to v1.6.0 for security fixes.
|
95
|
+
|
96
|
+
### Deprecated
|
97
|
+
- Nothing.
|
98
|
+
|
99
|
+
### Removed
|
100
|
+
- Ruby 2.1 support.
|
101
|
+
|
102
|
+
### Fixed
|
103
|
+
- Nothing.
|
104
|
+
|
105
|
+
## 0.7.0 - 2019-06-03
|
106
|
+
|
107
|
+
### Added
|
108
|
+
- Ensure `info[:email]` is always verified, and include `unverified_email`
|
109
|
+
|
110
|
+
### Deprecated
|
111
|
+
- Nothing.
|
112
|
+
|
113
|
+
### Removed
|
114
|
+
- Nothing.
|
115
|
+
|
116
|
+
### Fixed
|
117
|
+
- Nothing.
|
118
|
+
|
119
|
+
## 0.6.1 - 2019-03-07
|
120
|
+
|
121
|
+
### Added
|
122
|
+
- Return `email` and `email_verified` keys in response.
|
123
|
+
|
124
|
+
### Deprecated
|
125
|
+
- Nothing.
|
126
|
+
|
127
|
+
### Removed
|
128
|
+
- Nothing.
|
129
|
+
|
130
|
+
### Fixed
|
131
|
+
- Nothing.
|
132
|
+
|
4
133
|
## 0.6.0 - 2018-12-28
|
5
134
|
|
6
135
|
### Added
|
@@ -12,6 +141,7 @@ All notable changes to this project will be documented in this file.
|
|
12
141
|
### Removed
|
13
142
|
- Support for JWT 1.x.
|
14
143
|
- Support for `raw_friend_info` and `raw_image_info`.
|
144
|
+
- Stop using Google+ API endpoints.
|
15
145
|
|
16
146
|
### Fixed
|
17
147
|
- 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,15 +81,19 @@ 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
|
+
* `overridable_authorize_options`: By default, all `authorize_options` can be overridden with request parameters. You can restrict the behavior by using this option.
|
89
|
+
|
90
|
+
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
91
|
|
88
92
|
```ruby
|
89
93
|
Rails.application.config.middleware.use OmniAuth::Builder do
|
90
94
|
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'],
|
91
95
|
{
|
92
|
-
scope: '
|
96
|
+
scope: 'email, profile, http://gdata.youtube.com',
|
93
97
|
prompt: 'select_account',
|
94
98
|
image_aspect_ratio: 'square',
|
95
99
|
image_size: 50
|
@@ -176,6 +180,8 @@ devise :omniauthable, omniauth_providers: [:google_oauth2]
|
|
176
180
|
Then make sure your callbacks controller is setup.
|
177
181
|
|
178
182
|
```ruby
|
183
|
+
# app/controllers/users/omniauth_callbacks_controller.rb:
|
184
|
+
|
179
185
|
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
180
186
|
def google_oauth2
|
181
187
|
# You need to implement the method below in your model (e.g. app/models/user.rb)
|
@@ -185,7 +191,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
185
191
|
flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'Google'
|
186
192
|
sign_in_and_redirect @user, event: :authentication
|
187
193
|
else
|
188
|
-
session['devise.google_data'] = request.env['omniauth.auth'].except(
|
194
|
+
session['devise.google_data'] = request.env['omniauth.auth'].except('extra') # Removing extra as it can overflow some session stores
|
189
195
|
redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
|
190
196
|
end
|
191
197
|
end
|
@@ -213,6 +219,10 @@ end
|
|
213
219
|
For your views you can login using:
|
214
220
|
|
215
221
|
```erb
|
222
|
+
<%# omniauth-google-oauth2 1.0.x uses OmniAuth 2 and requires using HTTP Post to initiate authentication: %>
|
223
|
+
<%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, method: :post %>
|
224
|
+
|
225
|
+
<%# omniauth-google-oauth2 prior 1.0.0: %>
|
216
226
|
<%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path %>
|
217
227
|
|
218
228
|
<%# Devise prior 4.1.0: %>
|
@@ -223,7 +233,7 @@ An overview is available at https://github.com/plataformatec/devise/wiki/OmniAut
|
|
223
233
|
|
224
234
|
### One-time Code Flow (Hybrid Authentication)
|
225
235
|
|
226
|
-
Google describes the One-time Code Flow [here](https://developers.google.com
|
236
|
+
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
237
|
|
228
238
|
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
239
|
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 +242,7 @@ Google describes the One-time Code Flow [here](https://developers.google.com/+/w
|
|
232
242
|
|
233
243
|
This flow is immune to replay attacks, and conveys no useful information to a man in the middle.
|
234
244
|
|
235
|
-
The omniauth-google-oauth2 gem supports this mode of operation
|
245
|
+
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
246
|
|
237
247
|
```javascript
|
238
248
|
// Basic hybrid auth example following the pattern at:
|
@@ -247,7 +257,7 @@ function init() {
|
|
247
257
|
// Ready.
|
248
258
|
$('.google-login-button').click(function(e) {
|
249
259
|
e.preventDefault();
|
250
|
-
|
260
|
+
|
251
261
|
gapi.auth2.authorize({
|
252
262
|
client_id: 'YOUR_CLIENT_ID',
|
253
263
|
cookie_policy: 'single_host_origin',
|
@@ -260,7 +270,7 @@ function init() {
|
|
260
270
|
success: function(data) {
|
261
271
|
// response from server
|
262
272
|
}
|
263
|
-
});
|
273
|
+
});
|
264
274
|
} else {
|
265
275
|
// google authentication failed
|
266
276
|
}
|
@@ -280,6 +290,66 @@ In that case, ensure to send an additional parameter `redirect_uri=` (empty stri
|
|
280
290
|
|
281
291
|
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
292
|
|
293
|
+
#### Getting around the `redirect_uri_mismatch` error (See [Issue #365](https://github.com/zquestz/omniauth-google-oauth2/issues/365))
|
294
|
+
|
295
|
+
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:
|
296
|
+
|
297
|
+
```javascript
|
298
|
+
// Initialize the GoogleAuth object
|
299
|
+
let googleAuth;
|
300
|
+
gapi.load('client:auth2', async () => {
|
301
|
+
await gapi.client.init({ scope: '...', client_id: '...' });
|
302
|
+
googleAuth = gapi.auth2.getAuthInstance();
|
303
|
+
});
|
304
|
+
|
305
|
+
// Call this when the Google Sign In button is clicked
|
306
|
+
async function signInGoogle() {
|
307
|
+
const googleUser = await googleAuth.signIn(); // wait for the user to authorize through the modal
|
308
|
+
const { access_token } = googleUser.getAuthResponse();
|
309
|
+
|
310
|
+
const data = new FormData();
|
311
|
+
data.append('access_token', access_token);
|
312
|
+
|
313
|
+
const response = await api.post('/auth/google_oauth2/callback', data)
|
314
|
+
console.log(response);
|
315
|
+
}
|
316
|
+
```
|
317
|
+
|
318
|
+
#### Using Axios
|
319
|
+
If you're making a GET resquests from another domain using `access_token`.
|
320
|
+
```
|
321
|
+
axios
|
322
|
+
.get(
|
323
|
+
'url(path to your callback}',
|
324
|
+
{ params: { access_token: 'token' } },
|
325
|
+
headers....
|
326
|
+
)
|
327
|
+
```
|
328
|
+
|
329
|
+
If you're making a POST resquests from another domain using `access_token`.
|
330
|
+
```
|
331
|
+
axios
|
332
|
+
.post(
|
333
|
+
'url(path to your callback}',
|
334
|
+
{ access_token: 'token' },
|
335
|
+
headers....
|
336
|
+
)
|
337
|
+
|
338
|
+
--OR--
|
339
|
+
|
340
|
+
axios
|
341
|
+
.post(
|
342
|
+
'url(path to your callback}',
|
343
|
+
null,
|
344
|
+
{
|
345
|
+
params: {
|
346
|
+
access_token: 'token'
|
347
|
+
},
|
348
|
+
headers....
|
349
|
+
}
|
350
|
+
)
|
351
|
+
```
|
352
|
+
|
283
353
|
## Fixing Protocol Mismatch for `redirect_uri` in Rails
|
284
354
|
|
285
355
|
Just set the `full_host` in OmniAuth based on the Rails.env.
|
data/examples/Gemfile
CHANGED
data/examples/config.ru
CHANGED
@@ -19,6 +19,19 @@ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
|
19
19
|
|
20
20
|
# Main example app for omniauth-google-oauth2
|
21
21
|
class App < Sinatra::Base
|
22
|
+
configure do
|
23
|
+
set :sessions, true
|
24
|
+
set :inline_templates, true
|
25
|
+
end
|
26
|
+
|
27
|
+
use Rack::Session::Cookie, secret: ENV['RACK_COOKIE_SECRET']
|
28
|
+
|
29
|
+
use OmniAuth::Builder do
|
30
|
+
# For additional provider examples please look at 'omni_auth.rb'
|
31
|
+
# The key provider_ignores_state is only for AJAX flows. It is not recommended for normal logins.
|
32
|
+
provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline', prompt: 'consent', provider_ignores_state: true, scope: 'email,profile,calendar'
|
33
|
+
end
|
34
|
+
|
22
35
|
get '/' do
|
23
36
|
<<-HTML
|
24
37
|
<!DOCTYPE html>
|
@@ -73,7 +86,12 @@ class App < Sinatra::Base
|
|
73
86
|
</head>
|
74
87
|
<body>
|
75
88
|
<ul>
|
76
|
-
<li
|
89
|
+
<li>
|
90
|
+
<form method='post' action='/auth/google_oauth2'>
|
91
|
+
<input type="hidden" name="authenticity_token" value="#{request.env['rack.session']['csrf']}">
|
92
|
+
<button type='submit'>Login with Google</button>
|
93
|
+
</form>
|
94
|
+
</li>
|
77
95
|
<li><a href='#' class="googleplus-login">Sign in with Google via AJAX</a></li>
|
78
96
|
</ul>
|
79
97
|
</body>
|
@@ -109,12 +127,4 @@ class App < Sinatra::Base
|
|
109
127
|
end
|
110
128
|
end
|
111
129
|
|
112
|
-
use Rack::Session::Cookie, secret: ENV['RACK_COOKIE_SECRET']
|
113
|
-
|
114
|
-
use OmniAuth::Builder do
|
115
|
-
# For additional provider examples please look at 'omni_auth.rb'
|
116
|
-
# The key provider_ignores_state is only for AJAX flows. It is not recommended for normal logins.
|
117
|
-
provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline', prompt: 'consent', provider_ignores_state: true, scope: 'email,profile,calendar'
|
118
|
-
end
|
119
|
-
|
120
130
|
run App.new
|
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,13 +14,16 @@ 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/
|
18
|
+
AUTHORIZE_OPTIONS = %i[access_type hd login_hint prompt request_visible_actions scope state redirect_uri include_granted_scopes openid_realm device_id device_name]
|
16
19
|
|
17
20
|
option :name, 'google_oauth2'
|
18
21
|
option :skip_friends, true
|
19
22
|
option :skip_image_info, true
|
20
23
|
option :skip_jwt, false
|
21
24
|
option :jwt_leeway, 60
|
22
|
-
option :authorize_options,
|
25
|
+
option :authorize_options, AUTHORIZE_OPTIONS
|
26
|
+
option :overridable_authorize_options, AUTHORIZE_OPTIONS
|
23
27
|
option :authorized_client_ids, []
|
24
28
|
|
25
29
|
option :client_options,
|
@@ -29,7 +33,7 @@ module OmniAuth
|
|
29
33
|
|
30
34
|
def authorize_params
|
31
35
|
super.tap do |params|
|
32
|
-
options[:authorize_options].each do |k|
|
36
|
+
(options[:authorize_options] & options[:overridable_authorize_options]).each do |k|
|
33
37
|
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
|
34
38
|
end
|
35
39
|
|
@@ -47,6 +51,8 @@ module OmniAuth
|
|
47
51
|
prune!(
|
48
52
|
name: raw_info['name'],
|
49
53
|
email: verified_email,
|
54
|
+
unverified_email: raw_info['email'],
|
55
|
+
email_verified: raw_info['email_verified'],
|
50
56
|
first_name: raw_info['given_name'],
|
51
57
|
last_name: raw_info['family_name'],
|
52
58
|
image: image_url,
|
@@ -56,11 +62,17 @@ module OmniAuth
|
|
56
62
|
)
|
57
63
|
end
|
58
64
|
|
65
|
+
credentials do
|
66
|
+
# Tokens and expiration will be used from OAuth2 strategy credentials block
|
67
|
+
prune!({ 'scope' => token_info(access_token.token)['scope'] })
|
68
|
+
end
|
69
|
+
|
59
70
|
extra do
|
60
71
|
hash = {}
|
61
|
-
|
62
|
-
|
63
|
-
|
72
|
+
token = nil_or_empty?(access_token['id_token']) ? access_token.token : access_token['id_token']
|
73
|
+
hash[:id_token] = token
|
74
|
+
if !options[:skip_jwt] && !nil_or_empty?(token)
|
75
|
+
decoded = ::JWT.decode(token, nil, false).first
|
64
76
|
|
65
77
|
# We have to manually verify the claims because the third parameter to
|
66
78
|
# JWT.decode is false since no verification key is provided.
|
@@ -72,7 +84,7 @@ module OmniAuth
|
|
72
84
|
verify_sub: false,
|
73
85
|
verify_expiration: true,
|
74
86
|
verify_not_before: true,
|
75
|
-
verify_iat:
|
87
|
+
verify_iat: false,
|
76
88
|
verify_jti: false,
|
77
89
|
leeway: options[:jwt_leeway])
|
78
90
|
|
@@ -92,31 +104,55 @@ module OmniAuth
|
|
92
104
|
verify_hd(access_token)
|
93
105
|
access_token
|
94
106
|
end
|
107
|
+
|
95
108
|
alias build_access_token custom_build_access_token
|
96
109
|
|
97
110
|
private
|
98
111
|
|
112
|
+
def nil_or_empty?(obj)
|
113
|
+
obj.is_a?(String) ? obj.empty? : obj.nil?
|
114
|
+
end
|
115
|
+
|
99
116
|
def callback_url
|
100
|
-
options[:redirect_uri] || (full_host +
|
117
|
+
options[:redirect_uri] || (full_host + callback_path)
|
101
118
|
end
|
102
119
|
|
103
120
|
def get_access_token(request)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
redirect_uri
|
111
|
-
|
112
|
-
elsif verify_token(request.params['access_token'])
|
121
|
+
verifier = request.params['code']
|
122
|
+
redirect_uri = request.params['redirect_uri']
|
123
|
+
access_token = request.params['access_token']
|
124
|
+
if verifier && request.xhr?
|
125
|
+
client_get_token(verifier, redirect_uri || 'postmessage')
|
126
|
+
elsif verifier
|
127
|
+
client_get_token(verifier, redirect_uri || callback_url)
|
128
|
+
elsif access_token && verify_token(access_token)
|
113
129
|
::OAuth2::AccessToken.from_hash(client, request.params.dup)
|
114
|
-
|
115
|
-
|
116
|
-
|
130
|
+
elsif request.content_type =~ /json/i
|
131
|
+
begin
|
132
|
+
body = JSON.parse(request.body.read)
|
133
|
+
request.body.rewind # rewind request body for downstream middlewares
|
134
|
+
verifier = body && body['code']
|
135
|
+
access_token = body && body['access_token']
|
136
|
+
redirect_uri ||= body && body['redirect_uri']
|
137
|
+
if verifier
|
138
|
+
client_get_token(verifier, redirect_uri || 'postmessage')
|
139
|
+
elsif verify_token(access_token)
|
140
|
+
::OAuth2::AccessToken.from_hash(client, body.dup)
|
141
|
+
end
|
142
|
+
rescue JSON::ParserError => e
|
143
|
+
warn "[omniauth google-oauth2] JSON parse error=#{e}"
|
144
|
+
end
|
117
145
|
end
|
118
146
|
end
|
119
147
|
|
148
|
+
def client_get_token(verifier, redirect_uri)
|
149
|
+
client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_token_params
|
153
|
+
deep_symbolize(options.auth_token_params || {})
|
154
|
+
end
|
155
|
+
|
120
156
|
def get_scope(params)
|
121
157
|
raw_scope = params[:scope] || DEFAULT_SCOPE
|
122
158
|
scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
|
@@ -124,7 +160,11 @@ module OmniAuth
|
|
124
160
|
scope_list.join(' ')
|
125
161
|
end
|
126
162
|
|
127
|
-
def
|
163
|
+
def verified_email
|
164
|
+
raw_info['email_verified'] ? raw_info['email'] : nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_token_options(redirect_uri = '')
|
128
168
|
{ redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
|
129
169
|
end
|
130
170
|
|
@@ -135,10 +175,6 @@ module OmniAuth
|
|
135
175
|
end
|
136
176
|
end
|
137
177
|
|
138
|
-
def verified_email
|
139
|
-
raw_info['email_verified'] ? raw_info['email'] : nil
|
140
|
-
end
|
141
|
-
|
142
178
|
def image_url
|
143
179
|
return nil unless raw_info['picture']
|
144
180
|
|
@@ -149,6 +185,10 @@ module OmniAuth
|
|
149
185
|
if path_index && image_size_opts_passed?
|
150
186
|
u.path.insert(path_index, image_params)
|
151
187
|
u.path = u.path.gsub('//', '/')
|
188
|
+
|
189
|
+
# Check if the image is already sized!
|
190
|
+
split_path = u.path.split('/')
|
191
|
+
u.path = u.path.sub("/#{split_path[-3]}", '') if split_path[-3] =~ IMAGE_SIZE_REGEXP
|
152
192
|
end
|
153
193
|
|
154
194
|
u.query = strip_unnecessary_query_parameters(u.query)
|
@@ -187,12 +227,21 @@ module OmniAuth
|
|
187
227
|
URI.encode_www_form(stripped_params)
|
188
228
|
end
|
189
229
|
|
230
|
+
def token_info(access_token)
|
231
|
+
return nil unless access_token
|
232
|
+
|
233
|
+
@token_info ||= Hash.new do |h, k|
|
234
|
+
h[k] = client.request(:get, 'https://www.googleapis.com/oauth2/v3/tokeninfo', params: { access_token: access_token }).parsed
|
235
|
+
end
|
236
|
+
|
237
|
+
@token_info[access_token]
|
238
|
+
end
|
239
|
+
|
190
240
|
def verify_token(access_token)
|
191
241
|
return false unless access_token
|
192
242
|
|
193
|
-
|
194
|
-
|
195
|
-
raw_response['aud'] == options.client_id || options.authorized_client_ids.include?(raw_response['aud'])
|
243
|
+
token_info = token_info(access_token)
|
244
|
+
token_info['aud'] == options.client_id || options.authorized_client_ids.include?(token_info['aud'])
|
196
245
|
end
|
197
246
|
|
198
247
|
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', '~> 2.0.6'
|
25
|
+
gem.add_runtime_dependency 'omniauth', '~> 2.0'
|
26
|
+
gem.add_runtime_dependency 'omniauth-oauth2', '~> 1.8.0'
|
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
|
@@ -241,9 +242,18 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
241
242
|
context "authorize option #{k}" do
|
242
243
|
let(:request) { double('Request', params: { k.to_s => 'http://example.com' }, cookies: {}, env: {}) }
|
243
244
|
|
244
|
-
|
245
|
-
|
246
|
-
|
245
|
+
context 'when overridable_authorize_options is default' do
|
246
|
+
it "should set the #{k} authorize option dynamically in the request" do
|
247
|
+
@options = { k: '' }
|
248
|
+
expect(subject.authorize_params[k.to_s]).to eq('http://example.com')
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'when overridable_authorize_options is empty' do
|
253
|
+
it "should not set the #{k} authorize option dynamically in the request" do
|
254
|
+
@options = { k: '', overridable_authorize_options: [] }
|
255
|
+
expect(subject.authorize_params[k.to_s]).not_to eq('http://example.com')
|
256
|
+
end
|
247
257
|
end
|
248
258
|
end
|
249
259
|
end
|
@@ -251,9 +261,18 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
251
261
|
describe 'custom authorize_options' do
|
252
262
|
let(:request) { double('Request', params: { 'foo' => 'something' }, cookies: {}, env: {}) }
|
253
263
|
|
254
|
-
|
255
|
-
|
256
|
-
|
264
|
+
context 'when overridable_authorize_options is default' do
|
265
|
+
it 'should not support request overrides from custom authorize_options' do
|
266
|
+
@options = { authorize_options: [:foo], foo: '' }
|
267
|
+
expect(subject.authorize_params['foo']).not_to eq('something')
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'when overridable_authorize_options is customized' do
|
272
|
+
it 'should support request overrides from custom authorize_options' do
|
273
|
+
@options = { authorize_options: [:foo], overridable_authorize_options: [:foo], foo: '' }
|
274
|
+
expect(subject.authorize_params['foo']).to eq('something')
|
275
|
+
end
|
257
276
|
end
|
258
277
|
end
|
259
278
|
end
|
@@ -288,14 +307,92 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
288
307
|
end
|
289
308
|
end
|
290
309
|
|
291
|
-
describe '#
|
310
|
+
describe '#callback_url' do
|
311
|
+
let(:base_url) { 'https://example.com' }
|
312
|
+
|
292
313
|
it 'has the correct default callback path' do
|
293
|
-
|
314
|
+
allow(subject).to receive(:full_host) { base_url }
|
315
|
+
allow(subject).to receive(:script_name) { '' }
|
316
|
+
expect(subject.send(:callback_url)).to eq(base_url + '/auth/google_oauth2/callback')
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'should set the callback path with script_name if present' do
|
320
|
+
allow(subject).to receive(:full_host) { base_url }
|
321
|
+
allow(subject).to receive(:script_name) { '/v1' }
|
322
|
+
expect(subject.send(:callback_url)).to eq(base_url + '/v1/auth/google_oauth2/callback')
|
294
323
|
end
|
295
324
|
|
296
325
|
it 'should set the callback_path parameter if present' do
|
297
326
|
@options = { callback_path: '/auth/foo/callback' }
|
298
|
-
|
327
|
+
allow(subject).to receive(:full_host) { base_url }
|
328
|
+
allow(subject).to receive(:script_name) { '' }
|
329
|
+
expect(subject.send(:callback_url)).to eq(base_url + '/auth/foo/callback')
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe '#info' do
|
334
|
+
let(:client) do
|
335
|
+
OAuth2::Client.new('abc', 'def') do |builder|
|
336
|
+
builder.request :url_encoded
|
337
|
+
builder.adapter :test do |stub|
|
338
|
+
stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, response_hash.to_json] }
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, { 'access_token' => 'a' }) }
|
343
|
+
before { allow(subject).to receive(:access_token).and_return(access_token) }
|
344
|
+
|
345
|
+
context 'with verified email' do
|
346
|
+
let(:response_hash) do
|
347
|
+
{ email: 'something@domain.invalid', email_verified: true }
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'should return equal email and unverified_email' do
|
351
|
+
expect(subject.info[:email]).to eq('something@domain.invalid')
|
352
|
+
expect(subject.info[:unverified_email]).to eq('something@domain.invalid')
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
context 'with unverified email' do
|
357
|
+
let(:response_hash) do
|
358
|
+
{ email: 'something@domain.invalid', email_verified: false }
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'should return nil email, and correct unverified email' do
|
362
|
+
expect(subject.info[:email]).to eq(nil)
|
363
|
+
expect(subject.info[:unverified_email]).to eq('something@domain.invalid')
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe '#credentials' do
|
369
|
+
let(:client) { OAuth2::Client.new('abc', 'def') }
|
370
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, access_token: 'valid_access_token', expires_at: 123_456_789, refresh_token: 'valid_refresh_token') }
|
371
|
+
before(:each) do
|
372
|
+
allow(subject).to receive(:access_token).and_return(access_token)
|
373
|
+
subject.options.client_options[:connection_build] = proc do |builder|
|
374
|
+
builder.request :url_encoded
|
375
|
+
builder.adapter :test do |stub|
|
376
|
+
stub.get('/oauth2/v3/tokeninfo?access_token=valid_access_token') do
|
377
|
+
[200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
|
378
|
+
aud: '000000000000.apps.googleusercontent.com',
|
379
|
+
sub: '123456789',
|
380
|
+
scope: 'profile email'
|
381
|
+
)]
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should return access token and (optionally) refresh token' do
|
388
|
+
expect(subject.credentials.to_h).to \
|
389
|
+
match(hash_including(
|
390
|
+
'token' => 'valid_access_token',
|
391
|
+
'refresh_token' => 'valid_refresh_token',
|
392
|
+
'scope' => 'profile email',
|
393
|
+
'expires_at' => 123_456_789,
|
394
|
+
'expires' => true
|
395
|
+
))
|
299
396
|
end
|
300
397
|
end
|
301
398
|
|
@@ -308,12 +405,10 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
308
405
|
end
|
309
406
|
end
|
310
407
|
end
|
311
|
-
let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
|
312
|
-
|
313
408
|
before { allow(subject).to receive(:access_token).and_return(access_token) }
|
314
409
|
|
315
410
|
describe 'id_token' do
|
316
|
-
shared_examples 'id_token issued by valid issuer' do |issuer|
|
411
|
+
shared_examples 'id_token issued by valid issuer' do |issuer|
|
317
412
|
context 'when the id_token is passed into the access token' do
|
318
413
|
let(:token_info) do
|
319
414
|
{
|
@@ -370,7 +465,10 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
370
465
|
end
|
371
466
|
end
|
372
467
|
|
373
|
-
context 'when the
|
468
|
+
context 'when the access token is empty or nil' do
|
469
|
+
let(:access_token) { OAuth2::AccessToken.new(client, nil, { 'refresh_token' => 'foo' }) }
|
470
|
+
before { allow(subject.extra).to receive(:access_token).and_return(access_token) }
|
471
|
+
|
374
472
|
it 'should not include id_token' do
|
375
473
|
expect(subject.extra).not_to have_key(:id_token)
|
376
474
|
end
|
@@ -382,6 +480,19 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
382
480
|
end
|
383
481
|
|
384
482
|
describe 'raw_info' do
|
483
|
+
let(:token_info) do
|
484
|
+
{
|
485
|
+
'abc' => 'xyz',
|
486
|
+
'exp' => Time.now.to_i + 3600,
|
487
|
+
'nbf' => Time.now.to_i - 60,
|
488
|
+
'iat' => Time.now.to_i,
|
489
|
+
'aud' => 'appid',
|
490
|
+
'iss' => 'accounts.google.com'
|
491
|
+
}
|
492
|
+
end
|
493
|
+
let(:id_token) { JWT.encode(token_info, 'secret') }
|
494
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
|
495
|
+
|
385
496
|
context 'when skip_info is true' do
|
386
497
|
before { subject.options[:skip_info] = true }
|
387
498
|
|
@@ -426,6 +537,12 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
426
537
|
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50/photo.jpg')
|
427
538
|
end
|
428
539
|
|
540
|
+
it 'should return the image with size specified in the `image_size` option when sizing is in the picture' do
|
541
|
+
@options = { image_size: 50 }
|
542
|
+
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh4.googleusercontent.com/url/s96-c/photo.jpg' } }
|
543
|
+
expect(subject.info[:image]).to eq('https://lh4.googleusercontent.com/url/s50/photo.jpg')
|
544
|
+
end
|
545
|
+
|
429
546
|
it 'should handle a picture with too many slashes correctly' do
|
430
547
|
@options = { image_size: 50 }
|
431
548
|
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url//photo.jpg' } }
|
@@ -456,24 +573,48 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
456
573
|
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40/photo.jpg')
|
457
574
|
end
|
458
575
|
|
576
|
+
it 'should return the image with width and height specified in the `image_size` option when sizing is in the picture' do
|
577
|
+
@options = { image_size: { width: 50, height: 40 } }
|
578
|
+
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/w100-h80-c/photo.jpg' } }
|
579
|
+
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40/photo.jpg')
|
580
|
+
end
|
581
|
+
|
459
582
|
it 'should return square image when `image_aspect_ratio` is specified' do
|
460
583
|
@options = { image_aspect_ratio: 'square' }
|
461
584
|
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
|
462
585
|
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/c/photo.jpg')
|
463
586
|
end
|
464
587
|
|
588
|
+
it 'should return square image when `image_aspect_ratio` is specified and sizing is in the picture' do
|
589
|
+
@options = { image_aspect_ratio: 'square' }
|
590
|
+
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/c/photo.jpg' } }
|
591
|
+
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/c/photo.jpg')
|
592
|
+
end
|
593
|
+
|
465
594
|
it 'should return square sized image when `image_aspect_ratio` and `image_size` is set' do
|
466
595
|
@options = { image_aspect_ratio: 'square', image_size: 50 }
|
467
596
|
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
|
468
597
|
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50-c/photo.jpg')
|
469
598
|
end
|
470
599
|
|
600
|
+
it 'should return square sized image when `image_aspect_ratio` and `image_size` is set and sizing is in the picture' do
|
601
|
+
@options = { image_aspect_ratio: 'square', image_size: 50 }
|
602
|
+
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/s90/photo.jpg' } }
|
603
|
+
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/s50-c/photo.jpg')
|
604
|
+
end
|
605
|
+
|
471
606
|
it 'should return square sized image when `image_aspect_ratio` and `image_size` has height and width' do
|
472
607
|
@options = { image_aspect_ratio: 'square', image_size: { width: 50, height: 40 } }
|
473
608
|
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photo.jpg' } }
|
474
609
|
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40-c/photo.jpg')
|
475
610
|
end
|
476
611
|
|
612
|
+
it 'should return square sized image when `image_aspect_ratio` and `image_size` has height and width and sizing is in the picture' do
|
613
|
+
@options = { image_aspect_ratio: 'square', image_size: { width: 50, height: 40 } }
|
614
|
+
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/w100-h80/photo.jpg' } }
|
615
|
+
expect(subject.info[:image]).to eq('https://lh3.googleusercontent.com/url/w50-h40-c/photo.jpg')
|
616
|
+
end
|
617
|
+
|
477
618
|
it 'should return original image if image url does not end in `photo.jpg`' do
|
478
619
|
@options = { image_size: 50 }
|
479
620
|
allow(subject).to receive(:raw_info) { { 'picture' => 'https://lh3.googleusercontent.com/url/photograph.jpg' } }
|
@@ -536,20 +677,82 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
536
677
|
end
|
537
678
|
|
538
679
|
it 'should read access_token from hash if this is not an AJAX request with a code parameter' do
|
680
|
+
client = OAuth2::Client.new('abc', 'def') do |builder|
|
681
|
+
builder.request :url_encoded
|
682
|
+
builder.adapter :test do |stub|
|
683
|
+
stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
539
687
|
allow(request).to receive(:xhr?).and_return(false)
|
540
688
|
allow(request).to receive(:params).and_return('access_token' => 'valid_access_token')
|
541
689
|
expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
|
542
|
-
expect(subject).to receive(:client).and_return(
|
690
|
+
expect(subject).to receive(:client).and_return(client)
|
691
|
+
|
692
|
+
token = subject.build_access_token
|
693
|
+
expect(token).to be_instance_of(::OAuth2::AccessToken)
|
694
|
+
expect(token.token).to eq('valid_access_token')
|
695
|
+
expect(token.client).to eq(client)
|
696
|
+
end
|
697
|
+
|
698
|
+
it 'reads the code from a json request body' do
|
699
|
+
body = StringIO.new(%({"code":"json_access_token"}))
|
700
|
+
client = double(:client)
|
701
|
+
auth_code = double(:auth_code)
|
702
|
+
|
703
|
+
allow(request).to receive(:xhr?).and_return(false)
|
704
|
+
allow(request).to receive(:content_type).and_return('application/json')
|
705
|
+
allow(request).to receive(:body).and_return(body)
|
706
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
707
|
+
expect(subject).to receive(:client).and_return(client)
|
708
|
+
|
709
|
+
expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'postmessage' }, {})
|
710
|
+
|
711
|
+
subject.build_access_token
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'reads the redirect uri from a json request body' do
|
715
|
+
body = StringIO.new(%({"code":"json_access_token", "redirect_uri":"sample"}))
|
716
|
+
client = double(:client)
|
717
|
+
auth_code = double(:auth_code)
|
718
|
+
|
719
|
+
allow(request).to receive(:xhr?).and_return(false)
|
720
|
+
allow(request).to receive(:content_type).and_return('application/json')
|
721
|
+
allow(request).to receive(:body).and_return(body)
|
722
|
+
allow(client).to receive(:auth_code).and_return(auth_code)
|
723
|
+
expect(subject).to receive(:client).and_return(client)
|
724
|
+
|
725
|
+
expect(auth_code).to receive(:get_token).with('json_access_token', { redirect_uri: 'sample' }, {})
|
726
|
+
|
727
|
+
subject.build_access_token
|
728
|
+
end
|
729
|
+
|
730
|
+
it 'reads the access token from a json request body' do
|
731
|
+
body = StringIO.new(%({"access_token":"valid_access_token"}))
|
732
|
+
client = OAuth2::Client.new('abc', 'def') do |builder|
|
733
|
+
builder.request :url_encoded
|
734
|
+
builder.adapter :test do |stub|
|
735
|
+
stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
allow(request).to receive(:xhr?).and_return(false)
|
740
|
+
allow(request).to receive(:content_type).and_return('application/json')
|
741
|
+
allow(request).to receive(:body).and_return(body)
|
742
|
+
expect(subject).to receive(:client).and_return(client)
|
743
|
+
|
744
|
+
expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
|
543
745
|
|
544
746
|
token = subject.build_access_token
|
545
747
|
expect(token).to be_instance_of(::OAuth2::AccessToken)
|
546
748
|
expect(token.token).to eq('valid_access_token')
|
547
|
-
expect(token.client).to eq(
|
749
|
+
expect(token.client).to eq(client)
|
548
750
|
end
|
549
751
|
|
550
752
|
it 'should use callback_url without query_string if this is not an AJAX request' do
|
551
753
|
allow(request).to receive(:xhr?).and_return(false)
|
552
754
|
allow(request).to receive(:params).and_return('code' => 'valid_code')
|
755
|
+
allow(request).to receive(:content_type).and_return('application/x-www-form-urlencoded')
|
553
756
|
|
554
757
|
client = double(:client)
|
555
758
|
auth_code = double(:auth_code)
|
@@ -619,7 +822,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
|
|
619
822
|
end
|
620
823
|
end
|
621
824
|
end
|
622
|
-
let(:access_token) { OAuth2::AccessToken.from_hash(client, {}) }
|
825
|
+
let(:access_token) { OAuth2::AccessToken.from_hash(client, { 'access_token' => 'foo' }) }
|
623
826
|
|
624
827
|
context 'when domain is nil' do
|
625
828
|
let(:client) do
|
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:
|
4
|
+
version: 1.1.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-09-05 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: 2.0.6
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 2.0.6
|
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.8.0
|
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.8.0
|
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,15 @@ 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
|
-
|
144
|
-
|
145
|
-
signing_key:
|
158
|
+
rubygems_version: 3.0.9
|
159
|
+
signing_key:
|
146
160
|
specification_version: 4
|
147
161
|
summary: A Google OAuth2 strategy for OmniAuth 1.x
|
148
162
|
test_files: []
|