diff --git a/app/controllers/oauth/auth_base_controller.rb b/app/controllers/oauth/auth_base_controller.rb index c698c28d3c..1e29dfde3e 100644 --- a/app/controllers/oauth/auth_base_controller.rb +++ b/app/controllers/oauth/auth_base_controller.rb @@ -34,6 +34,17 @@ module OAuth # See config/initializers/doorkeeper.rb class AuthBaseController < ::ApplicationController skip_before_action :check_if_login_required + after_action :extend_content_security_policy layout 'only_logo' + + def extend_content_security_policy + use_content_security_policy_named_append(:oauth) + end + + def allowed_forms + allowed_redirect_urls = pre_auth&.client&.application&.redirect_uri + urls = allowed_redirect_urls.to_s.split + urls.map { |url| URI.join(url, '/') }.map(&:to_s) + end end end diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index f5bab818cb..aeffb0ce84 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -73,3 +73,9 @@ SecureHeaders::Configuration.default do |config| connect_src: connect_src } end + +SecureHeaders::Configuration.named_append(:oauth) do |request| + hosts = request.controller_instance.try(:allowed_forms) || [] + + { form_action: hosts } +end diff --git a/docs/system-admin-guide/authentication/oauth-applications/README.md b/docs/system-admin-guide/authentication/oauth-applications/README.md index ffaf090262..a748d34895 100644 --- a/docs/system-admin-guide/authentication/oauth-applications/README.md +++ b/docs/system-admin-guide/authentication/oauth-applications/README.md @@ -26,4 +26,21 @@ You can configure the following options to add your oauth application. 4. Choose **client credential flows** and define a user on whose behalf requests will be performed. 5. Press the blue **Create** button to add your oauth application. -![Sys-admin-authentication-add-oauth-application](Sys-admin-authentication-add-oauth-application.png) \ No newline at end of file +![Sys-admin-authentication-add-oauth-application](Sys-admin-authentication-add-oauth-application.png) + +## Oauth endpoints + +The authentication endpoints are at + +* Auth URL: `https://example.com/oauth/authorize` +* Access Token URL: `https://example.com/oauth/token` + +## Using Postman with oauth? + +Set redirect URLs to `urn:ietf:wg:oauth:2.0:oob` in both, for your application (see step 2 above) and +within Postman. + +In Postman the configuration should look like this (Replace `{{protocolHostPort}}` with your host, +i.e. `https://example.com`) + +![Sys-admin-authentication-add-oauth-application](Sys-admin-authentication-oauth-postman.png) diff --git a/docs/system-admin-guide/authentication/oauth-applications/Sys-admin-authentication-oauth-postman.png b/docs/system-admin-guide/authentication/oauth-applications/Sys-admin-authentication-oauth-postman.png new file mode 100644 index 0000000000..672b50eeb7 Binary files /dev/null and b/docs/system-admin-guide/authentication/oauth-applications/Sys-admin-authentication-oauth-postman.png differ diff --git a/spec/features/oauth/authorization_code_flow_spec.rb b/spec/features/oauth/authorization_code_flow_spec.rb index 8953331880..9d0a69abb7 100644 --- a/spec/features/oauth/authorization_code_flow_spec.rb +++ b/spec/features/oauth/authorization_code_flow_spec.rb @@ -28,21 +28,37 @@ require 'spec_helper' -describe 'OAuth authorization code flow', type: :feature, js: true do +describe 'OAuth authorization code flow', + type: :feature, + js: true do let!(:user) { FactoryBot.create(:user) } - let!(:app) { FactoryBot.create(:oauth_application, name: 'Cool API app!') } + let!(:redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' } + let!(:allowed_redirect_uri) { redirect_uri } + let!(:app) { FactoryBot.create(:oauth_application, name: 'Cool API app!', redirect_uri: allowed_redirect_uri) } let(:client_secret) { app.plaintext_secret } - def oauth_path(client_id) - "/oauth/authorize?response_type=code&client_id=#{client_id}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=api_v3" + def oauth_path(client_id, redirect_url) + "/oauth/authorize?response_type=code&client_id=#{client_id}&redirect_uri=#{CGI.escape(redirect_url)}&scope=api_v3" end before do - # Do not login, will do that in the oauth flow + @record_requests = Billy.config.record_requests + @whitelist = Billy.config.whitelist + Billy.configure do |c| + c.record_requests = true + c.whitelist = [] + end + end + + after do + Billy.configure do |c| + c.record_requests = @record_requests + c.whitelist = @whitelist + end end it 'can authorize and manage an OAuth application grant' do - visit oauth_path app.uid + visit oauth_path app.uid, redirect_uri # Expect we're guided to the login screen login_with user.login, 'adminADMIN!', visit_signin_path: false @@ -54,6 +70,20 @@ describe 'OAuth authorization code flow', type: :feature, js: true do expect(page).to have_selector('li strong', text: I18n.t('oauth.scopes.api_v3')) expect(page).to have_selector('li', text: I18n.t('oauth.scopes.api_v3_text')) + first = true + allow_any_instance_of(::OAuth::AuthBaseController) + .to receive(:allowed_forms).and_wrap_original do |m| + forms = m.call + + # Multiple requests end up here with one not containing the request url + if first + expect(forms).to include redirect_uri + first = false + end + + forms + end + # Authorize find('input.button[value="Authorize"]').click @@ -108,7 +138,7 @@ describe 'OAuth authorization code flow', type: :feature, js: true do end it 'does not authenticate unknown applications' do - visit oauth_path 'WAT' + visit oauth_path 'WAT', redirect_uri # Expect we're guided to the login screen login_with user.login, 'adminADMIN!', visit_signin_path: false @@ -120,4 +150,34 @@ describe 'OAuth authorization code flow', type: :feature, js: true do user.oauth_grants.reload expect(user.oauth_grants.count).to eq 0 end + + # Selenium can't return response headers + context 'in browser that can log response headers', js: false do + before do + login_as user + end + + context 'with real urls as allowed redirect uris' do + let!(:redirect_uri) { "https://foo.com/foo " } + let!(:allowed_redirect_uri) { "#{redirect_uri} https://bar.com/bar" } + it 'can authorize and manage an OAuth application grant' do + visit oauth_path app.uid, redirect_uri + + allow_any_instance_of(::OAuth::AuthBaseController) + .to receive(:allowed_forms).and_wrap_original do |m| + forms = m.call + + expect(forms).to include 'https://foo.com/' + expect(forms).to include 'https://bar.com/' + + forms + end + + # Check that the hosts of allowed redirection urls are present in the content security policy + expect(page.response_headers['content-security-policy']).to( + include("form-action 'self' https://foo.com/ https://bar.com/;") + ) + end + end + end end