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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -5
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +100 -80
- data/README.md +24 -3
- data/app/controllers/google_sign_in/authorizations_controller.rb +3 -1
- data/app/controllers/google_sign_in/base_controller.rb +1 -1
- data/app/controllers/google_sign_in/callbacks_controller.rb +21 -11
- data/bin/rails +1 -1
- data/google_sign_in.gemspec +3 -3
- data/lib/google_sign_in/engine.rb +3 -5
- data/lib/google_sign_in/identity.rb +8 -0
- data/lib/google_sign_in/redirect_protector.rb +2 -2
- data/lib/google_sign_in.rb +25 -0
- data/test/controllers/callbacks_controller_test.rb +107 -10
- data/test/dummy/config/application.rb +1 -1
- data/test/helpers/button_helper_test.rb +0 -3
- data/test/models/identity_test.rb +8 -0
- data/test/models/redirect_protector_test.rb +6 -0
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07ffe2df555e62583212eeed44495dad288e37609a46c4eb1f4717aedb11f4c9
|
4
|
+
data.tar.gz: b4eff8b60ac9cfdb32c835bf0780722f87fb34cfd4b0673a57bdfe4708841569
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fd9e9b40717cf051753ec5bf737281d33f0e3ba6914fbd364e9c8c1d316c9a21a8d0cdead93df94e1a674aa6d6774e3ec9608b6427e56e70021d6da199a7f79
|
7
|
+
data.tar.gz: 10c98f8dbb827cfeca28f59b9857d86bb5e6f3f0122e4f1b5dc64c4b29b663b2d01520893d41a18af94bf6cc6cc6e5d83e476be5554f27b08450f382e3f7a16e
|
data/.gitignore
CHANGED
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
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
google_sign_in (1.
|
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 (
|
13
|
-
actionpack (=
|
12
|
+
actioncable (6.0.0)
|
13
|
+
actionpack (= 6.0.0)
|
14
14
|
nio4r (~> 2.0)
|
15
15
|
websocket-driver (>= 0.6.1)
|
16
|
-
|
17
|
-
actionpack (=
|
18
|
-
|
19
|
-
|
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 (
|
23
|
-
actionview (=
|
24
|
-
activesupport (=
|
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
|
29
|
-
|
30
|
-
|
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.
|
35
|
-
activejob (
|
36
|
-
activesupport (=
|
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 (
|
39
|
-
activesupport (=
|
40
|
-
activerecord (
|
41
|
-
activemodel (=
|
42
|
-
activesupport (=
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
activerecord (=
|
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 (
|
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
|
-
|
54
|
-
|
55
|
-
|
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 (
|
58
|
-
concurrent-ruby (1.
|
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.
|
63
|
-
faraday (0.
|
75
|
+
erubi (1.9.0)
|
76
|
+
faraday (0.16.2)
|
64
77
|
multipart-post (>= 1.2, < 3)
|
65
|
-
globalid (0.4.
|
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.
|
70
|
-
i18n (1.
|
82
|
+
hashdiff (1.0.0)
|
83
|
+
i18n (1.6.0)
|
71
84
|
concurrent-ruby (~> 1.0)
|
72
|
-
jwt (
|
73
|
-
loofah (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.
|
89
|
+
mail (2.7.1)
|
77
90
|
mini_mime (>= 0.1.1)
|
78
|
-
marcel (0.3.
|
91
|
+
marcel (0.3.3)
|
79
92
|
mimemagic (~> 0.3.2)
|
80
|
-
method_source (0.9.
|
81
|
-
mimemagic (0.3.
|
82
|
-
|
83
|
-
|
84
|
-
|
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.
|
88
|
-
nio4r (2.
|
89
|
-
nokogiri (1.
|
90
|
-
mini_portile2 (~> 2.
|
91
|
-
|
92
|
-
|
93
|
-
|
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 (
|
98
|
-
|
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 (
|
102
|
-
actioncable (=
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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 (=
|
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
|
136
|
+
rails-html-sanitizer (1.2.0)
|
118
137
|
loofah (~> 2.2, >= 2.2.2)
|
119
|
-
railties (
|
120
|
-
actionpack (=
|
121
|
-
activesupport (=
|
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.
|
125
|
-
rake (
|
126
|
-
safe_yaml (1.0.
|
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.
|
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.
|
157
|
+
webmock (3.7.6)
|
139
158
|
addressable (>= 2.3.6)
|
140
159
|
crack (>= 0.3.2)
|
141
|
-
hashdiff
|
142
|
-
websocket-driver (0.7.
|
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.
|
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.
|
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[:
|
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[:
|
112
|
-
User.find_by google_id: GoogleSignIn::Identity.new(
|
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://
|
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
|
-
|
1
|
+
require 'google_sign_in/redirect_protector'
|
2
2
|
|
3
3
|
class GoogleSignIn::CallbacksController < GoogleSignIn::BaseController
|
4
4
|
def show
|
5
|
-
|
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
|
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/
|
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.
|
data/google_sign_in.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'google_sign_in'
|
3
|
-
s.version = '1.1
|
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
|
-
|
17
|
-
|
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.
|
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
|
@@ -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
|
|
data/lib/google_sign_in.rb
CHANGED
@@ -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
|
-
|
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[:
|
12
|
+
assert_equal 'eyJhbGciOiJSUzI', flash[:google_sign_in][:id_token]
|
13
|
+
assert_nil flash[:google_sign_in][:error]
|
13
14
|
end
|
14
15
|
|
15
|
-
|
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
|
-
|
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
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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="✓" />
|
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="✓" />
|
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="✓" />
|
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
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
215
|
-
|
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:
|