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.
@@ -1,245 +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 "restricts access when the request doesn't match the api_request_matcher" do
84
- allow(GDS::SSO::Config)
85
- .to receive(:api_request_matcher)
86
- .and_return(->(_request) { false })
87
-
88
- visit "/restricted"
89
- expect(page.status_code).to eql(302)
90
- expect(page.response_headers["Location"]).to match("/auth/gds")
91
- end
92
-
93
- it "allows access when given a valid bearer token" do
94
- stub_signon_user_request
95
- page.driver.header("Authorization", "Bearer 123")
96
-
97
- visit "/restricted"
98
- expect(page).to have_content("restricted kablooie")
99
- end
100
-
101
- it "restricts access when given an invalid bearer token" do
102
- stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
103
- .to_return(status: 401)
104
- page.driver.header("Authorization", "Bearer 123")
105
-
106
- visit "/restricted"
107
- expect(page.status_code).to eq(401)
108
- expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_token"')
109
- expect_json_response({ "message" => "Bearer token does not appear to be valid" })
110
- end
111
-
112
- it "returns a JSON 401 when a bearer token is missing and the app is api_only" do
113
- allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
114
-
115
- visit "/restricted"
116
- expect(page.status_code).to eq(401)
117
- expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_request"')
118
- expect_json_response({ "message" => "No bearer token was provided" })
119
- end
120
-
121
- it "returns a JSON 401 when a bearer token is missing and the request matches the api_request_matcher" do
122
- allow(GDS::SSO::Config)
123
- .to receive(:api_request_matcher)
124
- .and_return(->(request) { request.path == "/restricted" })
125
-
126
- visit "/restricted"
127
- expect(page.status_code).to eq(401)
128
- expect(page.response_headers["WWW-Authenticate"]).to eq('Bearer error="invalid_request"')
129
- expect_json_response({ "message" => "No bearer token was provided" })
130
- end
131
- end
132
-
133
- context "when accessing a route that requires authentication with the mock strategies" do
134
- before do
135
- # Using allow_any_instance_of because it's hard to access the instance
136
- # of the class used within the Rails middleware
137
- allow_any_instance_of(Warden::Config).to receive(:[]).and_call_original
138
- allow_any_instance_of(Warden::Config)
139
- .to receive(:[])
140
- .with(:default_strategies)
141
- .and_return({ _all: %i[mock_gds_sso gds_bearer_token] })
142
-
143
- allow(Warden::OAuth2.config).to receive(:token_model).and_return(GDS::SSO::MockBearerToken)
144
- allow(GDS::SSO).to receive(:test_user).and_return(TestUser.new)
145
- end
146
-
147
- it "allows access without being logged in" do
148
- visit "/restricted"
149
- expect(page.status_code).to eq(200)
150
- expect(page.body).to have_content("restricted kablooie")
151
- end
152
-
153
- it "allows access to an API mock user" do
154
- allow(GDS::SSO::Config).to receive(:api_only).and_return(true)
155
-
156
- visit "/restricted"
157
- expect(page.status_code).to eq(200)
158
- expect(page.body).to have_content("restricted kablooie")
159
- end
160
- end
161
-
162
- context "when accessing a route that requires a permission" do
163
- it "allows access when an authenticated user has the permission" do
164
- stub_signon_authenticated(permissions: %w[execute])
165
- visit "/this-requires-execute-permission"
166
- expect(page).to have_content("you have execute permission")
167
- end
168
-
169
- it "restricts access when an authenticated user lacks the permission" do
170
- stub_signon_authenticated
171
- visit "/this-requires-execute-permission"
172
- expect(page.status_code).to eq(403)
173
- expect(page).to have_content("Sorry, you don't seem to have the execute permission for this app.")
174
- end
175
-
176
- it "returns a JSON response when it's an API call" do
177
- allow(GDS::SSO::Config)
178
- .to receive(:api_request_matcher)
179
- .and_return(->(request) { request.path == "/this-requires-execute-permission" })
180
-
181
- stub_signon_user_request
182
- page.driver.header("Authorization", "Bearer 123")
183
-
184
- visit "/this-requires-execute-permission"
185
- expect(page.status_code).to eq(403)
186
- expect_json_response({ "message" => "Sorry, you don't seem to have the execute permission for this app." })
187
- end
188
- end
189
-
190
- context "when accessing a route that is restricted by the authorised user constraint" do
191
- it "allows access when an authenticated user has correct permissions" do
192
- stub_signon_authenticated(permissions: %w[execute])
193
- visit "/constraint-restricted"
194
- expect(page).to have_content("constraint restricted")
195
- end
196
-
197
- it "redirects an unauthenticated request to signon" do
198
- visit "/constraint-restricted"
199
- expect(page.response_headers["Location"]).to match("/auth/gds")
200
- visit page.response_headers["Location"]
201
- expect(page.response_headers["Location"]).to match("http://signon/oauth/authorize")
202
- end
203
-
204
- it "restricts access when an authenticated user does not have the correct permissions" do
205
- stub_signon_authenticated(permissions: %w[no-access])
206
- visit "/constraint-restricted"
207
- expect(page.status_code).to eq(403)
208
- end
209
- end
210
-
211
- def stub_signon_authenticated(permissions: [])
212
- # visit restricted page to trigger redirect URL to record state attribute
213
- visit "/auth/gds"
214
- state = CGI.parse(URI.parse(page.response_headers["Location"]).query)
215
- .then { |query| query["state"].first }
216
-
217
- stub_request(:post, "http://signon/oauth/access_token")
218
- .to_return(body: { access_token: "token" }.to_json,
219
- headers: { content_type: "application/json" })
220
-
221
- stub_signon_user_request(permissions:)
222
-
223
- visit "/auth/gds/callback?code=code&state=#{state}"
224
- end
225
-
226
- def stub_signon_user_request(permissions: [])
227
- stub_request(:get, "http://signon/user.json?client_id=gds-sso-test")
228
- .to_return(
229
- body: {
230
- user: {
231
- uid: "123",
232
- email: "test-user@example.com",
233
- name: "Test User",
234
- permissions:,
235
- },
236
- }.to_json,
237
- headers: { content_type: "application/json" },
238
- )
239
- end
240
-
241
- def expect_json_response(json_match)
242
- expect(page.response_headers["content-type"]).to match(/application\/json/)
243
- expect(JSON.parse(page.body)).to match(json_match)
244
- end
245
- end