gds-sso 20.0.0 → 21.1.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.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gds-sso
3
3
  version: !ruby/object:Gem::Version
4
- version: 20.0.0
4
+ version: 21.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-26 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: oauth2
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '5'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rack
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: rails
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -108,19 +122,19 @@ dependencies:
108
122
  - !ruby/object:Gem::Version
109
123
  version: 0.0.1
110
124
  - !ruby/object:Gem::Dependency
111
- name: capybara
125
+ name: climate_control
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
128
  - - "~>"
115
129
  - !ruby/object:Gem::Version
116
- version: '3'
130
+ version: '1'
117
131
  type: :development
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
- version: '3'
137
+ version: '1'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: combustion
126
140
  requirement: !ruby/object:Gem::Requirement
@@ -169,14 +183,14 @@ dependencies:
169
183
  requirements:
170
184
  - - '='
171
185
  - !ruby/object:Gem::Version
172
- version: 5.0.2
186
+ version: 5.1.18
173
187
  type: :development
174
188
  prerelease: false
175
189
  version_requirements: !ruby/object:Gem::Requirement
176
190
  requirements:
177
191
  - - '='
178
192
  - !ruby/object:Gem::Version
179
- version: 5.0.2
193
+ version: 5.1.18
180
194
  - !ruby/object:Gem::Dependency
181
195
  name: sqlite3
182
196
  requirement: !ruby/object:Gem::Requirement
@@ -191,20 +205,6 @@ dependencies:
191
205
  - - "~>"
192
206
  - !ruby/object:Gem::Version
193
207
  version: '2.1'
194
- - !ruby/object:Gem::Dependency
195
- name: timecop
196
- requirement: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - "~>"
199
- - !ruby/object:Gem::Version
200
- version: '0.9'
201
- type: :development
202
- prerelease: false
203
- version_requirements: !ruby/object:Gem::Requirement
204
- requirements:
205
- - - "~>"
206
- - !ruby/object:Gem::Version
207
- version: '0.9'
208
208
  - !ruby/object:Gem::Dependency
209
209
  name: webmock
210
210
  requirement: !ruby/object:Gem::Requirement
@@ -250,8 +250,6 @@ files:
250
250
  - lib/gds-sso/version.rb
251
251
  - lib/gds-sso/warden_config.rb
252
252
  - lib/omniauth/strategies/gds.rb
253
- - spec/controller/api_user_controller_spec.rb
254
- - spec/controller/controller_methods_spec.rb
255
253
  - spec/internal/app/assets/config/manifest.js
256
254
  - spec/internal/app/controllers/application_controller.rb
257
255
  - spec/internal/app/controllers/example_controller.rb
@@ -260,17 +258,21 @@ files:
260
258
  - spec/internal/config/initializers/gds-sso.rb
261
259
  - spec/internal/config/routes.rb
262
260
  - spec/internal/db/schema.rb
261
+ - spec/request/api/user_spec.rb
262
+ - spec/request/authentication_spec.rb
263
+ - spec/request/demo_app_spec.rb
263
264
  - spec/spec_helper.rb
264
265
  - spec/support/controller_spy.rb
266
+ - spec/support/request_helpers.rb
265
267
  - spec/support/serializable_user.rb
266
268
  - spec/support/test_user.rb
267
- - spec/support/timecop.rb
268
- - spec/system/authentication_and_authorisation_spec.rb
269
269
  - spec/unit/api_access_spec.rb
270
270
  - spec/unit/authorise_user_spec.rb
271
271
  - spec/unit/authorised_user_constraint_spec.rb
272
272
  - spec/unit/bearer_token_spec.rb
273
273
  - spec/unit/config_spec.rb
274
+ - spec/unit/controller_methods_spec.rb
275
+ - spec/unit/gds_sso_spec.rb
274
276
  - spec/unit/mock_bearer_token_spec.rb
275
277
  - spec/unit/railtie_spec.rb
276
278
  - spec/unit/session_serialisation_spec.rb
@@ -293,12 +295,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
293
295
  - !ruby/object:Gem::Version
294
296
  version: '0'
295
297
  requirements: []
296
- rubygems_version: 3.6.6
298
+ rubygems_version: 3.7.1
297
299
  specification_version: 4
298
300
  summary: Client for GDS' OAuth 2-based SSO
299
301
  test_files:
300
- - spec/controller/api_user_controller_spec.rb
301
- - spec/controller/controller_methods_spec.rb
302
302
  - spec/internal/app/assets/config/manifest.js
303
303
  - spec/internal/app/controllers/application_controller.rb
304
304
  - spec/internal/app/controllers/example_controller.rb
@@ -307,17 +307,21 @@ test_files:
307
307
  - spec/internal/config/initializers/gds-sso.rb
308
308
  - spec/internal/config/routes.rb
309
309
  - spec/internal/db/schema.rb
310
+ - spec/request/api/user_spec.rb
311
+ - spec/request/authentication_spec.rb
312
+ - spec/request/demo_app_spec.rb
310
313
  - spec/spec_helper.rb
311
314
  - spec/support/controller_spy.rb
315
+ - spec/support/request_helpers.rb
312
316
  - spec/support/serializable_user.rb
313
317
  - spec/support/test_user.rb
314
- - spec/support/timecop.rb
315
- - spec/system/authentication_and_authorisation_spec.rb
316
318
  - spec/unit/api_access_spec.rb
317
319
  - spec/unit/authorise_user_spec.rb
318
320
  - spec/unit/authorised_user_constraint_spec.rb
319
321
  - spec/unit/bearer_token_spec.rb
320
322
  - spec/unit/config_spec.rb
323
+ - spec/unit/controller_methods_spec.rb
324
+ - spec/unit/gds_sso_spec.rb
321
325
  - spec/unit/mock_bearer_token_spec.rb
322
326
  - spec/unit/railtie_spec.rb
323
327
  - spec/unit/session_serialisation_spec.rb
@@ -1,114 +0,0 @@
1
- require "spec_helper"
2
-
3
- def user_update_json
4
- {
5
- "user" => {
6
- "uid" => @user_to_update.uid,
7
- "name" => "Joshua Marshall",
8
- "email" => "user@domain.com",
9
- "permissions" => ["signin", "new permission"],
10
- "organisation_slug" => "justice-league",
11
- "organisation_content_id" => "aae1319e-5788-4677-998c-f1a53af528d0",
12
- "disabled" => false,
13
- },
14
- }.to_json
15
- end
16
-
17
- describe Api::UserController, type: :controller do
18
- before :each do
19
- user_to_update_attrs = [{
20
- uid: "a1s2d3#{rand(10_000)}",
21
- email: "old@domain.com",
22
- name: "Moshua Jarshall",
23
- permissions: %w[signin],
24
- }]
25
-
26
- signon_sso_push_user_attrs = [{
27
- uid: "a1s2d3#{rand(10_000)}",
28
- email: "ssopushuser@legit.com",
29
- name: "SSO Push user",
30
- permissions: %w[signin user_update_permission],
31
- }]
32
-
33
- @user_to_update = User.create!(*user_to_update_attrs)
34
- @signon_sso_push_user = User.create!(*signon_sso_push_user_attrs)
35
- end
36
-
37
- describe "PUT update" do
38
- it "should deny access to anybody but the API user (or a user with 'user_update_permission')" do
39
- malicious_user = User.new({
40
- uid: "2",
41
- name: "User",
42
- permissions: %w[signin],
43
- })
44
-
45
- request.env["warden"] = double("stub warden", authenticate!: true, authenticated?: true, user: malicious_user)
46
-
47
- request.env["RAW_POST_DATA"] = user_update_json
48
- put :update, body: user_update_json, params: { uid: @user_to_update.uid }
49
-
50
- expect(response.status).to eq(403)
51
- end
52
-
53
- it "should create/update the user record in the same way as the OAuth callback" do
54
- # Test that it authenticates
55
- request.env["warden"] = double("mock warden")
56
- expect(request.env["warden"]).to receive(:authenticate!).at_least(:once).and_return(true)
57
- expect(request.env["warden"]).to receive(:authenticated?).at_least(:once).and_return(true)
58
- expect(request.env["warden"]).to receive(:user).at_least(:once).and_return(@signon_sso_push_user)
59
-
60
- request.env["RAW_POST_DATA"] = user_update_json
61
- put :update, body: user_update_json, params: { uid: @user_to_update.uid }
62
-
63
- @user_to_update.reload
64
- expect(@user_to_update.name).to eq("Joshua Marshall")
65
- expect(@user_to_update.email).to eq("user@domain.com")
66
- expect(@user_to_update.permissions).to eq(["signin", "new permission"])
67
- expect(@user_to_update.organisation_slug).to eq("justice-league")
68
- expect(@user_to_update.organisation_content_id).to eq("aae1319e-5788-4677-998c-f1a53af528d0")
69
- expect(response.content_type).to eq("text/plain")
70
- end
71
- end
72
-
73
- describe "POST reauth" do
74
- it "should deny access to anybody but the API user (or a user with 'user_update_permission')" do
75
- malicious_user = User.new({
76
- uid: "2",
77
- name: "User",
78
- permissions: %w[signin],
79
- })
80
-
81
- request.env["warden"] = double("stub warden", authenticate!: true, authenticated?: true, user: malicious_user)
82
-
83
- post :reauth, params: { uid: @user_to_update.uid }
84
-
85
- expect(response.status).to eq(403)
86
- end
87
-
88
- it "should return success if user record doesn't exist" do
89
- request.env["warden"] = double("mock warden")
90
- expect(request.env["warden"]).to receive(:authenticate!).at_least(:once).and_return(true)
91
- expect(request.env["warden"]).to receive(:authenticated?).at_least(:once).and_return(true)
92
- expect(request.env["warden"]).to receive(:user).at_least(:once).and_return(@signon_sso_push_user)
93
-
94
- post :reauth, params: { uid: "nonexistent-user" }
95
-
96
- expect(response.status).to eq(200)
97
- expect(response.content_type).to eq("text/plain")
98
- end
99
-
100
- it "should set remotely_signed_out to true on the user" do
101
- # Test that it authenticates
102
- request.env["warden"] = double("mock warden")
103
- expect(request.env["warden"]).to receive(:authenticate!).at_least(:once).and_return(true)
104
- expect(request.env["warden"]).to receive(:authenticated?).at_least(:once).and_return(true)
105
- expect(request.env["warden"]).to receive(:user).at_least(:once).and_return(@signon_sso_push_user)
106
-
107
- post :reauth, params: { uid: @user_to_update.uid }
108
-
109
- @user_to_update.reload
110
- expect(@user_to_update).to be_remotely_signed_out
111
- expect(response.content_type).to eq("text/plain")
112
- end
113
- end
114
- end
@@ -1,7 +0,0 @@
1
- require "timecop"
2
-
3
- RSpec.configure do |config|
4
- config.after :each do
5
- Timecop.return
6
- end
7
- end
@@ -1,172 +0,0 @@
1
- require "spec_helper"
2
-
3
- RSpec.describe "Authenication and authorisation" do
4
- context "omniauth request phase" do
5
- let(:redirect_url) { URI.parse(page.response_headers["Location"]) }
6
- let(:authorize_params) { Rack::Utils.parse_query(redirect_url.query) }
7
-
8
- before do
9
- visit "/auth/gds"
10
- end
11
-
12
- it "includes pkce code_challenge_method in request for /oauth/authorize" do
13
- expect(redirect_url.path).to eql("/oauth/authorize")
14
- expect(authorize_params["code_challenge_method"]).to eq("S256")
15
- end
16
-
17
- it "includes pkce code_challenge in request for /oauth/authorize" do
18
- expect(redirect_url.path).to eql("/oauth/authorize")
19
- expect(authorize_params["code_challenge"]).to be_present
20
- end
21
- end
22
-
23
- context "omniauth callback phase" do
24
- it "includes pkce code_verifier in request for /oauth/access_token" do
25
- visit "/auth/gds"
26
-
27
- redirect_url = URI.parse(page.response_headers["Location"])
28
- expect(redirect_url.path).to eql("/oauth/authorize")
29
- state = Rack::Utils.parse_query(redirect_url.query)["state"]
30
-
31
- stub_request(:post, "http://signon/oauth/access_token")
32
-
33
- visit "/auth/gds/callback?state=#{state}"
34
-
35
- expect(WebMock).to have_requested(:post, "http://signon/oauth/access_token")
36
- .with(body: hash_including({ "code_verifier" => /.*/ }))
37
- end
38
- end
39
-
40
- context "when accessing a route that doesn't require permissions or authentication" do
41
- it "allows access" do
42
- visit "/not-restricted"
43
- expect(page).to have_content("jabberwocky")
44
- end
45
- end
46
-
47
- context "when accessing a route that requires authentication" do
48
- it "redirects an unauthenticated request to signon" do
49
- # We manually follow the redirects because we have configured capybara
50
- # to not follow redirects (and thus allow testing an external redirect)
51
- visit "/restricted"
52
- expect(page.response_headers["Location"]).to match("/auth/gds")
53
- visit page.response_headers["Location"]
54
- expect(page.response_headers["Location"]).to match("http://signon/oauth/authorize")
55
- end
56
-
57
- it "allows access for an authenticated user" do
58
- stub_signon_authenticated
59
-
60
- visit "/restricted"
61
- expect(page).to have_content("restricted kablooie")
62
- end
63
-
64
- it "restricts access if a user is authenticated but remotely signed out" do
65
- stub_signon_authenticated
66
- User.last.set_remotely_signed_out!
67
-
68
- visit "/restricted"
69
- expect(page.status_code).to eql(302)
70
- expect(page.response_headers["Location"]).to match("/auth/gds")
71
- end
72
-
73
- it "restricts access if a user is authenticated but session has expired" do
74
- stub_signon_authenticated
75
-
76
- Timecop.travel(Time.now.utc + GDS::SSO::Config.auth_valid_for + 5.minutes) do
77
- visit "/restricted"
78
- expect(page.status_code).to eql(302)
79
- expect(page.response_headers["Location"]).to match("/auth/gds")
80
- end
81
- end
82
-
83
- it "allows access when given a valid bearer token" do
84
- stub_signon_user_request
85
- page.driver.header("Authorization", "Bearer 123")
86
-
87
- visit "/restricted"
88
- expect(page).to have_content("restricted kablooie")
89
- end
90
-
91
- it "restricts access when given an invalid bearer token" do
92
- stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
93
- .to_return(status: 401)
94
- page.driver.header("Authorization", "Bearer 123")
95
-
96
- visit "/restricted"
97
- expect(page.status_code).to eq(401)
98
- end
99
-
100
- it "returns a 401 when a bearer token is missing and the app is api_only" do
101
- allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
102
-
103
- visit "/restricted"
104
- expect(page.status_code).to eq(401)
105
- end
106
- end
107
-
108
- context "when accessing a route that requires a permission" do
109
- it "allows access when an authenticated user has the permission" do
110
- stub_signon_authenticated(permissions: %w[execute])
111
- visit "/this-requires-execute-permission"
112
- expect(page).to have_content("you have execute permission")
113
- end
114
-
115
- it "restricts access when an authenticated user lacks the permission" do
116
- stub_signon_authenticated
117
- visit "/this-requires-execute-permission"
118
- expect(page.status_code).to eq(403)
119
- end
120
- end
121
-
122
- context "when accessing a route that is restricted by the authorised user constraint" do
123
- it "allows access when an authenticated user has correct permissions" do
124
- stub_signon_authenticated(permissions: %w[execute])
125
- visit "/constraint-restricted"
126
- expect(page).to have_content("constraint restricted")
127
- end
128
-
129
- it "redirects an unauthenticated request to signon" do
130
- visit "/constraint-restricted"
131
- expect(page.response_headers["Location"]).to match("/auth/gds")
132
- visit page.response_headers["Location"]
133
- expect(page.response_headers["Location"]).to match("http://signon/oauth/authorize")
134
- end
135
-
136
- it "restricts access when an authenticated user does not have the correct permissions" do
137
- stub_signon_authenticated(permissions: %w[no-access])
138
- visit "/constraint-restricted"
139
- expect(page.status_code).to eq(403)
140
- end
141
- end
142
-
143
- def stub_signon_authenticated(permissions: [])
144
- # visit restricted page to trigger redirect URL to record state attribute
145
- visit "/auth/gds"
146
- state = CGI.parse(URI.parse(page.response_headers["Location"]).query)
147
- .then { |query| query["state"].first }
148
-
149
- stub_request(:post, "http://signon/oauth/access_token")
150
- .to_return(body: { access_token: "token" }.to_json,
151
- headers: { content_type: "application/json" })
152
-
153
- stub_signon_user_request(permissions:)
154
-
155
- visit "/auth/gds/callback?code=code&state=#{state}"
156
- end
157
-
158
- def stub_signon_user_request(permissions: [])
159
- stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
160
- .to_return(
161
- body: {
162
- user: {
163
- uid: "123",
164
- email: "test-user@example.com",
165
- name: "Test User",
166
- permissions:,
167
- },
168
- }.to_json,
169
- headers: { content_type: "application/json" },
170
- )
171
- end
172
- end