gds-sso 22.3.0 → 23.0.0

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: '092e673d09d581f6032548857228e22f041e6c7cb51de00bb623bf3d907c5c2e'
4
- data.tar.gz: 0d5d69b1490123d751d6a17e04d215df17f3515d3fdcc257bfebbb72de3d90b2
3
+ metadata.gz: 620c8f21e86429c4a35f325a47451e554e359845c3e680bfa2c1add771b72bd3
4
+ data.tar.gz: 34cee6af9fdc9e5ff976c1f9388f0578d5de74e0d621f1a20fb9a077a0178049
5
5
  SHA512:
6
- metadata.gz: c8aa534e8e4640ca89c8a05e975714bb8af671796d45fd408fd53029c48d25265500bcb4cb1d485b9726406c0a87c45cc80049ae927185be965d0daff7c74d10
7
- data.tar.gz: c3db836c9f9546d00a8d993286a212a60273f20f178f51ff426e086aa6c16ecd7c8dc3431aa6085d8a4b33d583c985ab56c7186b7f3f95cf2afe98cf7d8bccfc
6
+ metadata.gz: 45dfe6138c1b0fa85adef3f89e874bb7dd6e28597c4c36092729012e05f9d1eb6ae9e336968c21aff61d689e51b87d5911b6ce002b8c4820160a1d71a6e5e371
7
+ data.tar.gz: dcf2803364d61e31bddb3d55d64c212fe389b4351c7143646353ee4bb3836c2deb54181b288a37ff60571b3dc93621064e4ed4474a17b39afdbd4a37ccd270e7
@@ -6,8 +6,6 @@ require "rails"
6
6
  module GDS
7
7
  module SSO
8
8
  class FailureApp < ActionController::Metal
9
- MAX_RETURN_TO_PATH_SIZE = 2048
10
-
11
9
  include ActionController::Redirecting
12
10
  include AbstractController::Rendering
13
11
  include ActionController::Rendering
@@ -43,7 +41,6 @@ module GDS
43
41
  return unless request.get?
44
42
 
45
43
  attempted_path = request.env["warden.options"][:attempted_path]
46
- return if attempted_path.bytesize > MAX_RETURN_TO_PATH_SIZE
47
44
 
48
45
  session["return_to"] = attempted_path
49
46
  end
@@ -0,0 +1,66 @@
1
+ module GDS
2
+ module SSO
3
+ class LimitSessionData
4
+ MAX_SESSION_DATA_SIZE = 2048
5
+ MANAGED_KEYS = %w[return_to].freeze
6
+ MANAGED_KEY_PREFIXES = %w[omniauth. warden.].freeze
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ status, headers, body = @app.call(env)
14
+
15
+ session = env["rack.session"]
16
+ return [status, headers, body] unless session.respond_to?(:to_hash)
17
+
18
+ if gds_sso_session_bytesize(session) > MAX_SESSION_DATA_SIZE
19
+ Rails.logger.warn("[gds-sso] Rejecting request: data added to the session by gds-sso exceeds #{MAX_SESSION_DATA_SIZE} bytes")
20
+ purge_gds_sso_session_data!(session)
21
+ return bad_request
22
+ end
23
+
24
+ [status, headers, body]
25
+ end
26
+
27
+ private
28
+
29
+ def gds_sso_session_bytesize(session)
30
+ session.to_hash.sum do |key, value|
31
+ managed_key?(key) ? key.bytesize + serialized_bytesize(value) : 0
32
+ end
33
+ end
34
+
35
+ def serialized_bytesize(value)
36
+ JSON.generate(value).bytesize
37
+ rescue StandardError
38
+ # If we can't serialise it to measure it, assume it's too large
39
+ Float::INFINITY
40
+ end
41
+
42
+ def managed_key?(key)
43
+ MANAGED_KEYS.include?(key) || MANAGED_KEY_PREFIXES.any? { |prefix| key.start_with?(prefix) }
44
+ end
45
+
46
+ def purge_gds_sso_session_data!(session)
47
+ session.to_hash.each_key do |key|
48
+ session.delete(key) if managed_key?(key)
49
+ end
50
+ end
51
+
52
+ def bad_request
53
+ body = "Bad Request"
54
+ [
55
+ 400,
56
+ {
57
+ "content-type" => "text/plain; charset=utf-8",
58
+ "content-length" => body.bytesize.to_s,
59
+ "cache-control" => "no-cache",
60
+ },
61
+ [body],
62
+ ]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,5 @@
1
1
  module GDS
2
2
  module SSO
3
- VERSION = "22.3.0".freeze
3
+ VERSION = "23.0.0".freeze
4
4
  end
5
5
  end
data/lib/gds-sso.rb CHANGED
@@ -12,6 +12,7 @@ module GDS
12
12
  module SSO
13
13
  autoload :FailureApp, "gds-sso/failure_app"
14
14
  autoload :ControllerMethods, "gds-sso/controller_methods"
15
+ autoload :LimitSessionData, "gds-sso/limit_session_data"
15
16
  autoload :User, "gds-sso/user"
16
17
  autoload :ApiAccess, "gds-sso/api_access"
17
18
  autoload :AuthoriseUser, "gds-sso/authorise_user"
@@ -38,6 +39,7 @@ module GDS
38
39
 
39
40
  OmniAuth.config.allowed_request_methods = %i[post get]
40
41
 
42
+ config.app_middleware.use GDS::SSO::LimitSessionData
41
43
  config.app_middleware.use ::OmniAuth::Builder do
42
44
  next if GDS::SSO::Config.api_only
43
45
 
@@ -1,5 +1,5 @@
1
1
  class ExampleController < ApplicationController
2
- before_action :authenticate_user!, except: :not_restricted
2
+ before_action :authenticate_user!, except: %i[not_restricted stuff_session]
3
3
  before_action -> { authorise_user!("execute") }, only: :this_requires_execute_permission
4
4
  def not_restricted
5
5
  render body: "jabberwocky"
@@ -16,4 +16,9 @@ class ExampleController < ApplicationController
16
16
  def constraint_restricted
17
17
  render body: "constraint restricted"
18
18
  end
19
+
20
+ def stuff_session
21
+ session["app_data"] = "A" * params.fetch(:size, 6000).to_i
22
+ render body: "ok"
23
+ end
19
24
  end
@@ -1,5 +1,5 @@
1
1
  class User < ActiveRecord::Base
2
2
  include GDS::SSO::User
3
3
 
4
- serialize :permissions, type: Array
4
+ serialize :permissions, type: Array, coder: YAML
5
5
  end
@@ -3,6 +3,7 @@
3
3
  Rails.application.routes.draw do
4
4
  get "/not-restricted" => "example#not_restricted"
5
5
  get "/restricted" => "example#restricted"
6
+ get "/stuff-session" => "example#stuff_session"
6
7
  get "/this-requires-execute-permission" => "example#this_requires_execute_permission"
7
8
 
8
9
  constraints(GDS::SSO::AuthorisedUserConstraint.new("constraint")) do
@@ -0,0 +1,98 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "GDS-SSO session data limitation", type: :request do
4
+ context "when a request would overflow the session cookie" do
5
+ it "responds with 400 when the query string is oversized" do
6
+ get "/auth/gds?payload=#{'A' * 6000}"
7
+
8
+ expect(response).to have_http_status(:bad_request)
9
+ end
10
+
11
+ it "responds with 400 when the Referer header is oversized" do
12
+ get "/auth/gds", headers: { "HTTP_REFERER" => "https://example.com/#{'A' * 6000}" }
13
+
14
+ expect(response).to have_http_status(:bad_request)
15
+ end
16
+
17
+ it "responds with 400 when the origin param is oversized" do
18
+ get "/auth/gds?origin=#{'A' * 6000}"
19
+
20
+ expect(response).to have_http_status(:bad_request)
21
+ end
22
+
23
+ it "responds with 400 when several individually small values together exceed the limit" do
24
+ get "/auth/gds?payload=#{'A' * 1300}", headers: { "HTTP_REFERER" => "https://example.com/#{'A' * 1300}" }
25
+
26
+ expect(response).to have_http_status(:bad_request)
27
+ end
28
+
29
+ it "responds with 400 to oversized return_to paths" do
30
+ get "/restricted?payload=#{'A' * 3000}"
31
+
32
+ expect(response).to have_http_status(:bad_request)
33
+ end
34
+
35
+ it "does not write the oversized data into the session cookie" do
36
+ get "/auth/gds?payload=#{'A' * 6000}"
37
+
38
+ expect(response).to have_http_status(:bad_request)
39
+ expect(response.headers["Set-Cookie"].to_s.bytesize).to be < ActionDispatch::Cookies::MAX_COOKIE_SIZE
40
+
41
+ # Regular request; no replay of oversized session data
42
+ get "/auth/gds"
43
+ expect(response).to have_http_status(:redirect)
44
+ expect(response.location).to start_with("http://signon/oauth/authorize")
45
+ end
46
+ end
47
+
48
+ context "when a request does not overflow the session cookie" do
49
+ it "redirects to signon, storing the query string, origin and state" do
50
+ get "/auth/gds?app_param=1", headers: { "HTTP_REFERER" => "https://example.com/some-page" }
51
+
52
+ expect(response).to have_http_status(:redirect)
53
+ expect(URI.parse(response.location).path).to eq("/oauth/authorize")
54
+ expect(session["omniauth.params"]).to eq("app_param" => "1")
55
+ expect(session["omniauth.origin"]).to eq("https://example.com/some-page")
56
+ expect(session["omniauth.state"]).to be_present
57
+ end
58
+
59
+ it "stores reasonably short return_to paths and redirects back to them" do
60
+ get "/restricted"
61
+
62
+ expect(response).to redirect_to("/auth/gds")
63
+ expect(session["return_to"]).to eq("/restricted")
64
+
65
+ state = request_to_establish_oauth_state
66
+ stub_signon_oauth_token_request
67
+ stub_successful_signon_user_request
68
+
69
+ get "/auth/gds/callback?code=code&state=#{state}"
70
+
71
+ expect(response).to redirect_to("/restricted")
72
+ end
73
+
74
+ it "does not limit the host applications's own session data" do
75
+ get "/stuff-session", params: { size: 1500 }
76
+
77
+ expect(response).to have_http_status(:success)
78
+ expect(session["app_data"].bytesize).to eq(1500)
79
+ end
80
+ end
81
+
82
+ context "when the host application causes an overflow" do
83
+ it "responds with 500 when the app itself overflows the cookie" do
84
+ get "/stuff-session", params: { size: 6000 }
85
+
86
+ expect(response).to have_http_status(:internal_server_error)
87
+ end
88
+ end
89
+
90
+ context "when the middleware is part of the stack" do
91
+ it "wraps OmniAuth and Warden so it sees all session writes" do
92
+ middlewares = Rails.application.middleware.middlewares.map(&:name)
93
+
94
+ expect(middlewares.index("GDS::SSO::LimitSessionData")).to be < middlewares.index("OmniAuth::Builder")
95
+ expect(middlewares.index("OmniAuth::Builder")).to be < middlewares.index("Warden::Manager")
96
+ end
97
+ end
98
+ end
@@ -18,14 +18,5 @@ describe GDS::SSO::FailureApp, type: :request do
18
18
  expect(response).to redirect_to("/auth/gds")
19
19
  expect(session["return_to"]).to eq(attempted_path)
20
20
  end
21
-
22
- it "should not attempt to store the return_to path in session when it is very long" do
23
- attempted_path = "some-#{'very-' * 1000}-long-path"
24
-
25
- get "/redirect", env: { "warden.options" => { attempted_path: } }
26
-
27
- expect(response).to redirect_to("/auth/gds")
28
- expect(session["return_to"]).to be_nil
29
- end
30
21
  end
31
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gds-sso
3
3
  version: !ruby/object:Gem::Version
4
- version: 22.3.0
4
+ version: 23.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
@@ -197,14 +197,14 @@ dependencies:
197
197
  requirements:
198
198
  - - '='
199
199
  - !ruby/object:Gem::Version
200
- version: 5.2.0
200
+ version: 5.2.1
201
201
  type: :development
202
202
  prerelease: false
203
203
  version_requirements: !ruby/object:Gem::Requirement
204
204
  requirements:
205
205
  - - '='
206
206
  - !ruby/object:Gem::Version
207
- version: 5.2.0
207
+ version: 5.2.1
208
208
  - !ruby/object:Gem::Dependency
209
209
  name: sqlite3
210
210
  requirement: !ruby/object:Gem::Requirement
@@ -257,6 +257,7 @@ files:
257
257
  - lib/gds-sso/config.rb
258
258
  - lib/gds-sso/controller_methods.rb
259
259
  - lib/gds-sso/failure_app.rb
260
+ - lib/gds-sso/limit_session_data.rb
260
261
  - lib/gds-sso/lint/user_spec.rb
261
262
  - lib/gds-sso/lint/user_test.rb
262
263
  - lib/gds-sso/railtie.rb
@@ -275,6 +276,7 @@ files:
275
276
  - spec/request/api/user_spec.rb
276
277
  - spec/request/authentication_spec.rb
277
278
  - spec/request/demo_app_spec.rb
279
+ - spec/request/limit_session_data_spec.rb
278
280
  - spec/spec_helper.rb
279
281
  - spec/support/controller_spy.rb
280
282
  - spec/support/request_helpers.rb
@@ -310,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
312
  - !ruby/object:Gem::Version
311
313
  version: '0'
312
314
  requirements: []
313
- rubygems_version: 4.0.10
315
+ rubygems_version: 4.0.23
314
316
  specification_version: 4
315
317
  summary: Client for GDS' OAuth 2-based SSO
316
318
  test_files:
@@ -325,6 +327,7 @@ test_files:
325
327
  - spec/request/api/user_spec.rb
326
328
  - spec/request/authentication_spec.rb
327
329
  - spec/request/demo_app_spec.rb
330
+ - spec/request/limit_session_data_spec.rb
328
331
  - spec/spec_helper.rb
329
332
  - spec/support/controller_spy.rb
330
333
  - spec/support/request_helpers.rb