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 +4 -4
- data/lib/gds-sso/failure_app.rb +0 -3
- data/lib/gds-sso/limit_session_data.rb +66 -0
- data/lib/gds-sso/version.rb +1 -1
- data/lib/gds-sso.rb +2 -0
- data/spec/internal/app/controllers/example_controller.rb +6 -1
- data/spec/internal/app/models/user.rb +1 -1
- data/spec/internal/config/routes.rb +1 -0
- data/spec/request/limit_session_data_spec.rb +98 -0
- data/spec/unit/failure_app_spec.rb +0 -9
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 620c8f21e86429c4a35f325a47451e554e359845c3e680bfa2c1add771b72bd3
|
|
4
|
+
data.tar.gz: 34cee6af9fdc9e5ff976c1f9388f0578d5de74e0d621f1a20fb9a077a0178049
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45dfe6138c1b0fa85adef3f89e874bb7dd6e28597c4c36092729012e05f9d1eb6ae9e336968c21aff61d689e51b87d5911b6ce002b8c4820160a1d71a6e5e371
|
|
7
|
+
data.tar.gz: dcf2803364d61e31bddb3d55d64c212fe389b4351c7143646353ee4bb3836c2deb54181b288a37ff60571b3dc93621064e4ed4474a17b39afdbd4a37ccd270e7
|
data/lib/gds-sso/failure_app.rb
CHANGED
|
@@ -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
|
data/lib/gds-sso/version.rb
CHANGED
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:
|
|
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
|
|
@@ -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:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|