diff --git a/app/services/oauth_clients/redirect_uri_from_state_service.rb b/app/services/oauth_clients/redirect_uri_from_state_service.rb new file mode 100644 index 0000000000..cb70a810ab --- /dev/null +++ b/app/services/oauth_clients/redirect_uri_from_state_service.rb @@ -0,0 +1,57 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2022 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-2013 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "rack/oauth2" +require "uri/http" + +module OAuthClients + class RedirectUriFromStateService + def initialize(state:, cookies:) + @state = state + @cookies = cookies + end + + def call + redirect_uri = oauth_state_cookie + + if redirect_uri.present? && ::API::V3::Utilities::PathHelper::ApiV3Path::same_origin?(redirect_uri) + ServiceResult.new(success: true, result: redirect_uri) + else + ServiceResult.new(success: false) + end + end + + private + + def oauth_state_cookie + return nil if @state.blank? + + @cookies["oauth_state_#{@state}"] + end + end +end diff --git a/spec/services/oauth_clients/redirect_uri_from_state_service_spec.rb b/spec/services/oauth_clients/redirect_uri_from_state_service_spec.rb new file mode 100644 index 0000000000..7a3ef4237e --- /dev/null +++ b/spec/services/oauth_clients/redirect_uri_from_state_service_spec.rb @@ -0,0 +1,73 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2022 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-2013 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 COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' +require 'webmock/rspec' + +describe ::OAuthClients::RedirectUriFromStateService, type: :model do + let(:state) { 'asdf123425' } + let(:redirect_uri) { File.join(::API::V3::Utilities::PathHelper::ApiV3Path::root_url, 'foo/bar') } + let(:cookies) { { "oauth_state_#{state}": redirect_uri }.with_indifferent_access } + let(:instance) { described_class.new(state:, cookies:) } + + describe '#call' do + subject { instance.call } + + shared_examples 'failed service result' do + it 'return a failed service result' do + expect(subject).to be_failure + end + end + + context 'when cookie found' do + context 'when redirect_uri has same origin' do + it 'returns the redirect URL value from the cookie' do + expect(subject).to be_success + end + end + + context 'when redirect_uri does not share same origin' do + let(:redirect_uri) { 'https://some-other-origin.com/bla' } + + it_behaves_like 'failed service result' + end + end + + context 'when no cookie present' do + let(:cookies) { {} } + + it_behaves_like 'failed service result' + end + + context 'when no state present' do + let(:state) { nil } + + it_behaves_like 'failed service result' + end + end +end