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.
- checksums.yaml +4 -4
- data/README.md +14 -1
- data/config/routes.rb +4 -3
- data/lib/gds-sso/api_access.rb +27 -1
- data/lib/gds-sso/authorised_user_constraint.rb +2 -3
- data/lib/gds-sso/bearer_token.rb +6 -1
- data/lib/gds-sso/config.rb +7 -0
- data/lib/gds-sso/controller_methods.rb +1 -9
- data/lib/gds-sso/failure_app.rb +6 -7
- data/lib/gds-sso/version.rb +1 -1
- data/lib/gds-sso/warden_config.rb +30 -3
- data/lib/gds-sso.rb +6 -0
- 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 +80 -8
- data/spec/unit/authorised_user_constraint_spec.rb +8 -31
- data/spec/unit/bearer_token_spec.rb +8 -0
- data/spec/unit/gds_sso_spec.rb +37 -0
- data/spec/unit/mock_bearer_token_spec.rb +55 -16
- data/spec/unit/session_serialisation_spec.rb +5 -4
- metadata +34 -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 -172
- /data/spec/{controller → unit}/controller_methods_spec.rb +0 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Integration tests with a demo app", type: :request do
|
4
|
+
describe "accessing a route that doesn't require authentication" do
|
5
|
+
it "allows access" do
|
6
|
+
get "/not-restricted"
|
7
|
+
expect(response).to have_http_status(:success)
|
8
|
+
expect(response.body).to eq("jabberwocky")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "accessing a route the requires authentication" do
|
13
|
+
context "when GDS::SSO isn't configured to treat this as an explicit API request" do
|
14
|
+
it "redirects an unauthenticated request to sign-in" do
|
15
|
+
get "/restricted"
|
16
|
+
expect(response).to redirect_to("/auth/gds")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "responds successfully for an authenticated user" do
|
20
|
+
authenticate_with_stub_signon
|
21
|
+
|
22
|
+
get "/restricted"
|
23
|
+
expect(response).to have_http_status(:success)
|
24
|
+
expect(response.body).to eq("restricted kablooie")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "redirects to sign-in if a user is authenticated but remotely signed out" do
|
28
|
+
authenticate_with_stub_signon
|
29
|
+
User.last.set_remotely_signed_out!
|
30
|
+
|
31
|
+
get "/restricted"
|
32
|
+
expect(response).to redirect_to("/auth/gds")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "redirects to sign-in if a user's session has expired" do
|
36
|
+
authenticate_with_stub_signon
|
37
|
+
|
38
|
+
travel_to(Time.now.utc + GDS::SSO::Config.auth_valid_for + 1.second) do
|
39
|
+
get "/restricted"
|
40
|
+
expect(response).to redirect_to("/auth/gds")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "allows access when given a valid bearer token" do
|
45
|
+
stub_successful_signon_user_request
|
46
|
+
|
47
|
+
get "/restricted", headers: { "Authorization" => "Bearer 123" }
|
48
|
+
expect(response).to have_http_status(:success)
|
49
|
+
expect(response.body).to eq("restricted kablooie")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "restricts access when given an invalid bearer token" do
|
53
|
+
stub_failed_signon_user_request
|
54
|
+
|
55
|
+
get "/restricted", headers: { "Authorization" => "Bearer invalid" }
|
56
|
+
expect(response).to have_http_status(:unauthorized)
|
57
|
+
expect_invalid_bearer_token_response(response)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when an application is configured as API only" do
|
62
|
+
before { allow(GDS::SSO::Config).to receive(:api_only).and_return(true) }
|
63
|
+
|
64
|
+
it "allows access when given a valid bearer token" do
|
65
|
+
stub_successful_signon_user_request
|
66
|
+
|
67
|
+
get "/restricted", headers: { "Authorization" => "Bearer 123" }
|
68
|
+
expect(response).to have_http_status(:success)
|
69
|
+
expect(response.body).to eq("restricted kablooie")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "rejects a request without a bearer token" do
|
73
|
+
get "/restricted"
|
74
|
+
expect(response).to have_http_status(:unauthorized)
|
75
|
+
expect_missing_bearer_token_response(response)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "restricts access for an invalid bearer token" do
|
79
|
+
stub_failed_signon_user_request
|
80
|
+
|
81
|
+
get "/restricted", headers: { "Authorization" => "Bearer invalid" }
|
82
|
+
expect(response).to have_http_status(:unauthorized)
|
83
|
+
expect_invalid_bearer_token_response(response)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when API requests are differentiated by api_request_matcher" do
|
88
|
+
it "treats a match as an API request" do
|
89
|
+
allow(GDS::SSO::Config)
|
90
|
+
.to receive(:api_request_matcher)
|
91
|
+
.and_return(->(request) { request.path == "/restricted" })
|
92
|
+
|
93
|
+
get "/restricted"
|
94
|
+
expect(response).to have_http_status(:unauthorized)
|
95
|
+
expect_missing_bearer_token_response(response)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "treats a non-match as a non-API request" do
|
99
|
+
allow(GDS::SSO::Config)
|
100
|
+
.to receive(:api_request_matcher)
|
101
|
+
.and_return(->(_request) { false })
|
102
|
+
|
103
|
+
get "/restricted"
|
104
|
+
expect(response).to redirect_to("/auth/gds")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "when accessing routes without authentication and using the mock strategies" do
|
110
|
+
before { use_mock_strategies }
|
111
|
+
|
112
|
+
it "allows a user access without authentication" do
|
113
|
+
# non-bearer token mock requests require a user to exist
|
114
|
+
User.create!(
|
115
|
+
uid: SecureRandom.uuid,
|
116
|
+
email: "user@example.com",
|
117
|
+
name: "Example User",
|
118
|
+
permissions: [],
|
119
|
+
)
|
120
|
+
|
121
|
+
get "/restricted"
|
122
|
+
expect(response).to have_http_status(:success)
|
123
|
+
expect(response.body).to eq("restricted kablooie")
|
124
|
+
end
|
125
|
+
|
126
|
+
it "can be configured to fail authentication with an env var" do
|
127
|
+
ClimateControl.modify("GDS_SSO_MOCK_INVALID" => "1") do
|
128
|
+
get "/restricted"
|
129
|
+
expect(response).to redirect_to("/auth/gds")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "allows an API request without a bearer token" do
|
134
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
135
|
+
|
136
|
+
get "/restricted"
|
137
|
+
expect(response).to have_http_status(:success)
|
138
|
+
expect(response.body).to eq("restricted kablooie")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "can be configured to fail API authentication with an env var" do
|
142
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
143
|
+
|
144
|
+
ClimateControl.modify("GDS_SSO_MOCK_INVALID" => "1") do
|
145
|
+
get "/restricted"
|
146
|
+
expect(response).to have_http_status(:unauthorized)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "when accessing a route that requires permission" do
|
152
|
+
context "when GDS::SSO isn't configured to treat this as an explicit API request" do
|
153
|
+
it "allows a user with the permission to access the resource" do
|
154
|
+
authenticate_with_stub_signon(permissions: %w[execute])
|
155
|
+
|
156
|
+
get "/this-requires-execute-permission"
|
157
|
+
expect(response).to have_http_status(:success)
|
158
|
+
expect(response.body).to eq("you have execute permission")
|
159
|
+
end
|
160
|
+
|
161
|
+
it "restricts a user lacking the permission" do
|
162
|
+
authenticate_with_stub_signon
|
163
|
+
|
164
|
+
get "/this-requires-execute-permission"
|
165
|
+
expect(response).to have_http_status(:forbidden)
|
166
|
+
expect(response.body).to include("Sorry, you don't seem to have the execute permission for this app.")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when GDS::SSO is configured to treat the request as an API request" do
|
171
|
+
before { allow(GDS::SSO::Config).to receive(:api_only).and_return(true) }
|
172
|
+
|
173
|
+
it "allows a user with the permission to access the resource" do
|
174
|
+
stub_successful_signon_user_request(permissions: %w[execute])
|
175
|
+
|
176
|
+
get "/this-requires-execute-permission", headers: { "Authorization" => "Bearer 123" }
|
177
|
+
expect(response).to have_http_status(:success)
|
178
|
+
expect(response.body).to eq("you have execute permission")
|
179
|
+
end
|
180
|
+
|
181
|
+
it "restricts a user lacking the permission" do
|
182
|
+
stub_successful_signon_user_request
|
183
|
+
|
184
|
+
get "/this-requires-execute-permission", headers: { "Authorization" => "Bearer 123" }
|
185
|
+
expect(response).to have_http_status(:forbidden)
|
186
|
+
expect_json_response(response, { "message" => "Sorry, you don't seem to have the execute permission for this app." })
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "when using bearer token for auth with the mock strategy" do
|
191
|
+
before { use_mock_strategies }
|
192
|
+
|
193
|
+
it "automatically grants permissions configured in GDS:SSO::Config.additional_mock_permissions_required" do
|
194
|
+
allow(GDS::SSO::Config).to receive(:additional_mock_permissions_required).and_return(%w[execute])
|
195
|
+
|
196
|
+
stub_successful_signon_user_request
|
197
|
+
|
198
|
+
get "/this-requires-execute-permission", headers: { "Authorization" => "Bearer 123" }
|
199
|
+
expect(response).to have_http_status(:success)
|
200
|
+
expect(response.body).to eq("you have execute permission")
|
201
|
+
end
|
202
|
+
|
203
|
+
it "doesn't grant access without that config" do
|
204
|
+
allow(GDS::SSO::Config).to receive(:additional_mock_permissions_required).and_return(nil)
|
205
|
+
|
206
|
+
stub_successful_signon_user_request
|
207
|
+
|
208
|
+
get "/this-requires-execute-permission", headers: { "Authorization" => "Bearer 123" }
|
209
|
+
expect(response).to have_http_status(:forbidden)
|
210
|
+
expect_json_response(response, { "message" => "Sorry, you don't seem to have the execute permission for this app." })
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when accessing a route that is restricted by the authorised user constraint" do
|
216
|
+
it "allows access when an authenticated user has correct permissions" do
|
217
|
+
authenticate_with_stub_signon(permissions: %w[constraint])
|
218
|
+
|
219
|
+
get "/constraint-restricted"
|
220
|
+
expect(response).to have_http_status(:success)
|
221
|
+
expect(response.body).to eq("constraint restricted")
|
222
|
+
end
|
223
|
+
|
224
|
+
it "redirects an unauthenticated request to signon" do
|
225
|
+
get "/constraint-restricted"
|
226
|
+
|
227
|
+
expect(response).to redirect_to("/auth/gds")
|
228
|
+
end
|
229
|
+
|
230
|
+
it "restricts access when an authenticated user does not have the correct permissions" do
|
231
|
+
authenticate_with_stub_signon
|
232
|
+
|
233
|
+
get "/constraint-restricted"
|
234
|
+
expect(response).to have_http_status(:forbidden)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def expect_missing_bearer_token_response(response)
|
239
|
+
expect(response.headers).to include("WWW-Authenticate" => 'Bearer error="invalid_request"')
|
240
|
+
expect_json_response(response, { "message" => "No bearer token was provided" })
|
241
|
+
end
|
242
|
+
|
243
|
+
def expect_invalid_bearer_token_response(response)
|
244
|
+
expect(response.headers).to include("WWW-Authenticate" => 'Bearer error="invalid_token"')
|
245
|
+
expect_json_response(response, { "message" => "Bearer token does not appear to be valid" })
|
246
|
+
end
|
247
|
+
|
248
|
+
def expect_json_response(response, json)
|
249
|
+
expect(response.media_type).to eq("application/json")
|
250
|
+
expect(response.parsed_body).to eq(json)
|
251
|
+
end
|
252
|
+
|
253
|
+
def use_mock_strategies
|
254
|
+
# Using allow_any_instance_of because it's hard to access the instance
|
255
|
+
# of the class used within the Rails middleware
|
256
|
+
allow_any_instance_of(Warden::Config).to receive(:[]).and_call_original
|
257
|
+
allow_any_instance_of(Warden::Config)
|
258
|
+
.to receive(:[])
|
259
|
+
.with(:default_strategies)
|
260
|
+
.and_return({ _all: %i[mock_gds_sso gds_bearer_token] })
|
261
|
+
|
262
|
+
allow(Warden::OAuth2.config).to receive(:token_model).and_return(GDS::SSO::MockBearerToken)
|
263
|
+
end
|
264
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Bad things happen if we don't ;-)
|
3
3
|
ENV["GDS_SSO_STRATEGY"] = "real"
|
4
4
|
|
5
|
-
require "
|
5
|
+
require "climate_control"
|
6
6
|
require "webmock/rspec"
|
7
7
|
require "combustion"
|
8
8
|
|
@@ -12,15 +12,10 @@ Combustion.initialize! :all do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
require "rspec/rails"
|
15
|
-
require "capybara/rails"
|
16
15
|
WebMock.disable_net_connect!
|
17
16
|
|
18
17
|
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].sort.each { |f| require f }
|
19
18
|
|
20
|
-
Capybara.register_driver :rack_test do |app|
|
21
|
-
Capybara::RackTest::Driver.new(app, follow_redirects: false)
|
22
|
-
end
|
23
|
-
|
24
19
|
RSpec.configure do |config|
|
25
20
|
config.run_all_when_everything_filtered = true
|
26
21
|
config.filter_run :focus
|
@@ -32,6 +27,20 @@ RSpec.configure do |config|
|
|
32
27
|
# --seed 1234
|
33
28
|
config.order = "random"
|
34
29
|
|
30
|
+
config.include ActiveSupport::Testing::TimeHelpers
|
35
31
|
config.include Warden::Test::Helpers
|
36
|
-
config.include
|
32
|
+
config.include RequestHelpers, type: :request
|
33
|
+
config.before(:each, type: :request) do
|
34
|
+
# we reload routes each test as GDS::SSO::Config affects what routes are
|
35
|
+
# available, we only want to run this once routes are loaded otherwise
|
36
|
+
# we can lose app routes
|
37
|
+
routes_reloader = Rails.application.routes_reloader
|
38
|
+
|
39
|
+
# Routes changed in Rails 8 to be lazily loaded so this wasn't a problem
|
40
|
+
# before Rails 8.
|
41
|
+
# TODO: remove this line once Rails 7 support is removed
|
42
|
+
next unless routes_reloader.respond_to?(:loaded)
|
43
|
+
|
44
|
+
routes_reloader.reload! if routes_reloader.loaded
|
45
|
+
end
|
37
46
|
end
|
@@ -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
|
@@ -1,17 +1,89 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "gds-sso/api_access"
|
3
|
+
require "rack/mock_request"
|
3
4
|
|
4
5
|
describe GDS::SSO::ApiAccess do
|
5
|
-
|
6
|
-
|
7
|
-
"
|
8
|
-
|
9
|
-
|
6
|
+
describe ".api_call?" do
|
7
|
+
it "returns true if the rack env has already been tagged as an api_call" do
|
8
|
+
expect(described_class.api_call?({ "gds_sso.api_call" => true })).to be(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns false if the rack env has already been tagged as not an api_call" do
|
12
|
+
expect(described_class.api_call?({ "gds_sso.api_call" => false })).to be(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns true if GDS::SSO has been configured as api_only" do
|
16
|
+
allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
|
17
|
+
|
18
|
+
expect(described_class.api_call?({})).to be(true)
|
19
|
+
end
|
20
|
+
|
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
|
27
|
+
|
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
|
32
|
+
|
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
|
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" })
|
42
|
+
|
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
|
53
|
+
end
|
54
|
+
|
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
|
64
|
+
end
|
10
65
|
end
|
11
66
|
|
12
|
-
|
13
|
-
it "
|
14
|
-
|
67
|
+
describe ".bearer_token" do
|
68
|
+
it "returns a bearer token set in a HTTP_AUTHORIZATION header" do
|
69
|
+
env = { "HTTP_AUTHORIZATION" => "Bearer 1234:5678" }
|
70
|
+
expect(described_class.bearer_token(env)).to eq("1234:5678")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns nil for an empty bearer token in the HTTP_AUTHORIZATION header" do
|
74
|
+
env = { "HTTP_AUTHORIZATION" => "Bearer " }
|
75
|
+
expect(described_class.bearer_token(env)).to be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it "supports all the authorization headers configured in Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS" do
|
79
|
+
Rack::Auth::AbstractRequest::AUTHORIZATION_KEYS.each do |header|
|
80
|
+
env = { header => "Bearer 1234" }
|
81
|
+
expect(described_class.bearer_token(env)).to eq("1234")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns nil if a bearer token isn't set" do
|
86
|
+
expect(described_class.bearer_token({})).to be_nil
|
15
87
|
end
|
16
88
|
end
|
17
89
|
end
|
@@ -3,48 +3,25 @@ require "gds-sso/authorised_user_constraint"
|
|
3
3
|
|
4
4
|
describe GDS::SSO::AuthorisedUserConstraint do
|
5
5
|
before do
|
6
|
+
allow(GDS::SSO).to receive(:authenticate_user!).and_return(user)
|
6
7
|
allow(GDS::SSO::AuthoriseUser).to receive(:call).and_return(true)
|
7
8
|
end
|
8
9
|
|
9
10
|
describe "#matches?" do
|
10
|
-
let(:user) {
|
11
|
-
let(:warden)
|
12
|
-
double(
|
13
|
-
"warden",
|
14
|
-
authenticated?: user_authenticated,
|
15
|
-
user:,
|
16
|
-
authenticate!: nil,
|
17
|
-
)
|
18
|
-
end
|
19
|
-
let(:user_authenticated) { true }
|
20
|
-
let(:remotely_signed_out) { false }
|
11
|
+
let(:user) { TestUser.new }
|
12
|
+
let(:warden) { instance_double("Warden::Proxy") }
|
21
13
|
let(:request) { double("request", env: { "warden" => warden }) }
|
22
14
|
|
23
|
-
it "
|
24
|
-
expect(GDS::SSO
|
25
|
-
expect(warden).not_to receive(:authenticate!)
|
15
|
+
it "authenticates the user" do
|
16
|
+
expect(GDS::SSO).to receive(:authenticate_user!).with(warden)
|
26
17
|
|
27
18
|
described_class.new(%w[signin]).matches?(request)
|
28
19
|
end
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
it "authenticates the user" do
|
34
|
-
expect(warden).to receive(:authenticate!)
|
35
|
-
|
36
|
-
described_class.new(%w[signin]).matches?(request)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "when the user is remotely signed out" do
|
41
|
-
let(:remotely_signed_out) { true }
|
42
|
-
|
43
|
-
it "authenticates the user" do
|
44
|
-
expect(warden).to receive(:authenticate!)
|
21
|
+
it "authorises the user" do
|
22
|
+
expect(GDS::SSO::AuthoriseUser).to receive(:call).with(user, %w[signin])
|
45
23
|
|
46
|
-
|
47
|
-
end
|
24
|
+
described_class.new(%w[signin]).matches?(request)
|
48
25
|
end
|
49
26
|
end
|
50
27
|
end
|
@@ -25,5 +25,13 @@ describe GDS::SSO::BearerToken do
|
|
25
25
|
|
26
26
|
expect(same_user_again.id).to eql(created_user.id)
|
27
27
|
end
|
28
|
+
|
29
|
+
it "returns nil for a nil token string" do
|
30
|
+
expect(described_class.locate(nil)).to be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns nil for an empty token string" do
|
34
|
+
expect(described_class.locate("")).to be_nil
|
35
|
+
end
|
28
36
|
end
|
29
37
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GDS::SSO do
|
4
|
+
describe "#authenticate_user!" do
|
5
|
+
let(:user) { TestUser.new }
|
6
|
+
let(:warden) do
|
7
|
+
instance_double("Warden::Proxy",
|
8
|
+
authenticate!: true,
|
9
|
+
authenticated?: false,
|
10
|
+
user:)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when a user is not already authenticated" do
|
14
|
+
it "authenticates the user and returns the user object" do
|
15
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
16
|
+
expect(warden).to have_received(:authenticate!)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when a user is already authenticated and not remotely signed out" do
|
21
|
+
it "doesn't reauthenticate the user" do
|
22
|
+
allow(warden).to receive(:authenticated?).and_return(true)
|
23
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
24
|
+
|
25
|
+
expect(warden).not_to have_received(:authenticate!)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when a user is already authenticated and remotely signed out" do
|
30
|
+
it "authenticates the user again" do
|
31
|
+
user.remotely_signed_out = true
|
32
|
+
expect(described_class.authenticate_user!(warden)).to be(user)
|
33
|
+
expect(warden).to have_received(:authenticate!)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,21 +2,60 @@ require "spec_helper"
|
|
2
2
|
require "gds-sso/bearer_token"
|
3
3
|
|
4
4
|
describe GDS::SSO::MockBearerToken do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
5
|
+
describe ".locate" do
|
6
|
+
it "returns a GDS::SSO.test_user if one is set" do
|
7
|
+
test_user = TestUser.new
|
8
|
+
allow(GDS::SSO).to receive(:test_user).and_return(test_user)
|
9
|
+
|
10
|
+
expect(described_class.locate("anything")).to be(test_user)
|
11
|
+
end
|
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
|
+
|
19
|
+
it "doesn't modify the permissions of GDS::SSO.test_user" do
|
20
|
+
test_user = TestUser.new(permissions: [])
|
21
|
+
allow(GDS::SSO).to receive(:test_user).and_return(test_user)
|
22
|
+
allow(GDS::SSO::Config).to receive(:permissions_for_dummy_api_user)
|
23
|
+
.and_return(%w[signin extra_permission])
|
24
|
+
|
25
|
+
expect(described_class.locate("anything")).to be(test_user)
|
26
|
+
expect(test_user.permissions).not_to include("extra_permission")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns a user with dummyapiuser@domain.com if one exists" do
|
30
|
+
test_user = TestUser.new
|
31
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
32
|
+
allow(TestUser).to receive(:where).and_return([test_user])
|
33
|
+
|
34
|
+
expect(described_class.locate("anything")).to be(test_user)
|
35
|
+
expect(TestUser).to have_received(:where).with(email: "dummyapiuser@domain.com")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "creates a user with dummyapiuser@domain.com if one does not exist" do
|
39
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
40
|
+
allow(GDS::SSO::Config).to receive(:additional_mock_permissions_required).and_return(nil)
|
41
|
+
allow(TestUser).to receive(:where).and_return([])
|
42
|
+
|
43
|
+
test_user = described_class.locate("anything")
|
44
|
+
expect(test_user).to be_an_instance_of(TestUser)
|
45
|
+
expect(test_user).to have_attributes(email: "dummyapiuser@domain.com",
|
46
|
+
name: "Dummy API user created by gds-sso",
|
47
|
+
permissions: %w[signin])
|
48
|
+
end
|
49
|
+
|
50
|
+
it "uses GDS::SSO::Config to overwrite any existing permissions" do
|
51
|
+
test_user = TestUser.new(permissions: %w[signin other_permission])
|
52
|
+
allow(GDS::SSO::Config).to receive(:user_klass).and_return(TestUser)
|
53
|
+
allow(TestUser).to receive(:where).and_return([test_user])
|
54
|
+
allow(GDS::SSO::Config).to receive(:permissions_for_dummy_api_user)
|
55
|
+
.and_return(%w[signin extra_permission])
|
56
|
+
|
57
|
+
test_user = described_class.locate("anything")
|
58
|
+
expect(test_user.permissions).to match_array(%w[signin extra_permission])
|
59
|
+
end
|
21
60
|
end
|
22
61
|
end
|
@@ -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
|