mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
[30007] Allow users to enable CORS on APIv3 resources
Allows a setting and configuration option to allow access to APIV3. Setting allows us to selectively enable this for cloud instances too. https://community.openproject.com/wp/30007
This commit is contained in:
@@ -286,6 +286,9 @@ gem 'bootsnap', '~> 1.4.5', require: false
|
||||
# API gems
|
||||
gem 'grape', '~> 1.3.0'
|
||||
|
||||
# CORS for API
|
||||
gem 'rack-cors', '~> 1.1.1'
|
||||
|
||||
gem 'reform', '~> 2.2.0'
|
||||
gem 'reform-rails', '~> 0.1.7'
|
||||
gem 'roar', '~> 1.1.0'
|
||||
|
||||
@@ -724,6 +724,8 @@ GEM
|
||||
rack (>= 0.4)
|
||||
rack-attack (6.2.2)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-mini-profiler (2.0.1)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (1.10.1)
|
||||
@@ -1088,6 +1090,7 @@ DEPENDENCIES
|
||||
puffing-billy (~> 2.3.1)
|
||||
puma (~> 4.3.5)
|
||||
rack-attack (~> 6.2.2)
|
||||
rack-cors (~> 1.1.1)
|
||||
rack-mini-profiler
|
||||
rack-protection (~> 2.0.8)
|
||||
rack-test (~> 1.1.0)
|
||||
|
||||
@@ -40,11 +40,17 @@ module AdminSettingsUpdater
|
||||
if params[:settings]
|
||||
Settings::UpdateService
|
||||
.new(user: current_user)
|
||||
.call(settings: permitted_params.settings.to_h)
|
||||
.call(settings: settings_params)
|
||||
|
||||
flash[:notice] = t(:notice_successful_update)
|
||||
redirect_to action: 'show', tab: params[:tab]
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def settings_params
|
||||
permitted_params.settings.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#-- encoding: UTF-8
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2020 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
class Settings::ApiController < SettingsController
|
||||
include AdminSettingsUpdater
|
||||
|
||||
menu_item :settings_api
|
||||
|
||||
def show
|
||||
render template: 'settings/_api'
|
||||
end
|
||||
|
||||
def default_breadcrumb
|
||||
t(:label_api_access_key_type)
|
||||
end
|
||||
|
||||
def settings_params
|
||||
super.tap do |settings|
|
||||
settings["apiv3_cors_origins"] = settings["apiv3_cors_origins"].split(/\r?\n/)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -50,6 +50,11 @@ module SettingsHelper
|
||||
action: { controller: '/settings/projects', action: 'show' },
|
||||
label: :label_project_plural
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
action: { controller: '/settings/api', action: 'show' },
|
||||
label: :label_api_access_key_type
|
||||
},
|
||||
{
|
||||
name: 'repositories',
|
||||
action: { controller: '/settings/repositories', action: 'show' },
|
||||
@@ -116,7 +121,13 @@ module SettingsHelper
|
||||
def setting_text_area(setting, options = {})
|
||||
setting_label(setting, options) +
|
||||
wrap_field_outer(options) do
|
||||
styled_text_area_tag("settings[#{setting}]", Setting.send(setting), options)
|
||||
value = Setting.send(setting)
|
||||
|
||||
if value.is_a?(Array)
|
||||
value = value.join("\n")
|
||||
end
|
||||
|
||||
styled_text_area_tag("settings[#{setting}]", value, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) 2012-2020 the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See docs/COPYRIGHT.rdoc for more details.
|
||||
|
||||
++#%>
|
||||
<%= toolbar title: t(:label_api_access_key_type) %>
|
||||
|
||||
<%= styled_form_tag(update_api_settings_path, method: :patch) do %>
|
||||
<section class="form--section">
|
||||
<fieldset class="form--fieldset">
|
||||
<legend class="form--fieldset-legend">Cross-Origin Resource Sharing (CORS)</legend>
|
||||
</fieldset>
|
||||
<div class="form--field">
|
||||
<%= setting_check_box :apiv3_cors_enabled %>
|
||||
</div>
|
||||
<div class="form--field">
|
||||
<%= setting_text_area :apiv3_cors_origins, rows: 5, container_class: '-wide' %>
|
||||
<div class="form--field-instructions">
|
||||
<p><%= t(:text_line_separated) %></p>
|
||||
<p><%= t(:setting_apiv3_cors_origins_text_html,
|
||||
origin_link: ::OpenProject::Static::Links[:origin_mdn_documentation][:href]) %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<%= styled_button_tag t(:button_save), class: '-highlight -with-icon icon-checkmark' %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,38 @@
|
||||
#-- encoding: UTF-8
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2020 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
||||
allow do
|
||||
origins { |source, env| ::API::V3::CORS.allowed?(source) }
|
||||
resource '/api/v3*',
|
||||
headers: :any,
|
||||
methods: :any,
|
||||
credentials: true,
|
||||
if: proc { ::API::V3::CORS.enabled? }
|
||||
end
|
||||
end
|
||||
@@ -55,6 +55,7 @@ OpenProject::Inflector.inflection(
|
||||
'scm' => 'SCM',
|
||||
'imap' => 'IMAP',
|
||||
'pop3' => 'POP3',
|
||||
'cors' => 'CORS',
|
||||
'openid_connect' => 'OpenIDConnect',
|
||||
'pdf_export' => 'PDFExport'
|
||||
)
|
||||
|
||||
@@ -2161,6 +2161,12 @@ en:
|
||||
|
||||
search_input_placeholder: "Search ..."
|
||||
|
||||
setting_apiv3_cors_enabled: "Enable CORS"
|
||||
setting_apiv3_cors_origins: "API V3 Cross-Origin Resource Sharing (CORS) allowed origins"
|
||||
setting_apiv3_cors_origins_text_html: >
|
||||
If CORS is enabled, these are the origins that are allowed to access OpenProject API.
|
||||
<br/>
|
||||
Please check the <a href="%{origin_link}" target="_blank">Documentation on the Origin header</a> on how to specify the expected values.
|
||||
setting_email_delivery_method: "Email delivery method"
|
||||
setting_sendmail_location: "Location of the sendmail executable"
|
||||
setting_smtp_enable_starttls_auto: "Automatically use STARTTLS if available"
|
||||
|
||||
@@ -365,3 +365,9 @@ installation_uuid:
|
||||
oauth_allow_remapping_of_existing_users:
|
||||
default: false
|
||||
format: boolean
|
||||
apiv3_cors_enabled:
|
||||
default: false
|
||||
format: boolean
|
||||
apiv3_cors_origins:
|
||||
serialized: true
|
||||
default: []
|
||||
|
||||
@@ -100,6 +100,15 @@ On the other hand using **API keys** has some advantages too, which is why we we
|
||||
Most importantly users may not actually have a password to begin with. Specifically when they have registered
|
||||
through an OpenID Connect provider.
|
||||
|
||||
# Cross-Origin Resource Sharing (CORS)
|
||||
|
||||
By default, the OpenProject API is _not_ responding with any CORS headers.
|
||||
If you want to allow cross-domain AJAX calls against your OpenProject instance, you need to enable CORS headers being returned.
|
||||
|
||||
Please see [our API settings documentation](https://docs.openproject.org/system-admin-guide/system-settings/api-settings/) on
|
||||
how to selectively enable CORS.
|
||||
|
||||
|
||||
# Allowed HTTP methods
|
||||
|
||||
- `GET` - Get a single resource or collection of resources
|
||||
|
||||
@@ -50,3 +50,11 @@ In Postman the configuration should look like this (Replace `{{protocolHostPort}
|
||||
i.e. `https://example.com`)
|
||||
|
||||

|
||||
|
||||
## CORS headers
|
||||
|
||||
By default, the OpenProject API is _not_ responding with any CORS headers.
|
||||
If you want to allow cross-domain AJAX calls against your OpenProject instance, you need to enable CORS headers being returned.
|
||||
|
||||
Please see [our API settings documentation](https://docs.openproject.org/system-admin-guide/system-settings/api-settings/) on
|
||||
how to selectively enable CORS.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
sidebar_navigation:
|
||||
title: API settings
|
||||
description: Settings for API functionality of OpenProject
|
||||
robots: index, follow
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
For more information on the concepts of Cross-Origin Resource Sharing (CORS), please see:
|
||||
|
||||
- [an overview of CORS from MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
|
||||
- [a tutorial on CORS by Auth0](https://auth0.com/blog/cors-tutorial-a-guide-to-cross-origin-resource-sharing/)
|
||||
@@ -0,0 +1,50 @@
|
||||
#-- encoding: UTF-8
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2020 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
# CORS helper methods for the API v3
|
||||
module API
|
||||
module V3
|
||||
module CORS
|
||||
##
|
||||
# Returns whether CORS headers should
|
||||
# be set on the APIv3 resources
|
||||
def self.enabled?
|
||||
Setting.apiv3_cors_enabled?
|
||||
end
|
||||
|
||||
##
|
||||
# Determine whether the given origin is included
|
||||
# in the allowed origin list
|
||||
def self.allowed?(source)
|
||||
Setting.apiv3_cors_origins.include?(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,6 +26,8 @@
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
require_relative 'cache/cache_key'
|
||||
|
||||
module OpenProject
|
||||
module Cache
|
||||
def self.fetch(*parts, &block)
|
||||
|
||||
@@ -184,6 +184,9 @@ module OpenProject
|
||||
ldap_encryption_documentation: {
|
||||
href: 'https://www.rubydoc.info/gems/net-ldap/Net/LDAP#constructor_details',
|
||||
},
|
||||
origin_mdn_documentation: {
|
||||
href: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin'
|
||||
},
|
||||
security_badge_documentation: {
|
||||
href: 'https://docs.openproject.org/system-admin-guide/information/#security-badge'
|
||||
},
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2020 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
require 'spec_helper'
|
||||
require 'rack/test'
|
||||
|
||||
describe 'API v3 CORS headers',
|
||||
type: :request,
|
||||
content_type: :json do
|
||||
include Rack::Test::Methods
|
||||
include Capybara::RSpecMatchers
|
||||
include API::V3::Utilities::PathHelper
|
||||
|
||||
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
|
||||
|
||||
# CORS needs to output headers even if you're unauthorized to allow authentication
|
||||
# to happen
|
||||
it 'returns the CORS header on an unauthorized resource as well', :aggregate_failures do
|
||||
options '/api/v3/work_packages/form',
|
||||
nil,
|
||||
'HTTP_ORIGIN' => 'https://foo.example.com'
|
||||
|
||||
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).to have_key('Access-Control-Max-Age')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when disabled',
|
||||
with_settings: { apiv3_cors_enabled: false, apiv3_cors_origins: %w[foo.example.com] } do
|
||||
it 'does not output CORS headers even though origin matches', :aggregate_failures do
|
||||
options '/api/v3',
|
||||
nil,
|
||||
'HTTP_ORIGIN' => 'foo.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
|
||||
end
|
||||
Reference in New Issue
Block a user