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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8220475142931f00b726a976c242b8a1d5ff19f7f58d423697df36708be7eff4
4
- data.tar.gz: d3a3d9717a687e19cd013c281b4215325f9843317df071601af8dcb087668cf6
3
+ metadata.gz: b0cddb7c5d88d861d18686ee5601d635e94f133d51863fbfe50f9aadf46c8664
4
+ data.tar.gz: 19728334ae4996d63a0547f3ec9637ee20234fc8083a95355b734b5358809c64
5
5
  SHA512:
6
- metadata.gz: 01eb71b28da44db2653c853e06c927822458f085624e1b9aa82f039289bdc390297f1fbe379b1f0dd13c100c9fd076a37de133ecfae9177a67690cf0ad2b594b
7
- data.tar.gz: 30bff27ae11a2b9e4e7f5781814b878233ea8db8f0965fd51bb08d2de1b6413f39f97292d018a97ae7b1aa70c351ad3512287aaa338bd2f9d39c3e8c74560b92
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 = "/" if path.blank?
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
- if request.params["state"] != request.session[config.state_session_key_name]
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
@@ -1,3 +1,3 @@
1
1
  module SimpleGoogleAuth
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  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
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_google_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Nesbitt