omniauth-google-oauth2 0.5.4 → 0.6.0

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
2
  SHA1:
3
- metadata.gz: 2a7656373110c92e06996caa456eea2a5dd429b6
4
- data.tar.gz: 54fa84ae8d29ab08e9387e79a7dde7796b4f5ddf
3
+ metadata.gz: 81997c7fd03317eea408408d6f18159972b8f3dc
4
+ data.tar.gz: d16e88cdee0f4fd599d4b8d8c05ac40ebc761c07
5
5
  SHA512:
6
- metadata.gz: d8c0b4beb4561a205866bbb36ef4fe5b438d95e07cf8832f1e657e48f04657e98d99c28bf69ad3f53b564c47017435cc8d38a7c9eb48700a0b07a642a6012624
7
- data.tar.gz: 7e3fd2adca8bb3227da30c0492c5738fdbeb7e50c8ccb8df2dac2379407ad5230092d26f4942e1654fdf680c2c961556ad111ab71d5fb788f48dcc1e24f3f324
6
+ metadata.gz: eb2b1d82471d000a983728b92676e682d7006b0a3551bcb109cd9216a3114e6d78500bdd3d29ae71b55c95f4b7a2315f4d3930bf017edc7a2506641bb51cfc06
7
+ data.tar.gz: 27605544db858360570a109a922689ec100bd70ff4ea63318a67f2ba6bc5a91c1f464e9110088addba9c83fc62a6fcae13ce54f22f53abcaf5933d04cb1538ff
@@ -1,6 +1,21 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## 0.6.0 - 2018-12-28
5
+
6
+ ### Added
7
+ - Support for JWT 2.x.
8
+
9
+ ### Deprecated
10
+ - Nothing.
11
+
12
+ ### Removed
13
+ - Support for JWT 1.x.
14
+ - Support for `raw_friend_info` and `raw_image_info`.
15
+
16
+ ### Fixed
17
+ - Nothing.
18
+
4
19
  ## 0.5.4 - 2018-12-07
5
20
 
6
21
  ### Added
data/README.md CHANGED
@@ -7,8 +7,6 @@ Strategy to authenticate with Google via OAuth2 in OmniAuth.
7
7
 
8
8
  Get your API key at: https://code.google.com/apis/console/ Note the Client ID and the Client Secret.
9
9
 
10
- **Note**: You must enable the "Contacts API" and "Google+ API" via the Google API console. Otherwise, you will receive an `OAuth2::Error`(`Error: "Invalid credentials"`) stating that access is not configured when you attempt to authenticate.
11
-
12
10
  For more details, read the Google docs: https://developers.google.com/accounts/docs/OAuth2
13
11
 
14
12
  ## Installation
@@ -25,8 +23,6 @@ Then `bundle install`.
25
23
 
26
24
  * Go to 'https://console.developers.google.com'
27
25
  * Select your project.
28
- * Click 'Enable and manage APIs'.
29
- * Make sure "Contacts API" and "Google+ API" are on.
30
26
  * Go to Credentials, then select the "OAuth consent screen" tab on top, and provide an 'EMAIL ADDRESS' and a 'PRODUCT NAME'
31
27
  * Wait 10 minutes for changes to take effect.
32
28
 
@@ -87,15 +83,13 @@ You can configure several options, which you pass in to the `provider` method vi
87
83
 
88
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.
89
85
 
90
- * `verify_iss`: Allows you to disable iss validation when decoding the JWT. This was added since Google now returns either `accounts.google.com` or `https://accounts.google.com`, and there is no way to predict what they will return, causing JWT validation failures.
91
-
92
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:
93
87
 
94
88
  ```ruby
95
89
  Rails.application.config.middleware.use OmniAuth::Builder do
96
90
  provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'],
97
91
  {
98
- scope: 'userinfo.email, userinfo.profile, plus.me, http://gdata.youtube.com',
92
+ scope: 'userinfo.email, userinfo.profile, http://gdata.youtube.com',
99
93
  prompt: 'select_account',
100
94
  image_aspect_ratio: 'square',
101
95
  image_size: 50
@@ -141,8 +135,6 @@ Here's an example of an authentication hash available in the callback by accessi
141
135
  "exp" => 1496120719
142
136
  },
143
137
  "raw_info" => {
144
- "kind" => "plus#personOpenIdConnect",
145
- "gender" => "male",
146
138
  "sub" => "100000000000000000000",
147
139
  "name" => "John Smith",
148
140
  "given_name" => "John",
@@ -244,48 +236,38 @@ The omniauth-google-oauth2 gem supports this mode of operation out of the box.
244
236
 
245
237
  ```javascript
246
238
  // Basic hybrid auth example following the pattern at:
247
- // https://developers.google.com/api-client-library/javascript/features/authentication#Authexample
248
- jQuery(function() {
249
- return $.ajax({
250
- url: 'https://apis.google.com/js/client:plus.js?onload=gpAsyncInit',
251
- dataType: 'script',
252
- cache: true
253
- });
254
- });
255
-
256
- window.gpAsyncInit = function() {
257
- gapi.auth.authorize({
258
- immediate: true,
259
- response_type: 'code',
260
- cookie_policy: 'single_host_origin',
261
- client_id: 'YOUR_CLIENT_ID',
262
- scope: 'email profile'
263
- }, function(response) {
264
- return;
265
- });
266
- $('.googleplus-login').click(function(e) {
267
- e.preventDefault();
268
- gapi.auth.authorize({
269
- immediate: false,
270
- response_type: 'code',
271
- cookie_policy: 'single_host_origin',
272
- client_id: 'YOUR_CLIENT_ID',
273
- scope: 'email profile'
274
- }, function(response) {
275
- if (response && !response.error) {
276
- // google authentication succeed, now post data to server.
277
- jQuery.ajax({type: 'POST', url: '/auth/google_oauth2/callback', data: response,
278
- success: function(data) {
279
- // response from server
280
- }
281
- });
282
- } else {
283
- // google authentication failed
284
- }
239
+ // https://developers.google.com/identity/sign-in/web/reference
240
+
241
+ <script src="https://apis.google.com/js/platform.js?onload=init" async defer></script>
242
+
243
+ ...
244
+
245
+ function init() {
246
+ gapi.load('auth2', function() {
247
+ // Ready.
248
+ $('.google-login-button').click(function(e) {
249
+ e.preventDefault();
250
+
251
+ gapi.auth2.authorize({
252
+ client_id: 'YOUR_CLIENT_ID',
253
+ cookie_policy: 'single_host_origin',
254
+ scope: 'email profile',
255
+ response_type: 'code'
256
+ }, function(response) {
257
+ if (response && !response.error) {
258
+ // google authentication succeed, now post data to server.
259
+ jQuery.ajax({type: 'POST', url: '/auth/google_oauth2/callback', data: response,
260
+ success: function(data) {
261
+ // response from server
262
+ }
263
+ });
264
+ } else {
265
+ // google authentication failed
266
+ }
267
+ });
285
268
  });
286
269
  });
287
270
  };
288
-
289
271
  ```
290
272
 
291
273
  #### Note about mobile clients (iOS, Android)
@@ -309,7 +291,7 @@ OmniAuth.config.full_host = Rails.env.production? ? 'https://domain.com' : 'http
309
291
 
310
292
  ## License
311
293
 
312
- Copyright (c) 2017 by Josh Ellithorpe
294
+ Copyright (c) 2018 by Josh Ellithorpe
313
295
 
314
296
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
315
297
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module GoogleOauth2
5
- VERSION = '0.5.4'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
@@ -8,9 +8,11 @@ module OmniAuth
8
8
  module Strategies
9
9
  # Main class for Google OAuth2 strategy.
10
10
  class GoogleOauth2 < OmniAuth::Strategies::OAuth2
11
+ ALLOWED_ISSUERS = ['accounts.google.com', 'https://accounts.google.com'].freeze
11
12
  BASE_SCOPE_URL = 'https://www.googleapis.com/auth/'
12
13
  BASE_SCOPES = %w[profile email openid].freeze
13
14
  DEFAULT_SCOPE = 'email,profile'
15
+ USER_INFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
14
16
 
15
17
  option :name, 'google_oauth2'
16
18
  option :skip_friends, true
@@ -19,7 +21,6 @@ module OmniAuth
19
21
  option :jwt_leeway, 60
20
22
  option :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]
21
23
  option :authorized_client_ids, []
22
- option :verify_iss, true
23
24
 
24
25
  option :client_options,
25
26
  site: 'https://oauth2.googleapis.com',
@@ -59,35 +60,30 @@ module OmniAuth
59
60
  hash = {}
60
61
  hash[:id_token] = access_token['id_token']
61
62
  if !options[:skip_jwt] && !access_token['id_token'].nil?
62
- hash[:id_info] = ::JWT.decode(
63
- access_token['id_token'], nil, false, verify_iss: options.verify_iss,
64
- iss: 'accounts.google.com',
65
- verify_aud: true,
66
- aud: options.client_id,
67
- verify_sub: false,
68
- verify_expiration: true,
69
- verify_not_before: true,
70
- verify_iat: true,
71
- verify_jti: false,
72
- leeway: options[:jwt_leeway]
73
- ).first
63
+ decoded = ::JWT.decode(access_token['id_token'], nil, false).first
64
+
65
+ # We have to manually verify the claims because the third parameter to
66
+ # JWT.decode is false since no verification key is provided.
67
+ ::JWT::Verify.verify_claims(decoded,
68
+ verify_iss: true,
69
+ iss: ALLOWED_ISSUERS,
70
+ verify_aud: true,
71
+ aud: options.client_id,
72
+ verify_sub: false,
73
+ verify_expiration: true,
74
+ verify_not_before: true,
75
+ verify_iat: true,
76
+ verify_jti: false,
77
+ leeway: options[:jwt_leeway])
78
+
79
+ hash[:id_info] = decoded
74
80
  end
75
81
  hash[:raw_info] = raw_info unless skip_info?
76
- hash[:raw_friend_info] = raw_friend_info(raw_info['sub']) unless skip_info? || options[:skip_friends]
77
- hash[:raw_image_info] = raw_image_info(raw_info['sub']) unless skip_info? || options[:skip_image_info]
78
82
  prune! hash
79
83
  end
80
84
 
81
85
  def raw_info
82
- @raw_info ||= access_token.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect').parsed
83
- end
84
-
85
- def raw_friend_info(id)
86
- @raw_friend_info ||= access_token.get("https://www.googleapis.com/plus/v1/people/#{id}/people/visible").parsed
87
- end
88
-
89
- def raw_image_info(id)
90
- @raw_image_info ||= access_token.get("https://www.googleapis.com/plus/v1/people/#{id}?fields=image").parsed
86
+ @raw_info ||= access_token.get(USER_INFO_URL).parsed
91
87
  end
92
88
 
93
89
  def custom_build_access_token
@@ -202,7 +198,7 @@ module OmniAuth
202
198
  def verify_hd(access_token)
203
199
  return true unless options.hd
204
200
 
205
- @raw_info ||= access_token.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect').parsed
201
+ @raw_info ||= access_token.get(USER_INFO_URL).parsed
206
202
 
207
203
  options.hd = options.hd.call if options.hd.is_a? Proc
208
204
  allowed_hosted_domains = Array(options.hd)
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.required_ruby_version = '>= 2.1'
22
22
 
23
- gem.add_runtime_dependency 'jwt', '>= 1.5'
23
+ gem.add_runtime_dependency 'jwt', '>= 2.0'
24
24
  gem.add_runtime_dependency 'omniauth', '>= 1.1.1'
25
25
  gem.add_runtime_dependency 'omniauth-oauth2', '>= 1.5'
26
26
 
@@ -304,9 +304,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
304
304
  OAuth2::Client.new('abc', 'def') do |builder|
305
305
  builder.request :url_encoded
306
306
  builder.adapter :test do |stub|
307
- stub.get('/plus/v1/people/me/openIdConnect') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
308
- stub.get('/plus/v1/people/12345/people/visible') { [200, { 'content-type' => 'application/json' }, '[{"foo":"bar"}]'] }
309
- stub.get('/plus/v1/people/12345?fields=image') { [200, { 'content-type' => 'application/json' }, '{"image":"imageData"}'] }
307
+ stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
310
308
  end
311
309
  end
312
310
  end
@@ -315,35 +313,60 @@ describe OmniAuth::Strategies::GoogleOauth2 do
315
313
  before { allow(subject).to receive(:access_token).and_return(access_token) }
316
314
 
317
315
  describe 'id_token' do
318
- context 'when the id_token is passed into the access token' do
319
- token_info =
316
+ shared_examples 'id_token issued by valid issuer' do |issuer| # rubocop:disable Metrics/BlockLength
317
+ context 'when the id_token is passed into the access token' do
318
+ let(:token_info) do
319
+ {
320
+ 'abc' => 'xyz',
321
+ 'exp' => Time.now.to_i + 3600,
322
+ 'nbf' => Time.now.to_i - 60,
323
+ 'iat' => Time.now.to_i,
324
+ 'aud' => 'appid',
325
+ 'iss' => issuer
326
+ }
327
+ end
328
+ let(:id_token) { JWT.encode(token_info, 'secret') }
329
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
330
+
331
+ it 'should include id_token when set on the access_token' do
332
+ expect(subject.extra).to include(id_token: id_token)
333
+ end
334
+
335
+ it 'should include id_info when id_token is set on the access_token and skip_jwt is false' do
336
+ subject.options[:skip_jwt] = false
337
+ expect(subject.extra).to include(id_info: token_info)
338
+ end
339
+
340
+ it 'should not include id_info when id_token is set on the access_token and skip_jwt is true' do
341
+ subject.options[:skip_jwt] = true
342
+ expect(subject.extra).not_to have_key(:id_info)
343
+ end
344
+
345
+ it 'should include id_info when id_token is set on the access_token by default' do
346
+ expect(subject.extra).to include(id_info: token_info)
347
+ end
348
+ end
349
+ end
350
+
351
+ it_behaves_like 'id_token issued by valid issuer', 'accounts.google.com'
352
+ it_behaves_like 'id_token issued by valid issuer', 'https://accounts.google.com'
353
+
354
+ context 'when the id_token is issued by an invalid issuer' do
355
+ let(:token_info) do
320
356
  {
321
357
  'abc' => 'xyz',
322
358
  'exp' => Time.now.to_i + 3600,
323
359
  'nbf' => Time.now.to_i - 60,
324
360
  'iat' => Time.now.to_i,
325
361
  'aud' => 'appid',
326
- 'iss' => 'accounts.google.com'
362
+ 'iss' => 'fake.google.com'
327
363
  }
328
- id_token = JWT.encode(token_info, 'secret')
329
- let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
330
-
331
- it 'should include id_token when set on the access_token' do
332
- expect(subject.extra).to include(id_token: id_token)
333
- end
334
-
335
- it 'should include id_info when id_token is set on the access_token and skip_jwt is false' do
336
- subject.options[:skip_jwt] = false
337
- expect(subject.extra).to include(id_info: token_info)
338
- end
339
-
340
- it 'should not include id_info when id_token is set on the access_token and skip_jwt is true' do
341
- subject.options[:skip_jwt] = true
342
- expect(subject.extra).not_to have_key(:id_info)
343
364
  end
365
+ let(:id_token) { JWT.encode(token_info, 'secret') }
366
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, 'id_token' => id_token) }
344
367
 
345
- it 'should include id_info when id_token is set on the access_token by default' do
346
- expect(subject.extra).to include(id_info: token_info)
368
+ it 'raises JWT::InvalidIssuerError' do
369
+ expect { subject.extra }.to raise_error(JWT::InvalidIssuerError)
347
370
  end
348
371
  end
349
372
 
@@ -375,66 +398,6 @@ describe OmniAuth::Strategies::GoogleOauth2 do
375
398
  end
376
399
  end
377
400
  end
378
-
379
- describe 'raw_friend_info' do
380
- context 'when skip_info is true' do
381
- before { subject.options[:skip_info] = true }
382
-
383
- it 'should not include raw_friend_info' do
384
- expect(subject.extra).not_to have_key(:raw_friend_info)
385
- end
386
- end
387
-
388
- context 'when skip_info is false' do
389
- before { subject.options[:skip_info] = false }
390
-
391
- context 'when skip_friends is true' do
392
- before { subject.options[:skip_friends] = true }
393
-
394
- it 'should not include raw_friend_info' do
395
- expect(subject.extra).not_to have_key(:raw_friend_info)
396
- end
397
- end
398
-
399
- context 'when skip_friends is false' do
400
- before { subject.options[:skip_friends] = false }
401
-
402
- it 'should not include raw_friend_info' do
403
- expect(subject.extra[:raw_friend_info]).to eq([{ 'foo' => 'bar' }])
404
- end
405
- end
406
- end
407
- end
408
-
409
- describe 'raw_image_info' do
410
- context 'when skip_info is true' do
411
- before { subject.options[:skip_info] = true }
412
-
413
- it 'should not include raw_image_info' do
414
- expect(subject.extra).not_to have_key(:raw_image_info)
415
- end
416
- end
417
-
418
- context 'when skip_info is false' do
419
- before { subject.options[:skip_info] = false }
420
-
421
- context 'when skip_image_info is true' do
422
- before { subject.options[:skip_image_info] = true }
423
-
424
- it 'should not include raw_image_info' do
425
- expect(subject.extra).not_to have_key(:raw_image_info)
426
- end
427
- end
428
-
429
- context 'when skip_image_info is false' do
430
- before { subject.options[:skip_image_info] = false }
431
-
432
- it 'should include raw_image_info' do
433
- expect(subject.extra[:raw_image_info]).to eq('image' => 'imageData')
434
- end
435
- end
436
- end
437
- end
438
401
  end
439
402
 
440
403
  describe 'populate auth hash urls' do
@@ -599,37 +562,6 @@ describe OmniAuth::Strategies::GoogleOauth2 do
599
562
  end
600
563
  end
601
564
 
602
- describe 'verify_iss option' do
603
- before(:each) do
604
- subject.options.client_options[:connection_build] = proc do |builder|
605
- builder.request :url_encoded
606
- builder.adapter :test do |stub|
607
- stub.get('/oauth2/v3/tokeninfo?access_token=invalid_iss_token') do
608
- [200, { 'Content-Type' => 'application/json; charset=UTF-8' },
609
- JSON.dump(
610
- aud: '000000000000.apps.googleusercontent.com',
611
- sub: '123456789',
612
- email_verified: 'true',
613
- email: 'example@example.com',
614
- access_type: 'offline',
615
- scope: 'profile email',
616
- expires_in: 436,
617
- iss: 'foobar.com'
618
- )]
619
- end
620
- end
621
- end
622
- subject.options.authorized_client_ids = ['000000000000.apps.googleusercontent.com']
623
- subject.options.client_id = '000000000000.apps.googleusercontent.com'
624
- subject.options[:verify_iss] = false
625
- end
626
-
627
- it 'should verify token if the iss does not match options.expected_iss' do
628
- result = subject.send(:verify_token, 'invalid_iss_token')
629
- expect(result).to eq(true)
630
- end
631
- end
632
-
633
565
  describe 'verify_token' do
634
566
  before(:each) do
635
567
  subject.options.client_options[:connection_build] = proc do |builder|
@@ -679,7 +611,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
679
611
  OAuth2::Client.new('abc', 'def') do |builder|
680
612
  builder.request :url_encoded
681
613
  builder.adapter :test do |stub|
682
- stub.get('/plus/v1/people/me/openIdConnect') do
614
+ stub.get('/oauth2/v3/userinfo') do
683
615
  [200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump(
684
616
  hd: 'example.com'
685
617
  )]
@@ -694,7 +626,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
694
626
  OAuth2::Client.new('abc', 'def') do |builder|
695
627
  builder.request :url_encoded
696
628
  builder.adapter :test do |stub|
697
- stub.get('/plus/v1/people/me/openIdConnect') do
629
+ stub.get('/oauth2/v3/userinfo') do
698
630
  [200, { 'Content-Type' => 'application/json; charset=UTF-8' }, JSON.dump({})]
699
631
  end
700
632
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-google-oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Ellithorpe
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-12-07 00:00:00.000000000 Z
12
+ date: 2018-12-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: jwt
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '1.5'
20
+ version: '2.0'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '1.5'
27
+ version: '2.0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: omniauth
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
141
  version: '0'
142
142
  requirements: []
143
143
  rubyforge_project:
144
- rubygems_version: 2.6.13
144
+ rubygems_version: 2.6.11
145
145
  signing_key:
146
146
  specification_version: 4
147
147
  summary: A Google OAuth2 strategy for OmniAuth 1.x