google_sign_in 1.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9f61243974b069b039924957b8a03b39daad94f8e76109f88cfbbb60769b321
4
- data.tar.gz: 782d1b3520a08d7ea1ee9b0d4bd1179d94679d02ab9187d2bc82aeaa8c28a481
3
+ metadata.gz: 07ffe2df555e62583212eeed44495dad288e37609a46c4eb1f4717aedb11f4c9
4
+ data.tar.gz: b4eff8b60ac9cfdb32c835bf0780722f87fb34cfd4b0673a57bdfe4708841569
5
5
  SHA512:
6
- metadata.gz: 7d53ffc9c30b06696d4f6e726570a9262e711cb1d5b2700d2deee935cc2009d68b2addd8262a6cd105380f1ba210a42b6e070cd8b6dfe45b6cb80584e70c4051
7
- data.tar.gz: b01f4f9a120d08c61f00169622162461435c26ba154c73d20f232815411b2636d582a3de3c6bca3209a0fe69bea79ab31aa164b75768958759b87b3a6ed81870
6
+ metadata.gz: 5fd9e9b40717cf051753ec5bf737281d33f0e3ba6914fbd364e9c8c1d316c9a21a8d0cdead93df94e1a674aa6d6774e3ec9608b6427e56e70021d6da199a7f79
7
+ data.tar.gz: 10c98f8dbb827cfeca28f59b9857d86bb5e6f3f0122e4f1b5dc64c4b29b663b2d01520893d41a18af94bf6cc6cc6e5d83e476be5554f27b08450f382e3f7a16e
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  .byebug_history
3
3
  log/*.log
4
4
  pkg/
5
+ *.gem
5
6
 
6
7
  test/dummy/db/*.sqlite3
7
8
  test/dummy/db/*.sqlite3-journal
data/.travis.yml CHANGED
@@ -2,14 +2,12 @@ language: ruby
2
2
  sudo: false
3
3
  cache: bundler
4
4
 
5
- # Bundler/RubyGems incompat on Ruby 2.5.0
6
- before_install: gem install bundler
5
+ # Bundler/RubyGems incompat on Ruby 2.5.0 and 2.6.1
6
+ before_install: gem update --system && gem install bundler -v 1.17.3
7
7
 
8
8
  rvm:
9
- - 2.2
10
- - 2.3
11
- - 2.4
12
9
  - 2.5
10
+ - 2.6
13
11
  - ruby-head
14
12
 
15
13
  matrix:
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # Changelog
2
+
3
+ Please see [our GitHub "Releases" page](https://github.com/basecamp/google_sign_in/releases).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- google_sign_in (1.1.0)
4
+ google_sign_in (1.2.0)
5
5
  google-id-token (>= 1.4.0)
6
6
  oauth2 (>= 1.4.0)
7
7
  rails (>= 5.2.0)
@@ -9,121 +9,140 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actioncable (5.2.1)
13
- actionpack (= 5.2.1)
12
+ actioncable (6.0.0)
13
+ actionpack (= 6.0.0)
14
14
  nio4r (~> 2.0)
15
15
  websocket-driver (>= 0.6.1)
16
- actionmailer (5.2.1)
17
- actionpack (= 5.2.1)
18
- actionview (= 5.2.1)
19
- activejob (= 5.2.1)
16
+ actionmailbox (6.0.0)
17
+ actionpack (= 6.0.0)
18
+ activejob (= 6.0.0)
19
+ activerecord (= 6.0.0)
20
+ activestorage (= 6.0.0)
21
+ activesupport (= 6.0.0)
22
+ mail (>= 2.7.1)
23
+ actionmailer (6.0.0)
24
+ actionpack (= 6.0.0)
25
+ actionview (= 6.0.0)
26
+ activejob (= 6.0.0)
20
27
  mail (~> 2.5, >= 2.5.4)
21
28
  rails-dom-testing (~> 2.0)
22
- actionpack (5.2.1)
23
- actionview (= 5.2.1)
24
- activesupport (= 5.2.1)
29
+ actionpack (6.0.0)
30
+ actionview (= 6.0.0)
31
+ activesupport (= 6.0.0)
25
32
  rack (~> 2.0)
26
33
  rack-test (>= 0.6.3)
27
34
  rails-dom-testing (~> 2.0)
28
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
29
- actionview (5.2.1)
30
- activesupport (= 5.2.1)
35
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
36
+ actiontext (6.0.0)
37
+ actionpack (= 6.0.0)
38
+ activerecord (= 6.0.0)
39
+ activestorage (= 6.0.0)
40
+ activesupport (= 6.0.0)
41
+ nokogiri (>= 1.8.5)
42
+ actionview (6.0.0)
43
+ activesupport (= 6.0.0)
31
44
  builder (~> 3.1)
32
45
  erubi (~> 1.4)
33
46
  rails-dom-testing (~> 2.0)
34
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
35
- activejob (5.2.1)
36
- activesupport (= 5.2.1)
47
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
48
+ activejob (6.0.0)
49
+ activesupport (= 6.0.0)
37
50
  globalid (>= 0.3.6)
38
- activemodel (5.2.1)
39
- activesupport (= 5.2.1)
40
- activerecord (5.2.1)
41
- activemodel (= 5.2.1)
42
- activesupport (= 5.2.1)
43
- arel (>= 9.0)
44
- activestorage (5.2.1)
45
- actionpack (= 5.2.1)
46
- activerecord (= 5.2.1)
51
+ activemodel (6.0.0)
52
+ activesupport (= 6.0.0)
53
+ activerecord (6.0.0)
54
+ activemodel (= 6.0.0)
55
+ activesupport (= 6.0.0)
56
+ activestorage (6.0.0)
57
+ actionpack (= 6.0.0)
58
+ activejob (= 6.0.0)
59
+ activerecord (= 6.0.0)
47
60
  marcel (~> 0.3.1)
48
- activesupport (5.2.1)
61
+ activesupport (6.0.0)
49
62
  concurrent-ruby (~> 1.0, >= 1.0.2)
50
63
  i18n (>= 0.7, < 2)
51
64
  minitest (~> 5.1)
52
65
  tzinfo (~> 1.1)
53
- addressable (2.5.2)
54
- public_suffix (>= 2.0.2, < 4.0)
55
- arel (9.0.0)
66
+ zeitwerk (~> 2.1, >= 2.1.8)
67
+ addressable (2.8.0)
68
+ public_suffix (>= 2.0.2, < 5.0)
56
69
  builder (3.2.3)
57
- byebug (9.1.0)
58
- concurrent-ruby (1.0.5)
70
+ byebug (11.0.1)
71
+ concurrent-ruby (1.1.5)
59
72
  crack (0.4.3)
60
73
  safe_yaml (~> 1.0.0)
61
74
  crass (1.0.4)
62
- erubi (1.7.1)
63
- faraday (0.12.2)
75
+ erubi (1.9.0)
76
+ faraday (0.16.2)
64
77
  multipart-post (>= 1.2, < 3)
65
- globalid (0.4.1)
78
+ globalid (0.4.2)
66
79
  activesupport (>= 4.2.0)
67
80
  google-id-token (1.4.2)
68
81
  jwt (>= 1)
69
- hashdiff (0.3.7)
70
- i18n (1.1.0)
82
+ hashdiff (1.0.0)
83
+ i18n (1.6.0)
71
84
  concurrent-ruby (~> 1.0)
72
- jwt (1.5.6)
73
- loofah (2.2.2)
85
+ jwt (2.2.1)
86
+ loofah (2.3.0)
74
87
  crass (~> 1.0.2)
75
88
  nokogiri (>= 1.5.9)
76
- mail (2.7.0)
89
+ mail (2.7.1)
77
90
  mini_mime (>= 0.1.1)
78
- marcel (0.3.2)
91
+ marcel (0.3.3)
79
92
  mimemagic (~> 0.3.2)
80
- method_source (0.9.0)
81
- mimemagic (0.3.2)
82
- mini_mime (1.0.1)
83
- mini_portile2 (2.3.0)
84
- minitest (5.11.3)
93
+ method_source (0.9.2)
94
+ mimemagic (0.3.10)
95
+ nokogiri (~> 1)
96
+ rake
97
+ mini_mime (1.0.2)
98
+ mini_portile2 (2.6.1)
99
+ minitest (5.14.4)
85
100
  multi_json (1.13.1)
86
101
  multi_xml (0.6.0)
87
- multipart-post (2.0.0)
88
- nio4r (2.3.1)
89
- nokogiri (1.8.4)
90
- mini_portile2 (~> 2.3.0)
91
- oauth2 (1.4.0)
92
- faraday (>= 0.8, < 0.13)
93
- jwt (~> 1.0)
102
+ multipart-post (2.1.1)
103
+ nio4r (2.5.2)
104
+ nokogiri (1.12.5)
105
+ mini_portile2 (~> 2.6.1)
106
+ racc (~> 1.4)
107
+ oauth2 (1.4.2)
108
+ faraday (>= 0.8, < 2.0)
109
+ jwt (>= 1.0, < 3.0)
94
110
  multi_json (~> 1.3)
95
111
  multi_xml (~> 0.5)
96
112
  rack (>= 1.2, < 3)
97
- public_suffix (3.0.3)
98
- rack (2.0.5)
113
+ public_suffix (4.0.1)
114
+ racc (1.5.2)
115
+ rack (2.0.7)
99
116
  rack-test (1.1.0)
100
117
  rack (>= 1.0, < 3)
101
- rails (5.2.1)
102
- actioncable (= 5.2.1)
103
- actionmailer (= 5.2.1)
104
- actionpack (= 5.2.1)
105
- actionview (= 5.2.1)
106
- activejob (= 5.2.1)
107
- activemodel (= 5.2.1)
108
- activerecord (= 5.2.1)
109
- activestorage (= 5.2.1)
110
- activesupport (= 5.2.1)
118
+ rails (6.0.0)
119
+ actioncable (= 6.0.0)
120
+ actionmailbox (= 6.0.0)
121
+ actionmailer (= 6.0.0)
122
+ actionpack (= 6.0.0)
123
+ actiontext (= 6.0.0)
124
+ actionview (= 6.0.0)
125
+ activejob (= 6.0.0)
126
+ activemodel (= 6.0.0)
127
+ activerecord (= 6.0.0)
128
+ activestorage (= 6.0.0)
129
+ activesupport (= 6.0.0)
111
130
  bundler (>= 1.3.0)
112
- railties (= 5.2.1)
131
+ railties (= 6.0.0)
113
132
  sprockets-rails (>= 2.0.0)
114
133
  rails-dom-testing (2.0.3)
115
134
  activesupport (>= 4.2.0)
116
135
  nokogiri (>= 1.6)
117
- rails-html-sanitizer (1.0.4)
136
+ rails-html-sanitizer (1.2.0)
118
137
  loofah (~> 2.2, >= 2.2.2)
119
- railties (5.2.1)
120
- actionpack (= 5.2.1)
121
- activesupport (= 5.2.1)
138
+ railties (6.0.0)
139
+ actionpack (= 6.0.0)
140
+ activesupport (= 6.0.0)
122
141
  method_source
123
142
  rake (>= 0.8.7)
124
- thor (>= 0.19.0, < 2.0)
125
- rake (12.0.0)
126
- safe_yaml (1.0.4)
143
+ thor (>= 0.20.3, < 2.0)
144
+ rake (13.0.0)
145
+ safe_yaml (1.0.5)
127
146
  sprockets (3.7.2)
128
147
  concurrent-ruby (~> 1.0)
129
148
  rack (> 1, < 3)
@@ -131,17 +150,18 @@ GEM
131
150
  actionpack (>= 4.0)
132
151
  activesupport (>= 4.0)
133
152
  sprockets (>= 3.0.0)
134
- thor (0.20.0)
153
+ thor (0.20.3)
135
154
  thread_safe (0.3.6)
136
155
  tzinfo (1.2.5)
137
156
  thread_safe (~> 0.1)
138
- webmock (3.4.2)
157
+ webmock (3.7.6)
139
158
  addressable (>= 2.3.6)
140
159
  crack (>= 0.3.2)
141
- hashdiff
142
- websocket-driver (0.7.0)
160
+ hashdiff (>= 0.4.0, < 2.0.0)
161
+ websocket-driver (0.7.1)
143
162
  websocket-extensions (>= 0.1.0)
144
- websocket-extensions (0.1.3)
163
+ websocket-extensions (0.1.4)
164
+ zeitwerk (2.1.10)
145
165
 
146
166
  PLATFORMS
147
167
  ruby
@@ -150,9 +170,9 @@ DEPENDENCIES
150
170
  bundler (~> 1.15)
151
171
  byebug
152
172
  google_sign_in!
153
- jwt
173
+ jwt (>= 1.5.6)
154
174
  rake
155
- webmock
175
+ webmock (>= 3.4.2)
156
176
 
157
177
  BUNDLED WITH
158
- 1.16.4
178
+ 1.17.3
data/README.md CHANGED
@@ -64,6 +64,16 @@ end
64
64
 
65
65
  **⚠️ Important:** Take care to protect your client secret from disclosure to third parties.
66
66
 
67
+ 9. (Optional) The callback route can be configured using:
68
+
69
+ ```ruby
70
+ # config/initializers/google_sign_in.rb
71
+ Rails.application.configure do
72
+ config.google_sign_in.root = "my_own/google_sign_in_route"
73
+ end
74
+ ```
75
+
76
+ Which would make the callback `/my_own/google_sign_in_route/callback`.
67
77
 
68
78
  ## Usage
69
79
 
@@ -80,7 +90,8 @@ This gem provides a `google_sign_in_button` helper. It generates a button which
80
90
  ```
81
91
 
82
92
  The `proceed_to` argument is required. After authenticating with Google, the gem redirects to `proceed_to`, providing
83
- a Google ID token in `flash[:google_sign_in_token]`. Your application decides what to do with it:
93
+ a Google ID token in `flash[:google_sign_in][:id_token]` or an [OAuth authorizaton code grant error](https://tools.ietf.org/html/rfc6749#section-4.1.2.1)
94
+ in `flash[:google_sign_in][:error]`. Your application decides what to do with it:
84
95
 
85
96
  ```ruby
86
97
  # config/routes.rb
@@ -108,8 +119,11 @@ class LoginsController < ApplicationController
108
119
 
109
120
  private
110
121
  def authenticate_with_google
111
- if flash[:google_sign_in_token].present?
112
- User.find_by google_id: GoogleSignIn::Identity.new(flash[:google_sign_in_token]).user_id
122
+ if id_token = flash[:google_sign_in][:id_token]
123
+ User.find_by google_id: GoogleSignIn::Identity.new(id_token).user_id
124
+ elsif error = flash[:google_sign_in][:error]
125
+ logger.error "Google authentication error: #{error}"
126
+ nil
113
127
  end
114
128
  end
115
129
  end
@@ -143,11 +157,18 @@ information contained in the token via the following instance methods:
143
157
 
144
158
  * `hosted_domain`: The user’s hosted G Suite domain, provided only if they belong to a G Suite.
145
159
 
160
+ * `given_name`: The user's given name.
161
+
162
+ * `family_name`: The user's last name.
163
+
146
164
 
147
165
  ## Security
148
166
 
149
167
  For information on our security response procedure, see [SECURITY.md](SECURITY.md).
150
168
 
169
+ ## Maintenance
170
+
171
+ Short of patching critical security issues, this gem is now considered done, and will not see any further feature development or minor bug fixes. Feel free to fork this work under the MIT license and continue the feature development under a different name.
151
172
 
152
173
  ## License
153
174
 
@@ -1,9 +1,11 @@
1
1
  require 'securerandom'
2
2
 
3
3
  class GoogleSignIn::AuthorizationsController < GoogleSignIn::BaseController
4
+ skip_forgery_protection only: :create
5
+
4
6
  def create
5
7
  redirect_to login_url(scope: 'openid profile email', state: state),
6
- flash: { proceed_to: params.require(:proceed_to), state: state }
8
+ allow_other_host: true, flash: { proceed_to: params.require(:proceed_to), state: state }
7
9
  end
8
10
 
9
11
  private
@@ -9,7 +9,7 @@ class GoogleSignIn::BaseController < ActionController::Base
9
9
  GoogleSignIn.client_id,
10
10
  GoogleSignIn.client_secret,
11
11
  authorize_url: 'https://accounts.google.com/o/oauth2/auth',
12
- token_url: 'https://www.googleapis.com/oauth2/v3/token',
12
+ token_url: 'https://oauth2.googleapis.com/token',
13
13
  redirect_uri: callback_url
14
14
  end
15
15
  end
@@ -1,27 +1,37 @@
1
- require_dependency 'google_sign_in/redirect_protector'
1
+ require 'google_sign_in/redirect_protector'
2
2
 
3
3
  class GoogleSignIn::CallbacksController < GoogleSignIn::BaseController
4
4
  def show
5
- if valid_request?
6
- redirect_to proceed_to_url, flash: { google_sign_in_token: id_token }
7
- else
8
- head :unprocessable_entity
9
- end
5
+ redirect_to proceed_to_url, flash: { google_sign_in: google_sign_in_response }
10
6
  rescue GoogleSignIn::RedirectProtector::Violation => error
11
7
  logger.error error.message
12
8
  head :bad_request
13
9
  end
14
10
 
15
11
  private
16
- def valid_request?
17
- flash[:state].present? && params.require(:state) == flash[:state]
18
- end
19
-
20
12
  def proceed_to_url
21
13
  flash[:proceed_to].tap { |url| GoogleSignIn::RedirectProtector.ensure_same_origin(url, request.url) }
22
14
  end
23
15
 
16
+ def google_sign_in_response
17
+ if valid_request? && params[:code].present?
18
+ { id_token: id_token }
19
+ else
20
+ { error: error_message_for(params[:error]) }
21
+ end
22
+ rescue OAuth2::Error => error
23
+ { error: error_message_for(error.code) }
24
+ end
25
+
26
+ def valid_request?
27
+ flash[:state].present? && params[:state] == flash[:state]
28
+ end
29
+
24
30
  def id_token
25
- client.auth_code.get_token(params.require(:code))['id_token']
31
+ client.auth_code.get_token(params[:code])['id_token']
32
+ end
33
+
34
+ def error_message_for(error_code)
35
+ error_code.presence_in(GoogleSignIn::OAUTH2_ERRORS) || "invalid_request"
26
36
  end
27
37
  end
data/bin/rails CHANGED
@@ -3,7 +3,7 @@
3
3
  # installed from the root of your application.
4
4
 
5
5
  ENGINE_ROOT = File.expand_path('..', __dir__)
6
- ENGINE_PATH = File.expand_path('../lib/blorgh/engine', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/google_sign_in/engine', __dir__)
7
7
  APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
8
8
 
9
9
  # Set up gems listed in the Gemfile.
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'google_sign_in'
3
- s.version = '1.1.0'
3
+ s.version = '1.2.1'
4
4
  s.authors = ['David Heinemeier Hansson', 'George Claghorn']
5
5
  s.email = ['david@basecamp.com', 'george@basecamp.com']
6
6
  s.summary = 'Sign in (or up) with Google for Rails applications'
@@ -14,8 +14,8 @@ Gem::Specification.new do |s|
14
14
  s.add_dependency 'oauth2', '>= 1.4.0'
15
15
 
16
16
  s.add_development_dependency 'bundler', '~> 1.15'
17
- s.add_development_dependency 'jwt'
18
- s.add_development_dependency 'webmock'
17
+ s.add_development_dependency 'jwt', '>= 1.5.6'
18
+ s.add_development_dependency 'webmock', '>= 3.4.2'
19
19
 
20
20
  s.files = `git ls-files`.split("\n")
21
21
  s.test_files = `git ls-files -- test/*`.split("\n")
@@ -13,14 +13,12 @@ module GoogleSignIn
13
13
  end
14
14
  end
15
15
 
16
- initializer 'google_sign_in.helpers' do
17
- ActiveSupport.on_load :action_controller_base do
18
- helper GoogleSignIn::Engine.helpers
19
- end
16
+ config.to_prepare do
17
+ ActionController::Base.helper GoogleSignIn::Engine.helpers
20
18
  end
21
19
 
22
20
  initializer 'google_sign_in.mount' do |app|
23
- app.routes.append do
21
+ app.routes.prepend do
24
22
  mount GoogleSignIn::Engine, at: app.config.google_sign_in.root || 'google_sign_in'
25
23
  end
26
24
  end
@@ -40,6 +40,14 @@ module GoogleSignIn
40
40
  @payload["hd"]
41
41
  end
42
42
 
43
+ def given_name
44
+ @payload["given_name"]
45
+ end
46
+
47
+ def family_name
48
+ @payload["family_name"]
49
+ end
50
+
43
51
  private
44
52
  delegate :client_id, to: GoogleSignIn
45
53
 
@@ -9,8 +9,8 @@ module GoogleSignIn
9
9
  QUALIFIED_URL_PATTERN = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
10
10
 
11
11
  def ensure_same_origin(target, source)
12
- if target =~ QUALIFIED_URL_PATTERN && origin_of(target) != origin_of(source)
13
- raise Violation, "Redirect target #{target} does not have same origin as request (expected #{origin_of(source)})"
12
+ if target.blank? || (target =~ QUALIFIED_URL_PATTERN && origin_of(target) != origin_of(source))
13
+ raise Violation, "Redirect target #{target.inspect} does not have same origin as request (expected #{origin_of(source)})"
14
14
  end
15
15
  end
16
16
 
@@ -4,6 +4,31 @@ require 'active_support/rails'
4
4
  module GoogleSignIn
5
5
  mattr_accessor :client_id
6
6
  mattr_accessor :client_secret
7
+
8
+ # https://tools.ietf.org/html/rfc6749#section-4.1.2.1
9
+ authorization_request_errors = %w[
10
+ invalid_request
11
+ unauthorized_client
12
+ access_denied
13
+ unsupported_response_type
14
+ invalid_scope
15
+ server_error
16
+ temporarily_unavailable
17
+ ]
18
+
19
+ # https://tools.ietf.org/html/rfc6749#section-5.2
20
+ access_token_request_errors = %w[
21
+ invalid_request
22
+ invalid_client
23
+ invalid_grant
24
+ unauthorized_client
25
+ unsupported_grant_type
26
+ invalid_scope
27
+ ]
28
+
29
+ # Authorization Code Grant errors from both authorization requests
30
+ # and access token requests.
31
+ OAUTH2_ERRORS = authorization_request_errors | access_token_request_errors
7
32
  end
8
33
 
9
34
  require 'google_sign_in/identity'
@@ -5,16 +5,92 @@ class GoogleSignIn::CallbacksControllerTest < ActionDispatch::IntegrationTest
5
5
  post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
6
6
  assert_response :redirect
7
7
 
8
- stub_token_request code: '4/SgCpHSVW5-Cy', access_token: 'ya29.GlwIBo', id_token: 'eyJhbGciOiJSUzI'
8
+ stub_token_for '4/SgCpHSVW5-Cy', access_token: 'ya29.GlwIBo', id_token: 'eyJhbGciOiJSUzI'
9
9
 
10
10
  get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: flash[:state])
11
11
  assert_redirected_to 'http://www.example.com/login'
12
- assert_equal 'eyJhbGciOiJSUzI', flash[:google_sign_in_token]
12
+ assert_equal 'eyJhbGciOiJSUzI', flash[:google_sign_in][:id_token]
13
+ assert_nil flash[:google_sign_in][:error]
13
14
  end
14
15
 
15
- test "protecting against CSRF" do
16
+ # Authorization request errors: https://tools.ietf.org/html/rfc6749#section-4.1.2.1
17
+ %w[ invalid_request unauthorized_client access_denied unsupported_response_type invalid_scope server_error temporarily_unavailable ].each do |error|
18
+ test "receiving an authorization code grant error: #{error}" do
19
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
20
+ assert_response :redirect
21
+
22
+ get google_sign_in.callback_url(error: error, state: flash[:state])
23
+ assert_redirected_to 'http://www.example.com/login'
24
+ assert_nil flash[:google_sign_in][:id_token]
25
+ assert_equal error, flash[:google_sign_in][:error]
26
+ end
27
+ end
28
+
29
+ test "receiving an invalid authorization error" do
30
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
31
+ assert_response :redirect
32
+
33
+ get google_sign_in.callback_url(error: 'unknown error code', state: flash[:state])
34
+ assert_redirected_to 'http://www.example.com/login'
35
+ assert_nil flash[:google_sign_in][:id_token]
36
+ assert_equal "invalid_request", flash[:google_sign_in][:error]
37
+ end
38
+
39
+ test "receiving neither code nor error" do
40
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
41
+ assert_response :redirect
42
+
43
+ get google_sign_in.callback_url(state: flash[:state])
44
+ assert_redirected_to 'http://www.example.com/login'
45
+ assert_nil flash[:google_sign_in][:id_token]
46
+ assert_equal 'invalid_request', flash[:google_sign_in][:error]
47
+ end
48
+
49
+ # Access token request errors: https://tools.ietf.org/html/rfc6749#section-5.2
50
+ %w[ invalid_request invalid_client invalid_grant unauthorized_client unsupported_grant_type invalid_scope ].each do |error|
51
+ test "receiving an access token request error: #{error}" do
52
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
53
+ assert_response :redirect
54
+
55
+ stub_token_error_for '4/SgCpHSVW5-Cy', error: error
56
+
57
+ get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: flash[:state])
58
+ assert_redirected_to 'http://www.example.com/login'
59
+ assert_nil flash[:google_sign_in][:id_token]
60
+ assert_equal error, flash[:google_sign_in][:error]
61
+ end
62
+ end
63
+
64
+ test "protecting against CSRF without flash state" do
65
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
66
+ assert_response :redirect
67
+
68
+ get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: 'invalid')
69
+ assert_redirected_to 'http://www.example.com/login'
70
+ assert_nil flash[:google_sign_in][:id_token]
71
+ assert_equal 'invalid_request', flash[:google_sign_in][:error]
72
+ end
73
+
74
+ test "protecting against CSRF with invalid state" do
75
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
76
+ assert_response :redirect
77
+ assert_not_nil flash[:state]
78
+
16
79
  get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: 'invalid')
17
- assert_response :unprocessable_entity
80
+ assert_redirected_to 'http://www.example.com/login'
81
+ assert_nil flash[:google_sign_in][:id_token]
82
+ assert_equal 'invalid_request', flash[:google_sign_in][:error]
83
+ end
84
+
85
+ test "protecting against CSRF with missing state" do
86
+ post google_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
87
+ assert_response :redirect
88
+ assert_not_nil flash[:state]
89
+
90
+ get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy')
91
+ assert_redirected_to 'http://www.example.com/login'
92
+ assert_nil flash[:google_sign_in][:id_token]
93
+ assert_equal 'invalid_request', flash[:google_sign_in][:error]
18
94
  end
19
95
 
20
96
  test "protecting against open redirects" do
@@ -25,12 +101,33 @@ class GoogleSignIn::CallbacksControllerTest < ActionDispatch::IntegrationTest
25
101
  assert_response :bad_request
26
102
  end
27
103
 
104
+ test "receiving no proceed_to URL" do
105
+ get google_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: 'invalid')
106
+ assert_response :bad_request
107
+ end
108
+
28
109
  private
29
- def stub_token_request(code:, **params)
30
- stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token').
31
- with(body: { grant_type: 'authorization_code', code: code,
32
- client_id: FAKE_GOOGLE_CLIENT_ID, client_secret: FAKE_GOOGLE_CLIENT_SECRET,
33
- redirect_uri: 'http://www.example.com/google_sign_in/callback' }).
34
- to_return(status: 200, headers: { 'Content-Type' => 'application/json' }, body: JSON.generate(params))
110
+ def stub_token_for(code, **response_body)
111
+ stub_token_request(code, status: 200, response: response_body)
112
+ end
113
+
114
+ def stub_token_error_for(code, error:)
115
+ stub_token_request(code, status: 418, response: { error: error })
116
+ end
117
+
118
+ def stub_token_request(code, status:, response:)
119
+ stub_request(:post, 'https://oauth2.googleapis.com/token').with(
120
+ body: {
121
+ grant_type: 'authorization_code',
122
+ code: code,
123
+ client_id: FAKE_GOOGLE_CLIENT_ID,
124
+ client_secret: FAKE_GOOGLE_CLIENT_SECRET,
125
+ redirect_uri: 'http://www.example.com/google_sign_in/callback'
126
+ }
127
+ ).to_return(
128
+ status: status,
129
+ headers: { 'Content-Type' => 'application/json' },
130
+ body: JSON.generate(response)
131
+ )
35
132
  end
36
133
  end
@@ -10,7 +10,7 @@ Bundler.require(*Rails.groups)
10
10
  module Dummy
11
11
  class Application < Rails::Application
12
12
  # Initialize configuration defaults for originally generated Rails version.
13
- config.load_defaults 5.2
13
+ config.load_defaults 6.0
14
14
 
15
15
  # Settings in config/environments/* take precedence over those specified here.
16
16
  # Application configuration can go into files in config/initializers
@@ -4,7 +4,6 @@ class GoogleSignIn::ButtonHelperTest < ActionView::TestCase
4
4
  test "generating a login button with text content" do
5
5
  assert_dom_equal <<-HTML, google_sign_in_button("Log in with Google", proceed_to: "https://www.example.com/login")
6
6
  <form action="/google_sign_in/authorization" accept-charset="UTF-8" method="post">
7
- <input name="utf8" type="hidden" value="&#x2713;" />
8
7
  <input name="proceed_to" type="hidden" value="https://www.example.com/login" />
9
8
  <button type="submit">Log in with Google</button>
10
9
  </form>
@@ -14,7 +13,6 @@ class GoogleSignIn::ButtonHelperTest < ActionView::TestCase
14
13
  test "generating a login button with HTML content" do
15
14
  assert_dom_equal <<-HTML, google_sign_in_button(proceed_to: "https://www.example.com/login") { image_tag("google.png") }
16
15
  <form action="/google_sign_in/authorization" accept-charset="UTF-8" method="post">
17
- <input name="utf8" type="hidden" value="&#x2713;" />
18
16
  <input name="proceed_to" type="hidden" value="https://www.example.com/login" />
19
17
  <button type="submit"><img src="/images/google.png"></button>
20
18
  </form>
@@ -27,7 +25,6 @@ class GoogleSignIn::ButtonHelperTest < ActionView::TestCase
27
25
 
28
26
  assert_dom_equal <<-HTML, button
29
27
  <form action="/google_sign_in/authorization" accept-charset="UTF-8" method="post">
30
- <input name="utf8" type="hidden" value="&#x2713;" />
31
28
  <input name="proceed_to" type="hidden" value="https://www.example.com/login" />
32
29
  <button type="submit" class="login-button" data-disable-with="Loading Google login…">Log in with Google</button>
33
30
  </form>
@@ -65,6 +65,14 @@ class GoogleSignIn::IdentityTest < ActiveSupport::TestCase
65
65
  assert_equal "basecamp.com", GoogleSignIn::Identity.new(token_with(hd: "basecamp.com")).hosted_domain
66
66
  end
67
67
 
68
+ test "extracting given name" do
69
+ assert_equal "George", GoogleSignIn::Identity.new(token_with(given_name: "George")).given_name
70
+ end
71
+
72
+ test "extracting family name" do
73
+ assert_equal "Claghorn", GoogleSignIn::Identity.new(token_with(family_name: "Claghorn")).family_name
74
+ end
75
+
68
76
  private
69
77
  def switch_client_id_to(value)
70
78
  previous_value = GoogleSignIn.client_id
@@ -20,6 +20,12 @@ class GoogleSignIn::RedirectProtectorTest < ActiveSupport::TestCase
20
20
  end
21
21
  end
22
22
 
23
+ test "disallows empty URL target" do
24
+ assert_raises GoogleSignIn::RedirectProtector::Violation do
25
+ GoogleSignIn::RedirectProtector.ensure_same_origin nil, 'https://basecamp.com'
26
+ end
27
+ end
28
+
23
29
  test "allows URL target with same origin as source" do
24
30
  assert_nothing_raised do
25
31
  GoogleSignIn::RedirectProtector.ensure_same_origin 'https://basecamp.com', 'https://basecamp.com'
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google_sign_in
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  - George Claghorn
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-09-14 00:00:00.000000000 Z
12
+ date: 2021-12-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -73,29 +73,29 @@ dependencies:
73
73
  requirements:
74
74
  - - ">="
75
75
  - !ruby/object:Gem::Version
76
- version: '0'
76
+ version: 1.5.6
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: '0'
83
+ version: 1.5.6
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: webmock
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="
89
89
  - !ruby/object:Gem::Version
90
- version: '0'
90
+ version: 3.4.2
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: '0'
98
- description:
97
+ version: 3.4.2
98
+ description:
99
99
  email:
100
100
  - david@basecamp.com
101
101
  - george@basecamp.com
@@ -105,6 +105,7 @@ extra_rdoc_files: []
105
105
  files:
106
106
  - ".gitignore"
107
107
  - ".travis.yml"
108
+ - CHANGELOG.md
108
109
  - Gemfile
109
110
  - Gemfile.lock
110
111
  - MIT-LICENSE
@@ -196,7 +197,7 @@ homepage: https://github.com/basecamp/google_sign_in
196
197
  licenses:
197
198
  - MIT
198
199
  metadata: {}
199
- post_install_message:
200
+ post_install_message:
200
201
  rdoc_options: []
201
202
  require_paths:
202
203
  - lib
@@ -211,9 +212,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
212
  - !ruby/object:Gem::Version
212
213
  version: '0'
213
214
  requirements: []
214
- rubyforge_project:
215
- rubygems_version: 2.7.3
216
- signing_key:
215
+ rubygems_version: 3.2.22
216
+ signing_key:
217
217
  specification_version: 4
218
218
  summary: Sign in (or up) with Google for Rails applications
219
219
  test_files: