gds-sso 21.0.0 → 22.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.
@@ -0,0 +1,45 @@
1
+ module RequestHelpers
2
+ def authenticate_with_stub_signon(permissions: [])
3
+ state = request_to_establish_oauth_state
4
+
5
+ stub_signon_oauth_token_request
6
+ stub_successful_signon_user_request(permissions:)
7
+
8
+ get "/auth/gds/callback?code=code&state=#{state}"
9
+ expect(response).to have_http_status(:redirect)
10
+ end
11
+
12
+ def request_to_establish_oauth_state
13
+ get "/auth/gds"
14
+ expect(response).to have_http_status(:redirect)
15
+ location = URI.parse(response.location)
16
+ query = Rack::Utils.parse_query(location.query)
17
+ query.fetch("state")
18
+ end
19
+
20
+ def stub_signon_oauth_token_request
21
+ stub_request(:post, "http://signon/oauth/access_token")
22
+ .to_return(body: { access_token: "token" }.to_json,
23
+ headers: { content_type: "application/json" })
24
+ end
25
+
26
+ def stub_successful_signon_user_request(permissions: [])
27
+ stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
28
+ .to_return(
29
+ body: {
30
+ user: {
31
+ uid: "123",
32
+ email: "test-user@example.com",
33
+ name: "Test User",
34
+ permissions:,
35
+ },
36
+ }.to_json,
37
+ headers: { content_type: "application/json" },
38
+ )
39
+ end
40
+
41
+ def stub_failed_signon_user_request
42
+ stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
43
+ .to_return(status: 401)
44
+ end
45
+ end
@@ -18,31 +18,49 @@ describe GDS::SSO::ApiAccess do
18
18
  expect(described_class.api_call?({})).to be(true)
19
19
  end
20
20
 
21
- it "returns true if the request matches the api_request_matcher" do
22
- allow(GDS::SSO::Config)
23
- .to receive(:api_request_matcher)
24
- .and_return(->(request) { request.path == "/api" })
21
+ context "when an api_request_matcher has been configured" do
22
+ before do
23
+ allow(GDS::SSO::Config)
24
+ .to receive(:api_request_matcher)
25
+ .and_return(->(request) { request.path == "/api" })
26
+ end
25
27
 
26
- env = Rack::MockRequest.env_for("/api")
27
- expect(described_class.api_call?(env)).to be(true)
28
- end
28
+ it "returns true if the request matches the api_request_matcher" do
29
+ env = Rack::MockRequest.env_for("/api")
30
+ expect(described_class.api_call?(env)).to be(true)
31
+ end
29
32
 
30
- it "returns false if the request doesn't match the api_request_matcher" do
31
- allow(GDS::SSO::Config)
32
- .to receive(:api_request_matcher)
33
- .and_return(->(request) { request.path == "/api" })
33
+ it "returns true if the request is for GDS SSO API at default location" do
34
+ env = Rack::MockRequest.env_for("/auth/gds/api/#{SecureRandom.uuid}")
35
+ expect(described_class.api_call?(env)).to be(true)
36
+ end
34
37
 
35
- env = Rack::MockRequest.env_for("/other")
36
- expect(described_class.api_call?(env)).to be(false)
37
- end
38
+ it "returns true if it matches a configured gds_sso_api_request_matcher" do
39
+ allow(GDS::SSO::Config)
40
+ .to receive(:gds_sso_api_request_matcher)
41
+ .and_return(->(request) { request.path == "/special/gds-sso/route" })
38
42
 
39
- it "returns true if a bearer token is present" do
40
- env = { "HTTP_AUTHORIZATION" => "Bearer 1234:5678" }
41
- expect(described_class.api_call?(env)).to be(true)
43
+ env = Rack::MockRequest.env_for("/special/gds-sso/route")
44
+ expect(described_class.api_call?(env)).to be(true)
45
+ end
46
+
47
+ it "returns false if the request doesn't match the gds_sso_api_request_matcher or api_request_matcher" do
48
+ allow(GDS::SSO::Config).to receive(:gds_sso_api_request_matcher).and_return(nil)
49
+
50
+ env = Rack::MockRequest.env_for("/other")
51
+ expect(described_class.api_call?(env)).to be(false)
52
+ end
42
53
  end
43
54
 
44
- it "returns false otherwise" do
45
- expect(described_class.api_call?({})).to be(false)
55
+ context "when an api_request_matcher has not been configured" do
56
+ it "returns true if a bearer token is present" do
57
+ env = { "HTTP_AUTHORIZATION" => "Bearer 1234:5678" }
58
+ expect(described_class.api_call?(env)).to be(true)
59
+ end
60
+
61
+ it "returns false if nothing indicates an API call" do
62
+ expect(described_class.api_call?({})).to be(false)
63
+ end
46
64
  end
47
65
  end
48
66
 
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe GDS::SSO::FailureApp, type: :request do
4
+ describe "#redirect" do
5
+ before do
6
+ Rails.application.routes.draw do
7
+ get "redirect", to: GDS::SSO::FailureApp.action(:redirect)
8
+ end
9
+ end
10
+
11
+ after { Rails.application.reload_routes! }
12
+
13
+ it "should store the return_to path in session when it is reasonably short" do
14
+ attempted_path = "some-reasonably-short-path"
15
+
16
+ get "/redirect", env: { "warden.options" => { attempted_path: } }
17
+
18
+ expect(response).to redirect_to("/auth/gds")
19
+ expect(session["return_to"]).to eq(attempted_path)
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
+ end
31
+ end
@@ -10,6 +10,12 @@ describe GDS::SSO::MockBearerToken do
10
10
  expect(described_class.locate("anything")).to be(test_user)
11
11
  end
12
12
 
13
+ it "returns nil if ENV['GDS_SSO_MOCK_INVALID'] is set" do
14
+ ClimateControl.modify("GDS_SSO_MOCK_INVALID" => "1") do
15
+ expect(described_class.locate("anything")).to be_nil
16
+ end
17
+ end
18
+
13
19
  it "doesn't modify the permissions of GDS::SSO.test_user" do
14
20
  test_user = TestUser.new(permissions: [])
15
21
  allow(GDS::SSO).to receive(:test_user).and_return(test_user)
@@ -14,11 +14,12 @@ describe Warden::SessionSerializer do
14
14
 
15
15
  describe "serializing a user" do
16
16
  it "should return the uid and an ISO 8601 string timestamp" do
17
- Timecop.freeze
18
- result = @serializer.serialize(@user)
17
+ freeze_time do
18
+ result = @serializer.serialize(@user)
19
19
 
20
- expect(result).to eq([1234, Time.now.utc.iso8601])
21
- expect(result.last).to be_a(String)
20
+ expect(result).to eq([1234, Time.now.utc.iso8601])
21
+ expect(result.last).to be_a(String)
22
+ end
22
23
  end
23
24
 
24
25
  it "should return nil if the user has no uid" do
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: 21.0.0
4
+ version: 22.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
@@ -122,19 +122,19 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: 0.0.1
124
124
  - !ruby/object:Gem::Dependency
125
- name: capybara
125
+ name: climate_control
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '3'
130
+ version: '1'
131
131
  type: :development
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '3'
137
+ version: '1'
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: combustion
140
140
  requirement: !ruby/object:Gem::Requirement
@@ -169,28 +169,28 @@ dependencies:
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '6'
172
+ version: '8'
173
173
  type: :development
174
174
  prerelease: false
175
175
  version_requirements: !ruby/object:Gem::Requirement
176
176
  requirements:
177
177
  - - "~>"
178
178
  - !ruby/object:Gem::Version
179
- version: '6'
179
+ version: '8'
180
180
  - !ruby/object:Gem::Dependency
181
181
  name: rubocop-govuk
182
182
  requirement: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - '='
185
185
  - !ruby/object:Gem::Version
186
- version: 5.0.2
186
+ version: 5.1.19
187
187
  type: :development
188
188
  prerelease: false
189
189
  version_requirements: !ruby/object:Gem::Requirement
190
190
  requirements:
191
191
  - - '='
192
192
  - !ruby/object:Gem::Version
193
- version: 5.0.2
193
+ version: 5.1.19
194
194
  - !ruby/object:Gem::Dependency
195
195
  name: sqlite3
196
196
  requirement: !ruby/object:Gem::Requirement
@@ -205,20 +205,6 @@ dependencies:
205
205
  - - "~>"
206
206
  - !ruby/object:Gem::Version
207
207
  version: '2.1'
208
- - !ruby/object:Gem::Dependency
209
- name: timecop
210
- requirement: !ruby/object:Gem::Requirement
211
- requirements:
212
- - - "~>"
213
- - !ruby/object:Gem::Version
214
- version: '0.9'
215
- type: :development
216
- prerelease: false
217
- version_requirements: !ruby/object:Gem::Requirement
218
- requirements:
219
- - - "~>"
220
- - !ruby/object:Gem::Version
221
- version: '0.9'
222
208
  - !ruby/object:Gem::Dependency
223
209
  name: webmock
224
210
  requirement: !ruby/object:Gem::Requirement
@@ -264,8 +250,6 @@ files:
264
250
  - lib/gds-sso/version.rb
265
251
  - lib/gds-sso/warden_config.rb
266
252
  - lib/omniauth/strategies/gds.rb
267
- - spec/controller/api_user_controller_spec.rb
268
- - spec/controller/controller_methods_spec.rb
269
253
  - spec/internal/app/assets/config/manifest.js
270
254
  - spec/internal/app/controllers/application_controller.rb
271
255
  - spec/internal/app/controllers/example_controller.rb
@@ -274,17 +258,21 @@ files:
274
258
  - spec/internal/config/initializers/gds-sso.rb
275
259
  - spec/internal/config/routes.rb
276
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
277
264
  - spec/spec_helper.rb
278
265
  - spec/support/controller_spy.rb
266
+ - spec/support/request_helpers.rb
279
267
  - spec/support/serializable_user.rb
280
268
  - spec/support/test_user.rb
281
- - spec/support/timecop.rb
282
- - spec/system/authentication_and_authorisation_spec.rb
283
269
  - spec/unit/api_access_spec.rb
284
270
  - spec/unit/authorise_user_spec.rb
285
271
  - spec/unit/authorised_user_constraint_spec.rb
286
272
  - spec/unit/bearer_token_spec.rb
287
273
  - spec/unit/config_spec.rb
274
+ - spec/unit/controller_methods_spec.rb
275
+ - spec/unit/failure_app_spec.rb
288
276
  - spec/unit/gds_sso_spec.rb
289
277
  - spec/unit/mock_bearer_token_spec.rb
290
278
  - spec/unit/railtie_spec.rb
@@ -312,8 +300,6 @@ rubygems_version: 3.7.1
312
300
  specification_version: 4
313
301
  summary: Client for GDS' OAuth 2-based SSO
314
302
  test_files:
315
- - spec/controller/api_user_controller_spec.rb
316
- - spec/controller/controller_methods_spec.rb
317
303
  - spec/internal/app/assets/config/manifest.js
318
304
  - spec/internal/app/controllers/application_controller.rb
319
305
  - spec/internal/app/controllers/example_controller.rb
@@ -322,17 +308,21 @@ test_files:
322
308
  - spec/internal/config/initializers/gds-sso.rb
323
309
  - spec/internal/config/routes.rb
324
310
  - spec/internal/db/schema.rb
311
+ - spec/request/api/user_spec.rb
312
+ - spec/request/authentication_spec.rb
313
+ - spec/request/demo_app_spec.rb
325
314
  - spec/spec_helper.rb
326
315
  - spec/support/controller_spy.rb
316
+ - spec/support/request_helpers.rb
327
317
  - spec/support/serializable_user.rb
328
318
  - spec/support/test_user.rb
329
- - spec/support/timecop.rb
330
- - spec/system/authentication_and_authorisation_spec.rb
331
319
  - spec/unit/api_access_spec.rb
332
320
  - spec/unit/authorise_user_spec.rb
333
321
  - spec/unit/authorised_user_constraint_spec.rb
334
322
  - spec/unit/bearer_token_spec.rb
335
323
  - spec/unit/config_spec.rb
324
+ - spec/unit/controller_methods_spec.rb
325
+ - spec/unit/failure_app_spec.rb
336
326
  - spec/unit/gds_sso_spec.rb
337
327
  - spec/unit/mock_bearer_token_spec.rb
338
328
  - spec/unit/railtie_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