simple_google_auth 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/simple_google_auth/receiver.rb +30 -2
- data/lib/simple_google_auth/version.rb +1 -1
- data/spec/simple_google_auth/receiver_spec.rb +21 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0cddb7c5d88d861d18686ee5601d635e94f133d51863fbfe50f9aadf46c8664
|
|
4
|
+
data.tar.gz: 19728334ae4996d63a0547f3ec9637ee20234fc8083a95355b734b5358809c64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 990eb2a0608217e84f01a2e5cfd5273773e758c64581930563cee30b38deae2f4becae0bf070f7edf2d3550d4b8e4ca10f793d7be144ef418f41ea8c915c21d6
|
|
7
|
+
data.tar.gz: 721625b234e2c0b937dca66e38bec0ce8ac5c087237d57a46831404112110f22f46e06934768c7bbb22837d829546a0868a06396d400fc37bda14049cf94e517
|
|
@@ -11,10 +11,11 @@ module SimpleGoogleAuth
|
|
|
11
11
|
data = AuthDataPresenter.new(auth_data)
|
|
12
12
|
raise Error, "Authentication failed" unless config.authenticate.call(data)
|
|
13
13
|
|
|
14
|
+
renew_session(request)
|
|
14
15
|
request.session[config.data_session_key_name] = auth_data
|
|
15
16
|
|
|
16
17
|
path = config.authentication_uri_state_path_extractor.call(request.session[config.state_session_key_name])
|
|
17
|
-
path = "/"
|
|
18
|
+
path = "/" unless safe_redirect_path?(path)
|
|
18
19
|
[302, {"Location" => path}, [" "]]
|
|
19
20
|
|
|
20
21
|
rescue Error => e
|
|
@@ -26,7 +27,9 @@ module SimpleGoogleAuth
|
|
|
26
27
|
|
|
27
28
|
protected
|
|
28
29
|
def ensure_params_are_correct(request, config)
|
|
29
|
-
|
|
30
|
+
expected_state = request.session[config.state_session_key_name]
|
|
31
|
+
|
|
32
|
+
if expected_state.blank? || !states_match?(request.params["state"], expected_state)
|
|
30
33
|
raise Error, "Invalid state returned from Google"
|
|
31
34
|
elsif request.params["error"]
|
|
32
35
|
raise Error, "Authentication failed: #{request.params["error"]}"
|
|
@@ -34,5 +37,30 @@ module SimpleGoogleAuth
|
|
|
34
37
|
raise Error, "No authentication code returned"
|
|
35
38
|
end
|
|
36
39
|
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Constant-time comparison so the CSRF state token isn't leaked via timing.
|
|
44
|
+
# A blank expected state is rejected by the caller before we get here.
|
|
45
|
+
def states_match?(actual, expected)
|
|
46
|
+
return false if actual.nil?
|
|
47
|
+
ActiveSupport::SecurityUtils.secure_compare(actual.to_s, expected.to_s)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Only redirect back to a path on our own host. A safe path starts with a
|
|
51
|
+
# single "/" that isn't followed by another "/" or "\" -- both of which a
|
|
52
|
+
# browser can resolve to a different origin ("//evil.com", "/\evil.com").
|
|
53
|
+
# Written as an allowlist so we don't have to chase every dangerous form.
|
|
54
|
+
def safe_redirect_path?(path)
|
|
55
|
+
path = path.to_s
|
|
56
|
+
path.start_with?("/") && !path.start_with?("//", "/\\")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Rotate the session on successful login to defend against session fixation.
|
|
60
|
+
# No-op on session stores that don't expose options (e.g. in unit tests).
|
|
61
|
+
def renew_session(request)
|
|
62
|
+
session = request.session
|
|
63
|
+
session.options[:renew] = true if session.respond_to?(:options) && session.options
|
|
64
|
+
end
|
|
37
65
|
end
|
|
38
66
|
end
|
|
@@ -44,6 +44,18 @@ describe SimpleGoogleAuth::Receiver do
|
|
|
44
44
|
|
|
45
45
|
expect(subject).to eq [302, {"Location" => "/place"}, [" "]]
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
it "does not follow a protocol-relative redirect path (open redirect)" do
|
|
49
|
+
expect(authentication_uri_state_path_extractor).to receive(:call).with(state).and_return('//evil.com')
|
|
50
|
+
|
|
51
|
+
expect(subject).to eq [302, {"Location" => "/"}, [" "]]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "does not follow a backslash-prefixed redirect path (open redirect)" do
|
|
55
|
+
expect(authentication_uri_state_path_extractor).to receive(:call).with(state).and_return('/\\evil.com')
|
|
56
|
+
|
|
57
|
+
expect(subject).to eq [302, {"Location" => "/"}, [" "]]
|
|
58
|
+
end
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
context "and the authenticator rejects the login" do
|
|
@@ -63,6 +75,15 @@ describe SimpleGoogleAuth::Receiver do
|
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
|
|
78
|
+
context "when the session holds no state and the callback omits it (forged callback)" do
|
|
79
|
+
let(:state) { nil }
|
|
80
|
+
let(:params) { {"code" => code} }
|
|
81
|
+
|
|
82
|
+
it "rejects the login rather than treating two blank states as a match" do
|
|
83
|
+
expect(subject).to eq [302, {"Location" => "/error?message=Invalid+state+returned+from+Google"}, [" "]]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
66
87
|
context "when the google authentication fails" do
|
|
67
88
|
let(:params) { {"state" => state, "error" => "bad stuff"} }
|
|
68
89
|
|