gds-sso 22.2.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: cd35ea0d1b6cbd64b7ac7118faef8f0d028e51b000b912e39f2afa08fe5ca1fb
4
- data.tar.gz: 259d4c3379e5132c999aa5063356db9b1723060130c72a747ded41ac2c33e978
3
+ metadata.gz: 620c8f21e86429c4a35f325a47451e554e359845c3e680bfa2c1add771b72bd3
4
+ data.tar.gz: 34cee6af9fdc9e5ff976c1f9388f0578d5de74e0d621f1a20fb9a077a0178049
5
5
  SHA512:
6
- metadata.gz: d20aecd2dc96aad9a3c28ce830110991aa59e70b3f5332f7896a92ecc404edbf8d47d3f48d003c1f1d2080b925cb4158f3a492c71d2e08e5f3a7089569172bd0
7
- data.tar.gz: ece858e91091091ef617ef42243abb45b85cbc6d012851191045ed77d760c96cba0133b319dc237612a8bd76bed1b1757bd8b815db94d44be8998adcaccbb4f0
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.2.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.2.0
4
+ version: 23.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
@@ -149,6 +149,20 @@ dependencies:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
151
  version: '1.3'
152
+ - !ruby/object:Gem::Dependency
153
+ name: ostruct
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
152
166
  - !ruby/object:Gem::Dependency
153
167
  name: rake
154
168
  requirement: !ruby/object:Gem::Requirement
@@ -183,14 +197,14 @@ dependencies:
183
197
  requirements:
184
198
  - - '='
185
199
  - !ruby/object:Gem::Version
186
- version: 5.2.0
200
+ version: 5.2.1
187
201
  type: :development
188
202
  prerelease: false
189
203
  version_requirements: !ruby/object:Gem::Requirement
190
204
  requirements:
191
205
  - - '='
192
206
  - !ruby/object:Gem::Version
193
- version: 5.2.0
207
+ version: 5.2.1
194
208
  - !ruby/object:Gem::Dependency
195
209
  name: sqlite3
196
210
  requirement: !ruby/object:Gem::Requirement
@@ -243,6 +257,7 @@ files:
243
257
  - lib/gds-sso/config.rb
244
258
  - lib/gds-sso/controller_methods.rb
245
259
  - lib/gds-sso/failure_app.rb
260
+ - lib/gds-sso/limit_session_data.rb
246
261
  - lib/gds-sso/lint/user_spec.rb
247
262
  - lib/gds-sso/lint/user_test.rb
248
263
  - lib/gds-sso/railtie.rb
@@ -261,6 +276,7 @@ files:
261
276
  - spec/request/api/user_spec.rb
262
277
  - spec/request/authentication_spec.rb
263
278
  - spec/request/demo_app_spec.rb
279
+ - spec/request/limit_session_data_spec.rb
264
280
  - spec/spec_helper.rb
265
281
  - spec/support/controller_spy.rb
266
282
  - spec/support/request_helpers.rb
@@ -296,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
296
312
  - !ruby/object:Gem::Version
297
313
  version: '0'
298
314
  requirements: []
299
- rubygems_version: 4.0.9
315
+ rubygems_version: 4.0.23
300
316
  specification_version: 4
301
317
  summary: Client for GDS' OAuth 2-based SSO
302
318
  test_files:
@@ -311,6 +327,7 @@ test_files:
311
327
  - spec/request/api/user_spec.rb
312
328
  - spec/request/authentication_spec.rb
313
329
  - spec/request/demo_app_spec.rb
330
+ - spec/request/limit_session_data_spec.rb
314
331
  - spec/spec_helper.rb
315
332
  - spec/support/controller_spy.rb
316
333
  - spec/support/request_helpers.rb