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.
- checksums.yaml +4 -4
- data/config/routes.rb +4 -3
- data/lib/gds-sso/api_access.rb +6 -1
- data/lib/gds-sso/bearer_token.rb +1 -0
- data/lib/gds-sso/config.rb +5 -0
- data/lib/gds-sso/failure_app.rb +8 -1
- data/lib/gds-sso/version.rb +1 -1
- data/spec/internal/config/routes.rb +1 -1
- data/spec/request/api/user_spec.rb +144 -0
- data/spec/request/authentication_spec.rb +77 -0
- data/spec/request/demo_app_spec.rb +264 -0
- data/spec/spec_helper.rb +16 -7
- data/spec/support/request_helpers.rb +45 -0
- data/spec/unit/api_access_spec.rb +37 -19
- data/spec/unit/failure_app_spec.rb +31 -0
- data/spec/unit/mock_bearer_token_spec.rb +6 -0
- data/spec/unit/session_serialisation_spec.rb +5 -4
- metadata +20 -30
- data/spec/controller/api_user_controller_spec.rb +0 -114
- data/spec/support/timecop.rb +0 -7
- data/spec/system/authentication_and_authorisation_spec.rb +0 -245
- /data/spec/{controller → unit}/controller_methods_spec.rb +0 -0
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
.to
|
33
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
18
|
-
|
17
|
+
freeze_time do
|
18
|
+
result = @serializer.serialize(@user)
|
19
19
|
|
20
|
-
|
21
|
-
|
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:
|
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:
|
125
|
+
name: climate_control
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|
127
127
|
requirements:
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
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: '
|
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: '
|
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: '
|
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.
|
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.
|
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
|