gds-sso 17.1.1 → 18.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/README.md +0 -6
- data/lib/gds-sso/railtie.rb +1 -0
- data/lib/gds-sso/version.rb +1 -1
- data/spec/internal/app/controllers/example_controller.rb +5 -4
- data/spec/internal/config/initializers/gds-sso.rb +1 -1
- data/spec/internal/config/routes.rb +2 -3
- data/spec/spec_helper.rb +13 -7
- data/spec/system/authentication_and_authorisation_spec.rb +115 -0
- metadata +38 -56
- data/spec/fixtures/integration/authorize_api_users.sql +0 -7
- data/spec/fixtures/integration/signon.sql +0 -26
- data/spec/internal/config/storage.yml +0 -3
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/requests/end_to_end_spec.rb +0 -215
- data/spec/support/backport_controller_test_params.rb +0 -21
- data/spec/support/signon_integration_helpers.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6450ab2114242674c0c5f9ddeba9d15cca06e538f2f211a1c621469f9fd2d9af
|
4
|
+
data.tar.gz: eedc4aaa3abd89833c43af03c841f63dc6d68f9fbbd965a15aa917bc20ad933a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfe330d97944bac31202779575770107056ca493ac5af511eb5fb316bbd4a83b459367dd683d69c01656c3e446177dabd008e47cf6d83138f29f139b7e765374
|
7
|
+
data.tar.gz: 01b11032563802c91717c6418150dafadd4518f95845c938bb6ad855895971d541f1a95d59cddae92060e4896ec6fdea6620af24a72515cd9fb79074d705c3c3
|
data/README.md
CHANGED
@@ -177,12 +177,6 @@ Run the tests with:
|
|
177
177
|
bundle exec rake
|
178
178
|
```
|
179
179
|
|
180
|
-
By default, the tests use the master of [Signon](https://github.com/alphagov/signon) for running integration tests. If you want to use a branch (or commit, or tag), you can run it like this:
|
181
|
-
|
182
|
-
```
|
183
|
-
SIGNON_COMMITISH=my_branch_name bundle exec rake
|
184
|
-
```
|
185
|
-
|
186
180
|
## Licence
|
187
181
|
|
188
182
|
[MIT License](LICENCE)
|
data/lib/gds-sso/railtie.rb
CHANGED
data/lib/gds-sso/version.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class ExampleController < ApplicationController
|
2
|
-
before_action :authenticate_user!,
|
2
|
+
before_action :authenticate_user!, except: :not_restricted
|
3
|
+
before_action -> { authorise_user!("execute") }, only: :this_requires_execute_permission
|
3
4
|
|
4
|
-
def
|
5
|
+
def not_restricted
|
5
6
|
render body: "jabberwocky"
|
6
7
|
end
|
7
8
|
|
@@ -9,7 +10,7 @@ class ExampleController < ApplicationController
|
|
9
10
|
render body: "restricted kablooie"
|
10
11
|
end
|
11
12
|
|
12
|
-
def
|
13
|
-
render body: "you have
|
13
|
+
def this_requires_execute_permission
|
14
|
+
render body: "you have execute permission"
|
14
15
|
end
|
15
16
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Rails.application.routes.draw do
|
4
|
-
|
5
|
-
root to: "example#index"
|
4
|
+
get "/not-restricted" => "example#not_restricted"
|
6
5
|
get "/restricted" => "example#restricted"
|
7
|
-
get "/
|
6
|
+
get "/this-requires-execute-permission" => "example#this_requires_execute_permission"
|
8
7
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,22 +2,28 @@
|
|
2
2
|
# Bad things happen if we don't ;-)
|
3
3
|
ENV["GDS_SSO_STRATEGY"] = "real"
|
4
4
|
|
5
|
-
require "bundler/setup"
|
6
|
-
require "combustion"
|
7
5
|
require "capybara/rspec"
|
6
|
+
require "webmock/rspec"
|
7
|
+
require "combustion"
|
8
8
|
|
9
|
-
Combustion.initialize! :all
|
9
|
+
Combustion.initialize! :all do
|
10
|
+
config.cache_store = :null_store
|
11
|
+
end
|
10
12
|
|
11
13
|
require "rspec/rails"
|
12
14
|
require "capybara/rails"
|
13
|
-
|
14
|
-
require "capybara/mechanize"
|
15
|
+
WebMock.disable_net_connect!
|
15
16
|
|
16
17
|
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].sort.each { |f| require f }
|
17
18
|
|
19
|
+
Capybara.register_driver :rack_test do |app|
|
20
|
+
Capybara::RackTest::Driver.new(app, follow_redirects: false)
|
21
|
+
end
|
22
|
+
|
18
23
|
RSpec.configure do |config|
|
19
24
|
config.run_all_when_everything_filtered = true
|
20
25
|
config.filter_run :focus
|
26
|
+
config.use_transactional_fixtures = true
|
21
27
|
|
22
28
|
# Run specs in random order to surface order dependencies. If you find an
|
23
29
|
# order dependency and want to debug it, you can fix the order by providing
|
@@ -25,6 +31,6 @@ RSpec.configure do |config|
|
|
25
31
|
# --seed 1234
|
26
32
|
config.order = "random"
|
27
33
|
|
28
|
-
config.include
|
29
|
-
config.include
|
34
|
+
config.include Warden::Test::Helpers
|
35
|
+
config.include Capybara::DSL
|
30
36
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "Authenication and authorisation" do
|
4
|
+
context "when accessing a route that doesn't require permissions or authentication" do
|
5
|
+
it "allows access" do
|
6
|
+
visit "/not-restricted"
|
7
|
+
expect(page).to have_content("jabberwocky")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when accessing a route that requires authentication" do
|
12
|
+
it "redirects an unauthenticated request to signon" do
|
13
|
+
# We manually follow the redirects because we have configured capybara
|
14
|
+
# to not follow redirects (and thus allow testing an external redirect)
|
15
|
+
visit "/restricted"
|
16
|
+
expect(page.response_headers["Location"]).to match("/auth/gds")
|
17
|
+
visit page.response_headers["Location"]
|
18
|
+
expect(page.response_headers["Location"]).to match("http://signon/oauth/authorize")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows access for an authenticated user" do
|
22
|
+
stub_signon_authenticated
|
23
|
+
|
24
|
+
visit "/restricted"
|
25
|
+
expect(page).to have_content("restricted kablooie")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "restricts access if a user is authenticated but remotely signed out" do
|
29
|
+
stub_signon_authenticated
|
30
|
+
User.last.set_remotely_signed_out!
|
31
|
+
|
32
|
+
visit "/restricted"
|
33
|
+
expect(page.status_code).to eql(302)
|
34
|
+
expect(page.response_headers["Location"]).to match("/auth/gds")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "restricts access if a user is authenticated but session has expired" do
|
38
|
+
stub_signon_authenticated
|
39
|
+
|
40
|
+
Timecop.travel(Time.now.utc + GDS::SSO::Config.auth_valid_for + 5.minutes) do
|
41
|
+
visit "/restricted"
|
42
|
+
expect(page.status_code).to eql(302)
|
43
|
+
expect(page.response_headers["Location"]).to match("/auth/gds")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "allows access when given a valid bearer token" do
|
48
|
+
stub_signon_user_request
|
49
|
+
page.driver.header("Authorization", "Bearer 123")
|
50
|
+
|
51
|
+
visit "/restricted"
|
52
|
+
expect(page).to have_content("restricted kablooie")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "restricts access when given an invalid bearer token" do
|
56
|
+
stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
|
57
|
+
.to_return(status: 401)
|
58
|
+
page.driver.header("Authorization", "Bearer 123")
|
59
|
+
|
60
|
+
visit "/restricted"
|
61
|
+
expect(page.status_code).to eq(401)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns a 401 when a bearer token is missing and the app is api_only" do
|
65
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
66
|
+
|
67
|
+
visit "/restricted"
|
68
|
+
expect(page.status_code).to eq(401)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when accessing a route that requires a permission" do
|
73
|
+
it "allows access when an authenticated user has the permission" do
|
74
|
+
stub_signon_authenticated(permissions: %w[execute])
|
75
|
+
visit "/this-requires-execute-permission"
|
76
|
+
expect(page).to have_content("you have execute permission")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "restricts access when an authenticated user lacks the permission" do
|
80
|
+
stub_signon_authenticated
|
81
|
+
visit "/this-requires-execute-permission"
|
82
|
+
expect(page.status_code).to eq(403)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def stub_signon_authenticated(permissions: [])
|
87
|
+
# visit restricted page to trigger redirect URL to record state attribute
|
88
|
+
visit "/auth/gds"
|
89
|
+
state = CGI.parse(URI.parse(page.response_headers["Location"]).query)
|
90
|
+
.then { |query| query["state"].first }
|
91
|
+
|
92
|
+
stub_request(:post, "http://signon/oauth/access_token")
|
93
|
+
.to_return(body: { access_token: "token" }.to_json,
|
94
|
+
headers: { content_type: "application/json" })
|
95
|
+
|
96
|
+
stub_signon_user_request(permissions: permissions)
|
97
|
+
|
98
|
+
visit "/auth/gds/callback?code=code&state=#{state}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def stub_signon_user_request(permissions: [])
|
102
|
+
stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
|
103
|
+
.to_return(
|
104
|
+
body: {
|
105
|
+
user: {
|
106
|
+
uid: "123",
|
107
|
+
email: "test-user@example.com",
|
108
|
+
name: "Test User",
|
109
|
+
permissions: permissions,
|
110
|
+
},
|
111
|
+
}.to_json,
|
112
|
+
headers: { content_type: "application/json" },
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gds-sso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 18.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GOV.UK Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oauth2
|
@@ -128,26 +128,6 @@ dependencies:
|
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '3'
|
131
|
-
- !ruby/object:Gem::Dependency
|
132
|
-
name: capybara-mechanize
|
133
|
-
requirement: !ruby/object:Gem::Requirement
|
134
|
-
requirements:
|
135
|
-
- - "~>"
|
136
|
-
- !ruby/object:Gem::Version
|
137
|
-
version: '1'
|
138
|
-
- - ">="
|
139
|
-
- !ruby/object:Gem::Version
|
140
|
-
version: 1.12.1
|
141
|
-
type: :development
|
142
|
-
prerelease: false
|
143
|
-
version_requirements: !ruby/object:Gem::Requirement
|
144
|
-
requirements:
|
145
|
-
- - "~>"
|
146
|
-
- !ruby/object:Gem::Version
|
147
|
-
version: '1'
|
148
|
-
- - ">="
|
149
|
-
- !ruby/object:Gem::Version
|
150
|
-
version: 1.12.1
|
151
131
|
- !ruby/object:Gem::Dependency
|
152
132
|
name: combustion
|
153
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -194,16 +174,16 @@ dependencies:
|
|
194
174
|
name: rubocop-govuk
|
195
175
|
requirement: !ruby/object:Gem::Requirement
|
196
176
|
requirements:
|
197
|
-
- -
|
177
|
+
- - "~>"
|
198
178
|
- !ruby/object:Gem::Version
|
199
|
-
version: 4
|
179
|
+
version: '4'
|
200
180
|
type: :development
|
201
181
|
prerelease: false
|
202
182
|
version_requirements: !ruby/object:Gem::Requirement
|
203
183
|
requirements:
|
204
|
-
- -
|
184
|
+
- - "~>"
|
205
185
|
- !ruby/object:Gem::Version
|
206
|
-
version: 4
|
186
|
+
version: '4'
|
207
187
|
- !ruby/object:Gem::Dependency
|
208
188
|
name: sqlite3
|
209
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -232,6 +212,20 @@ dependencies:
|
|
232
212
|
- - "~>"
|
233
213
|
- !ruby/object:Gem::Version
|
234
214
|
version: '0.9'
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: webmock
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
type: :development
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
235
229
|
description: Client for GDS' OAuth 2-based SSO
|
236
230
|
email:
|
237
231
|
- govuk-dev@digital.cabinet-office.gov.uk
|
@@ -264,8 +258,6 @@ files:
|
|
264
258
|
- lib/omniauth/strategies/gds.rb
|
265
259
|
- spec/controller/api_user_controller_spec.rb
|
266
260
|
- spec/controller/controller_methods_spec.rb
|
267
|
-
- spec/fixtures/integration/authorize_api_users.sql
|
268
|
-
- spec/fixtures/integration/signon.sql
|
269
261
|
- spec/internal/app/assets/config/manifest.js
|
270
262
|
- spec/internal/app/controllers/application_controller.rb
|
271
263
|
- spec/internal/app/controllers/example_controller.rb
|
@@ -273,17 +265,13 @@ files:
|
|
273
265
|
- spec/internal/config/database.yml
|
274
266
|
- spec/internal/config/initializers/gds-sso.rb
|
275
267
|
- spec/internal/config/routes.rb
|
276
|
-
- spec/internal/config/storage.yml
|
277
268
|
- spec/internal/db/schema.rb
|
278
|
-
- spec/internal/public/favicon.ico
|
279
|
-
- spec/requests/end_to_end_spec.rb
|
280
269
|
- spec/spec_helper.rb
|
281
|
-
- spec/support/backport_controller_test_params.rb
|
282
270
|
- spec/support/controller_spy.rb
|
283
271
|
- spec/support/serializable_user.rb
|
284
|
-
- spec/support/signon_integration_helpers.rb
|
285
272
|
- spec/support/test_user.rb
|
286
273
|
- spec/support/timecop.rb
|
274
|
+
- spec/system/authentication_and_authorisation_spec.rb
|
287
275
|
- spec/unit/api_access_spec.rb
|
288
276
|
- spec/unit/bearer_token_spec.rb
|
289
277
|
- spec/unit/config_spec.rb
|
@@ -303,44 +291,38 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
303
291
|
requirements:
|
304
292
|
- - ">="
|
305
293
|
- !ruby/object:Gem::Version
|
306
|
-
version: '
|
294
|
+
version: '3.0'
|
307
295
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
308
296
|
requirements:
|
309
297
|
- - ">="
|
310
298
|
- !ruby/object:Gem::Version
|
311
299
|
version: '0'
|
312
300
|
requirements: []
|
313
|
-
rubygems_version: 3.
|
301
|
+
rubygems_version: 3.4.18
|
314
302
|
signing_key:
|
315
303
|
specification_version: 4
|
316
304
|
summary: Client for GDS' OAuth 2-based SSO
|
317
305
|
test_files:
|
318
|
-
- spec/spec_helper.rb
|
319
|
-
- spec/fixtures/integration/signon.sql
|
320
|
-
- spec/fixtures/integration/authorize_api_users.sql
|
321
|
-
- spec/requests/end_to_end_spec.rb
|
322
|
-
- spec/support/controller_spy.rb
|
323
|
-
- spec/support/timecop.rb
|
324
|
-
- spec/support/serializable_user.rb
|
325
|
-
- spec/support/test_user.rb
|
326
|
-
- spec/support/signon_integration_helpers.rb
|
327
|
-
- spec/support/backport_controller_test_params.rb
|
328
306
|
- spec/controller/api_user_controller_spec.rb
|
329
307
|
- spec/controller/controller_methods_spec.rb
|
330
|
-
- spec/internal/config/
|
308
|
+
- spec/internal/app/assets/config/manifest.js
|
309
|
+
- spec/internal/app/controllers/application_controller.rb
|
310
|
+
- spec/internal/app/controllers/example_controller.rb
|
311
|
+
- spec/internal/app/models/user.rb
|
331
312
|
- spec/internal/config/database.yml
|
332
313
|
- spec/internal/config/initializers/gds-sso.rb
|
333
|
-
- spec/internal/config/
|
334
|
-
- spec/internal/app/models/user.rb
|
335
|
-
- spec/internal/app/controllers/example_controller.rb
|
336
|
-
- spec/internal/app/controllers/application_controller.rb
|
337
|
-
- spec/internal/app/assets/config/manifest.js
|
314
|
+
- spec/internal/config/routes.rb
|
338
315
|
- spec/internal/db/schema.rb
|
339
|
-
- spec/
|
340
|
-
- spec/
|
341
|
-
- spec/
|
316
|
+
- spec/spec_helper.rb
|
317
|
+
- spec/support/controller_spy.rb
|
318
|
+
- spec/support/serializable_user.rb
|
319
|
+
- spec/support/test_user.rb
|
320
|
+
- spec/support/timecop.rb
|
321
|
+
- spec/system/authentication_and_authorisation_spec.rb
|
342
322
|
- spec/unit/api_access_spec.rb
|
343
|
-
- spec/unit/
|
344
|
-
- spec/unit/session_serialisation_spec.rb
|
323
|
+
- spec/unit/bearer_token_spec.rb
|
345
324
|
- spec/unit/config_spec.rb
|
325
|
+
- spec/unit/mock_bearer_token_spec.rb
|
346
326
|
- spec/unit/railtie_spec.rb
|
327
|
+
- spec/unit/session_serialisation_spec.rb
|
328
|
+
- spec/unit/user_spec.rb
|
@@ -1,7 +0,0 @@
|
|
1
|
-
DELETE FROM `oauth_access_tokens`;
|
2
|
-
|
3
|
-
INSERT INTO oauth_access_tokens (resource_owner_id, application_id, token, refresh_token, expires_in, created_at)
|
4
|
-
VALUES (1, 1, 'caaeb53be5c7277fb0ef158181bfd1537b57f9e3b83eb795be3cd0af6e118b28', '1bc343797483954d7306d67e96687feccdfdaa8b23ed662ae23e2b03e6661d16', POW(2, 31)-1, '2012-06-27 13:57:47');
|
5
|
-
|
6
|
-
INSERT INTO oauth_access_tokens (resource_owner_id, application_id, token, refresh_token, expires_in, created_at)
|
7
|
-
VALUES (1, 2, '98c72f4da02fdc43398e029d05567542944d2a9b0df3c20b0accd8bd6c5dc728', 'e2da0489a58219fd4f542139909737627874ceacd2af23f5c268ccecb36e85af', POW(2, 31)-1, '2014-07-14 09:06:14');
|
@@ -1,26 +0,0 @@
|
|
1
|
-
-- Clean data from database
|
2
|
-
DELETE FROM `oauth_access_grants`;
|
3
|
-
DELETE FROM `oauth_access_tokens`;
|
4
|
-
DELETE FROM `oauth_applications`;
|
5
|
-
DELETE FROM `supported_permissions`;
|
6
|
-
DELETE FROM `users`;
|
7
|
-
DELETE FROM `user_application_permissions`;
|
8
|
-
|
9
|
-
-- Setup fixture data
|
10
|
-
INSERT INTO `oauth_applications` (id, name, uid, secret, redirect_uri, created_at, updated_at, home_uri, description)
|
11
|
-
VALUES (1,'GDS_SSO integration test','gds-sso-test','secret','http://www.example-client.com/auth/gds/callback','2012-04-19 13:26:54','2012-04-19 13:26:54', 'http://home.com', 'GDS_SSO integration test');
|
12
|
-
INSERT INTO `oauth_applications` (id, name, uid, secret, redirect_uri, created_at, updated_at, home_uri, description)
|
13
|
-
VALUES (2,'A different appilcation','application-2','different secret','http://www.example-client2.com/auth/gds/callback','2014-07-14-09:07:32','2014-07-14-09:07:32', 'http://www.example-client2.com', '');
|
14
|
-
|
15
|
-
INSERT INTO `supported_permissions` (id, application_id, name, created_at, updated_at)
|
16
|
-
VALUES (1,1,'signin','2012-04-19 13:26:54','2012-04-19 13:26:54');
|
17
|
-
INSERT INTO `supported_permissions` (id, application_id, name, created_at, updated_at)
|
18
|
-
VALUES (2,2,'signin','2012-04-19 13:26:54','2012-04-19 13:26:54');
|
19
|
-
|
20
|
-
INSERT INTO `users` (id, email, encrypted_password, password_salt, created_at, updated_at, confirmed_at, name, uid, role, password_changed_at)
|
21
|
-
VALUES (1,'test@example-client.com','3a890fe5e95b328b83f2ba57ea893cae595f4937291ff5550acb68f4a8dafeac22e5f8120c1e66be8f2b769df142dd3d111b404c5c1741595c9ecc9e7e6ad827','QCsZsJs7m5ojdgvysHSy','2012-04-19 13:26:54','2012-04-19 13:26:54','2012-04-19 13:26:54','Test User','integration-uid', "normal", NOW());
|
22
|
-
|
23
|
-
INSERT INTO `user_application_permissions` (user_id, application_id, supported_permission_id, created_at, updated_at)
|
24
|
-
VALUES (1,1,1,'2012-04-19 13:26:54','2012-04-19 13:26:54');
|
25
|
-
INSERT INTO `user_application_permissions` (user_id, application_id, supported_permission_id, created_at, updated_at)
|
26
|
-
VALUES (1,2,2,'2012-04-19 13:26:54','2012-04-19 13:26:54');
|
File without changes
|
@@ -1,215 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
require "timecop"
|
3
|
-
|
4
|
-
describe "Integration of client using GDS-SSO with signon" do
|
5
|
-
include SignonIntegrationHelpers
|
6
|
-
|
7
|
-
before :all do
|
8
|
-
wait_for_signon_to_start
|
9
|
-
end
|
10
|
-
|
11
|
-
before :each do
|
12
|
-
# points to an internal app, using combustion gem
|
13
|
-
# see spec/internal
|
14
|
-
@client_host = "www.example-client.com"
|
15
|
-
Capybara.current_driver = :mechanize
|
16
|
-
Capybara::Mechanize.local_hosts << @client_host
|
17
|
-
|
18
|
-
load_signon_setup_fixture
|
19
|
-
end
|
20
|
-
|
21
|
-
describe "Web client accesses" do
|
22
|
-
before :each do
|
23
|
-
page.driver.header "accept", "text/html"
|
24
|
-
end
|
25
|
-
|
26
|
-
specify "a non-restricted page can be accessed without authentication" do
|
27
|
-
visit "http://#{@client_host}/"
|
28
|
-
expect(page).to have_content("jabberwocky")
|
29
|
-
end
|
30
|
-
|
31
|
-
specify "first access to a restricted page requires authentication and application approval" do
|
32
|
-
visit "http://#{@client_host}/restricted"
|
33
|
-
expect(page).to have_content("Sign in")
|
34
|
-
fill_in "Email", with: "test@example-client.com"
|
35
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
36
|
-
click_on "Sign in"
|
37
|
-
|
38
|
-
expect(page).to have_content("restricted kablooie")
|
39
|
-
end
|
40
|
-
|
41
|
-
specify "access to a restricted page for an approved application requires only authentication" do
|
42
|
-
# First we login to authorise the app
|
43
|
-
visit "http://#{@client_host}/restricted"
|
44
|
-
fill_in "Email", with: "test@example-client.com"
|
45
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
46
|
-
click_on "Sign in"
|
47
|
-
|
48
|
-
# At this point the app should be authorised, we reset the session to simulate a new browser visit.
|
49
|
-
reset_session!
|
50
|
-
page.driver.header "accept", "text/html"
|
51
|
-
|
52
|
-
visit "http://#{@client_host}/restricted"
|
53
|
-
expect(page).to have_content("Sign in")
|
54
|
-
|
55
|
-
fill_in "Email", with: "test@example-client.com"
|
56
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
57
|
-
click_on "Sign in"
|
58
|
-
|
59
|
-
expect(page).to have_content("restricted kablooie")
|
60
|
-
end
|
61
|
-
|
62
|
-
specify "access to a page that requires signin permission granted" do
|
63
|
-
# First we login to authorise the app
|
64
|
-
visit "http://#{@client_host}/this_requires_signin_permission"
|
65
|
-
fill_in "Email", with: "test@example-client.com"
|
66
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
67
|
-
click_on "Sign in"
|
68
|
-
|
69
|
-
# At this point the app should be authorised, we reset the session to simulate a new browser visit.
|
70
|
-
reset_session!
|
71
|
-
page.driver.header "accept", "text/html"
|
72
|
-
|
73
|
-
visit "http://#{@client_host}/this_requires_signin_permission"
|
74
|
-
expect(page).to have_content("Sign in")
|
75
|
-
|
76
|
-
fill_in "Email", with: "test@example-client.com"
|
77
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
78
|
-
click_on "Sign in"
|
79
|
-
|
80
|
-
expect(page).to have_content("you have signin permission")
|
81
|
-
end
|
82
|
-
|
83
|
-
describe "remotely signed out" do
|
84
|
-
specify "should prevent all access to the application until successful signin" do
|
85
|
-
# First we login and authorise the app
|
86
|
-
visit "http://#{@client_host}/restricted"
|
87
|
-
fill_in "Email", with: "test@example-client.com"
|
88
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
89
|
-
click_on "Sign in"
|
90
|
-
|
91
|
-
page.driver.header "accept", "text/html"
|
92
|
-
expect(page).to have_content("restricted kablooie")
|
93
|
-
|
94
|
-
# logout from signon
|
95
|
-
visit "http://localhost:4567/users/sign_out"
|
96
|
-
|
97
|
-
# Simulate a POST to /auth/gds/api/users/:uid/reauth by signon
|
98
|
-
# This is already tested in api_user_controller_spec.rb
|
99
|
-
user = User.where(email: "test@example-client.com").first
|
100
|
-
user.set_remotely_signed_out!
|
101
|
-
|
102
|
-
# attempt to visit a restricted page
|
103
|
-
visit "http://#{@client_host}/restricted"
|
104
|
-
|
105
|
-
# be redirected to signon
|
106
|
-
expect(page).to have_content("GOV.UK Signon")
|
107
|
-
fill_in "Email", with: "test@example-client.com"
|
108
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
109
|
-
click_on "Sign in"
|
110
|
-
|
111
|
-
# then back again to the restricted page
|
112
|
-
expect(page).to have_content("restricted kablooie")
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
describe "session expiry" do
|
117
|
-
it "should force you to re-authenticate with signon N hours after login" do
|
118
|
-
visit "http://#{@client_host}/restricted"
|
119
|
-
expect(page).to have_content("Sign in")
|
120
|
-
fill_in "Email", with: "test@example-client.com"
|
121
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
122
|
-
click_on "Sign in"
|
123
|
-
|
124
|
-
expect(page).to have_content("restricted kablooie")
|
125
|
-
|
126
|
-
visit "http://localhost:4567/users/sign_out"
|
127
|
-
|
128
|
-
Timecop.travel(Time.now.utc + GDS::SSO::Config.auth_valid_for + 5.minutes) do
|
129
|
-
visit "http://#{@client_host}/restricted"
|
130
|
-
end
|
131
|
-
|
132
|
-
expect(page).to have_content("Sign in")
|
133
|
-
end
|
134
|
-
|
135
|
-
it "should accept signon's remembered authentication N hours after login" do
|
136
|
-
visit "http://#{@client_host}/restricted"
|
137
|
-
expect(page).to have_content("Sign in")
|
138
|
-
fill_in "Email", with: "test@example-client.com"
|
139
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
140
|
-
click_on "Sign in"
|
141
|
-
|
142
|
-
expect(page).to have_content("restricted kablooie")
|
143
|
-
|
144
|
-
Timecop.travel(Time.now.utc + GDS::SSO::Config.auth_valid_for + 5.minutes) do
|
145
|
-
visit "http://#{@client_host}/restricted"
|
146
|
-
end
|
147
|
-
|
148
|
-
expect(page).to have_content("restricted kablooie")
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should not require re-authentication with signon fewer than N hours after login" do
|
152
|
-
visit "http://#{@client_host}/restricted"
|
153
|
-
expect(page).to have_content("Sign in")
|
154
|
-
fill_in "Email", with: "test@example-client.com"
|
155
|
-
fill_in "Password", with: "q1w2e3r4t5y6u7i8o9p0"
|
156
|
-
click_on "Sign in"
|
157
|
-
|
158
|
-
expect(page).to have_content("restricted kablooie")
|
159
|
-
|
160
|
-
Timecop.travel(Time.now.utc + GDS::SSO::Config.auth_valid_for - 5.minutes) do
|
161
|
-
visit "http://#{@client_host}/restricted"
|
162
|
-
end
|
163
|
-
|
164
|
-
expect(page).to have_content("restricted kablooie")
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
describe "OAuth based API client accesses" do
|
170
|
-
before :each do
|
171
|
-
page.driver.header "accept", "application/json"
|
172
|
-
authorize_signon_api_user
|
173
|
-
|
174
|
-
token = "caaeb53be5c7277fb0ef158181bfd1537b57f9e3b83eb795be3cd0af6e118b28"
|
175
|
-
page.driver.header "authorization", "Bearer #{token}"
|
176
|
-
end
|
177
|
-
|
178
|
-
specify "access to a restricted page for an api client requires auth" do
|
179
|
-
page.driver.header "authorization", "Bearer Bad Token"
|
180
|
-
visit "http://#{@client_host}/restricted"
|
181
|
-
expect(page.driver.response.status).to eq(401)
|
182
|
-
end
|
183
|
-
|
184
|
-
specify "setting a correct bearer token allows sign in" do
|
185
|
-
visit "http://#{@client_host}/restricted"
|
186
|
-
expect(page).to have_content("restricted kablooie")
|
187
|
-
end
|
188
|
-
|
189
|
-
specify "setting a correct bearer token picks up permissions" do
|
190
|
-
visit "http://#{@client_host}/this_requires_signin_permission"
|
191
|
-
expect(page).to have_content("you have signin permission")
|
192
|
-
end
|
193
|
-
|
194
|
-
specify "a token for one app cannot be used to access a different app" do
|
195
|
-
page.driver.header "authorization", "Bearer 98c72f4da02fdc43398e029d05567542944d2a9b0df3c20b0accd8bd6c5dc728"
|
196
|
-
visit "http://#{@client_host}/restricted"
|
197
|
-
expect(page.driver.response.status).to eq(401)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
context "when in api_only mode" do
|
202
|
-
around do |examples|
|
203
|
-
GDS::SSO::Config.api_only = true
|
204
|
-
Combustion::Application.reload_routes!
|
205
|
-
examples.run
|
206
|
-
GDS::SSO::Config.api_only = false
|
207
|
-
Combustion::Application.reload_routes!
|
208
|
-
end
|
209
|
-
|
210
|
-
specify "accessing without a bearer token is not authorized" do
|
211
|
-
visit "http://#{@client_host}/restricted"
|
212
|
-
expect(page.driver.response.status).to eq(401)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module BackportControllerTestParams
|
2
|
-
def delete(*args)
|
3
|
-
action, rest = *args
|
4
|
-
super(action, rest[:params])
|
5
|
-
end
|
6
|
-
|
7
|
-
def get(*args)
|
8
|
-
action, rest = *args
|
9
|
-
super(action, rest[:params])
|
10
|
-
end
|
11
|
-
|
12
|
-
def post(*args)
|
13
|
-
action, rest = *args
|
14
|
-
super(action, rest[:params])
|
15
|
-
end
|
16
|
-
|
17
|
-
def put(*args)
|
18
|
-
action, rest = *args
|
19
|
-
super(action, rest[:params])
|
20
|
-
end
|
21
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require "net/http"
|
2
|
-
|
3
|
-
module SignonIntegrationHelpers
|
4
|
-
def wait_for_signon_to_start
|
5
|
-
retries = 0
|
6
|
-
url = GDS::SSO::Config.oauth_root_url
|
7
|
-
puts "Waiting for signon to start at #{url}"
|
8
|
-
until signon_started?(url)
|
9
|
-
print "."
|
10
|
-
if retries > 20
|
11
|
-
raise "Signon is not running at #{url}. Please start with `./start_signon.sh`. Under jenkins this should happen automatically."
|
12
|
-
end
|
13
|
-
|
14
|
-
retries += 1
|
15
|
-
sleep 1
|
16
|
-
end
|
17
|
-
puts "Signon is now running at #{url}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def signon_started?(url)
|
21
|
-
uri = URI.parse(url)
|
22
|
-
conn = Net::HTTP.start(uri.host, uri.port)
|
23
|
-
true
|
24
|
-
rescue Errno::ECONNREFUSED
|
25
|
-
false
|
26
|
-
ensure
|
27
|
-
conn.try(:finish)
|
28
|
-
end
|
29
|
-
|
30
|
-
def load_signon_setup_fixture
|
31
|
-
load_signon_fixture("signon.sql")
|
32
|
-
end
|
33
|
-
|
34
|
-
def authorize_signon_api_user
|
35
|
-
load_signon_fixture("authorize_api_users.sql")
|
36
|
-
end
|
37
|
-
|
38
|
-
def load_signon_fixture(filename)
|
39
|
-
require "erb"
|
40
|
-
parsed = ERB.new(File.read("#{signon_path}/config/database.yml")).result
|
41
|
-
db = YAML.safe_load(parsed, aliases: true)["test"]
|
42
|
-
|
43
|
-
cmd = "mysql #{db['database']} -u#{db['username']} -p#{db['password']} < #{fixture_file(filename)}"
|
44
|
-
system cmd or raise "Error loading signon fixture"
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def fixture_file(filename)
|
50
|
-
File.join(File.dirname(__FILE__), "../fixtures/integration", filename)
|
51
|
-
end
|
52
|
-
|
53
|
-
def signon_path
|
54
|
-
Rails.root.join("..", "..", "tmp", "signon").to_s
|
55
|
-
end
|
56
|
-
end
|