diff --git a/config/initializers/rack-cors.rb b/config/initializers/rack-cors.rb index babec0d18b..c1b20d2832 100644 --- a/config/initializers/rack-cors.rb +++ b/config/initializers/rack-cors.rb @@ -35,5 +35,11 @@ Rails.application.config.middleware.insert_after Rails::Rack::Logger, Rack::Cors methods: :any, credentials: true, if: proc { ::API::V3::CORS.enabled? } + + resource '/oauth/*', + headers: :any, + methods: :any, + credentials: true, + if: proc { ::API::V3::CORS.enabled? } end end diff --git a/docs/system-admin-guide/system-settings/api-settings/README.md b/docs/system-admin-guide/system-settings/api-settings/README.md index bb63a796bb..f5c4c46121 100644 --- a/docs/system-admin-guide/system-settings/api-settings/README.md +++ b/docs/system-admin-guide/system-settings/api-settings/README.md @@ -1,6 +1,6 @@ --- sidebar_navigation: - title: API settings +title: API settings description: Settings for API functionality of OpenProject robots: index, follow keywords: API settings @@ -8,12 +8,12 @@ keywords: API settings # API system settings In the API settings, you can selectively control whether foreign applications may access your OpenProject -API endpoints from within the browser. +API endpoints from within the browser. ## Cross-Origin Resource Sharing (CORS) -To enable CORS headers being returned by the [OpenProject APIv3](https://docs.openproject.org/api/), -enable the check box on this page. +To enable CORS headers being returned by the [OpenProject APIv3](../../../api/), +enable the check box on this page. This will also enable it for dependent authentication endpoints, such as OAuth endpoints `/oauth/token` and the like. You will then have to enter the allowed values for the Origin header that OpenProject will allow access to. This is necessary, since authenticated resources of OpenProject cannot be accessible to all origins with the `*` header value. diff --git a/spec/requests/api/v3/cors_header_spec.rb b/spec/requests/api/v3/cors_header_spec.rb index c720b75215..a846918e22 100644 --- a/spec/requests/api/v3/cors_header_spec.rb +++ b/spec/requests/api/v3/cors_header_spec.rb @@ -36,35 +36,43 @@ describe 'API v3 CORS headers', include Capybara::RSpecMatchers include API::V3::Utilities::PathHelper + shared_examples 'outputs CORS headers' do |request_path| + it 'outputs CORS headers', :aggregate_failures do + options request_path, + nil, + 'HTTP_ORIGIN' => 'https://foo.example.com', + 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', + 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'test' + + expect(last_response.headers['Access-Control-Allow-Origin']).to eq('https://foo.example.com') + expect(last_response.headers['Access-Control-Allow-Methods']).to eq('GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS') + expect(last_response.headers['Access-Control-Allow-Headers']).to eq('test') + expect(last_response.headers).to have_key('Access-Control-Max-Age') + end + + it 'rejects CORS headers for invalid origin' do + options request_path, + nil, + 'HTTP_ORIGIN' => 'invalid.example.com', + 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', + 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'test' + + expect(last_response.headers).not_to have_key 'Access-Control-Allow-Origin' + expect(last_response.headers).not_to have_key 'Access-Control-Allow-Methods' + expect(last_response.headers).not_to have_key 'Access-Control-Allow-Headers' + expect(last_response.headers).not_to have_key 'Access-Control-Max-Age' + end + end + context 'with setting enabled', with_settings: { apiv3_cors_enabled: true } do context 'with allowed origin set to specific values', with_settings: { apiv3_cors_origins: %w[https://foo.example.com bla.test] } do - it 'outputs CORS headers', :aggregate_failures do - options '/api/v3', - nil, - 'HTTP_ORIGIN' => 'https://foo.example.com', - 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', - 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'test' - expect(last_response.headers['Access-Control-Allow-Origin']).to eq('https://foo.example.com') - expect(last_response.headers['Access-Control-Allow-Methods']).to eq('GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS') - expect(last_response.headers['Access-Control-Allow-Headers']).to eq('test') - expect(last_response.headers).to have_key('Access-Control-Max-Age') - end - - it 'rejects CORS headers for invalid origin' do - options '/api/v3', - nil, - 'HTTP_ORIGIN' => 'invalid.example.com', - 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET', - 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' => 'test' - - expect(last_response.headers).not_to have_key 'Access-Control-Allow-Origin' - expect(last_response.headers).not_to have_key 'Access-Control-Allow-Methods' - expect(last_response.headers).not_to have_key 'Access-Control-Allow-Headers' - expect(last_response.headers).not_to have_key 'Access-Control-Max-Age' - end + it_behaves_like 'outputs CORS headers', '/api/v3' + it_behaves_like 'outputs CORS headers', '/oauth/token' + it_behaves_like 'outputs CORS headers', '/oauth/authorize' + it_behaves_like 'outputs CORS headers', '/oauth/revoke' # CORS needs to output headers even if you're unauthorized to allow authentication # to happen