Skip to content

Commit

Permalink
Refine CORS configuration and add specs
Browse files Browse the repository at this point in the history
This fixes a couple of bugs in the previous implementation of this.
Using GovukContentSecurityPolicy::GOVUK_DOMAINS didn't work because it
makes use of wildcard origins, which aren't supported by rack-cors. I
chose to put together a simple regex as an alternative. It also had an
incorrect path for the resource - I had a wildcard asterisk so it can
handle with and without format as the Rails path is without a format,
yet in our apps we've configured a .json format [1].

Since the original was written it was also discovered that this
configuration would be needed for more than just development
environments and would actually be needed in production. The situation
where this is needed is CSV previews [2] which are GOV.UK pages with the
layout_super_navigation_header component hosted on the
assets.publishing.service.gov.uk.

In order to demonstrate CORS taking effect requests need to be provided
with an origin header e.g:

```
➜  ~ curl -Is -H "Origin: https://www.gov.uk" \
  http://127.0.0.1:3062/api/search/autocomplete.json\?q\=test | grep access-control
access-control-allow-origin: https://www.gov.uk
access-control-allow-methods: GET
access-control-expose-headers:
access-control-max-age: 7200
```

An absence of any access-control-* headers indicates a CORS fail and in
a browser a request will be blocked e.g:

```
➜  ~ curl -Is -H "Origin: https://example.com" \
  http://127.0.0.1:3062/api/search/autocomplete.json\?q\=test | grep access-control
```

I've wrote request specs that demonstrate these behaviours.

[1]: https://github.com/alphagov/govuk_publishing_components/blob/171e814b327bcfa0f2437fff0514ff086e31c96b/app/views/govuk_publishing_components/components/_layout_super_navigation_header.html.erb#L334
[2]: https://assets.publishing.service.gov.uk/media/663ca4da8603389a07a6d2f8/Malpractice_in_VTQ_-_Example_CSV_File.csv/preview
  • Loading branch information
kevindew committed Dec 12, 2024
1 parent c5bf11c commit cf3002f
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 6 deletions.
11 changes: 6 additions & 5 deletions config/initializers/cors.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Be sure to restart your server when you modify this file.

Rails.application.config.middleware.insert_before 0, Rack::Cors do
# Allow the autocomplete API to be accessed from any GOV.UK domain, including non-production ones.
# This lets us use the API in local development "live" stacks as well as the GOV.UK Publishing
# Components guide.
# Allow the autocomplete API to be accessed from any GOV.UK domain, including
# non-production ones. This enables autocomplete on CSV preview GOV.UK pages,
# which are hosted on assets.publishing.service.gov.uk.
# This also allows for local development usage.
allow do
origins GovukContentSecurityPolicy::GOVUK_DOMAINS
origins %r{(www|dev|publishing\.service)\.gov\.uk\z}

resource "/api/autocomplete.json",
resource "/api/search/autocomplete*",
headers: :any,
methods: %i[get]
end
Expand Down
30 changes: 29 additions & 1 deletion spec/requests/api/autocomplete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

let(:suggestions) { %w[blue grey red] }
let(:autocomplete_response) { instance_double(GdsApi::Response, to_hash: { suggestions: }) }
let(:params) { { q: "loving him was" } }

before do
allow(Services).to receive(:search_api_v2).and_return(search_api_v2)
end

it "returns suggestions from Search API v2" do
get "/api/search/autocomplete?q=loving+him+was"
get "/api/search/autocomplete", params: params

expect(search_api_v2).to have_received(:autocomplete).with("loving him was")
expect(response).to be_successful
Expand All @@ -23,4 +24,31 @@

expect(response).to have_http_status(:bad_request)
end

describe "CORS headers" do
%w[https://www.gov.uk http://example.dev.gov.uk https://example.publishing.service.gov.uk].each do |allowed_host|
it "returns CORS headers for #{allowed_host}" do
get "/api/search/autocomplete", params:, headers: { Origin: allowed_host }

expect(response.headers.to_h).to include({
"access-control-allow-origin" => allowed_host,
"access-control-allow-methods" => "GET",
})
end
end

it "returns CORS headers when there is a format extension on the path" do
get "/api/search/autocomplete.json", params:, headers: { Origin: "https://www.gov.uk" }

expect(response.headers)
.to include("access-control-allow-origin", "access-control-allow-methods")
end

it "doesn't return CORS headers for an unsupported hosts" do
get "/api/search/autocomplete", params:, headers: { Origin: "https://www.gov.uk.non-govuk.com" }

expect(response.headers)
.not_to include("access-control-allow-origin", "access-control-allow-methods")
end
end
end

0 comments on commit cf3002f

Please sign in to comment.