diff --git a/app/services/remote_identities/create_service.rb b/app/services/remote_identities/create_service.rb index 34626f03e3e3..513924d4781b 100644 --- a/app/services/remote_identities/create_service.rb +++ b/app/services/remote_identities/create_service.rb @@ -47,7 +47,7 @@ def initialize(user:, integration:, token:, force_update: false) def call if @model.new_record? || @force_update - origin_result = @integration.extract_origin_user_id(@token) + origin_result = @integration.extract_origin_user_id(@token.user) user_id = origin_result.value_or { return ServiceResult.failure(errors: it) } diff --git a/modules/wikis/app/contracts/wikis/adapters/input/strategy_contract.rb b/modules/wikis/app/contracts/wikis/adapters/input/strategy_contract.rb new file mode 100644 index 000000000000..03e88117bb18 --- /dev/null +++ b/modules/wikis/app/contracts/wikis/adapters/input/strategy_contract.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + module Input + class StrategyContract < DryApplicationContract + AUTH_METHODS = %i[bearer_token internal].to_set.freeze + + params do + required(:key).filled(:symbol, included_in?: AUTH_METHODS) + optional(:user).maybe + end + end + end + end +end diff --git a/modules/wikis/app/models/wikis/adapters/input/user_query.rb b/modules/wikis/app/models/wikis/adapters/input/strategy.rb similarity index 85% rename from modules/wikis/app/models/wikis/adapters/input/user_query.rb rename to modules/wikis/app/models/wikis/adapters/input/strategy.rb index d567bb919de2..a5ac9d60e04f 100644 --- a/modules/wikis/app/models/wikis/adapters/input/user_query.rb +++ b/modules/wikis/app/models/wikis/adapters/input/strategy.rb @@ -29,5 +29,11 @@ #++ module Wikis::Adapters::Input - UserQuery = Data.define(:access_token) + Strategy = Data.define(:key, :user) do + private_class_method :new + + def self.build(key:, user: nil, contract: StrategyContract.new) + contract.call(key:, user:).to_monad.fmap { new(**it.to_h) } + end + end end diff --git a/modules/wikis/app/models/wikis/provider.rb b/modules/wikis/app/models/wikis/provider.rb index 4d591e843304..a6d40d49581d 100644 --- a/modules/wikis/app/models/wikis/provider.rb +++ b/modules/wikis/app/models/wikis/provider.rb @@ -44,6 +44,10 @@ class Provider < ApplicationRecord def to_s = self.class.registry_prefix def user_connected?(_user) = raise SubclassResponsibilityError + def auth_strategy_for(user) + resolve("authentication.user_bound").call(user) + end + class << self def registry_prefix = raise SubclassResponsibilityError end diff --git a/modules/wikis/app/models/wikis/xwiki_provider.rb b/modules/wikis/app/models/wikis/xwiki_provider.rb index bf2c980bf830..384a10e71748 100644 --- a/modules/wikis/app/models/wikis/xwiki_provider.rb +++ b/modules/wikis/app/models/wikis/xwiki_provider.rb @@ -54,8 +54,9 @@ def user_connected?(user) OAuthClientToken.for_user_and_client(user, oauth_client).exists? end - def extract_origin_user_id(token) - resolve("queries.user").call(Wikis::Adapters::Input::UserQuery.new(access_token: token.access_token)) + def extract_origin_user_id(user) + auth_strategy = auth_strategy_for(user) + resolve("queries.user").call(auth_strategy:) end def authenticate_via_two_way_oauth2? diff --git a/modules/wikis/app/services/wikis/adapters/authentication.rb b/modules/wikis/app/services/wikis/adapters/authentication.rb new file mode 100644 index 000000000000..3ca6e4fb7232 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/authentication.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + class Authentication + class << self + # @param strategy [Input::Strategy] + def [](strategy) + auth = strategy.value_or { raise ArgumentError, "Invalid authentication strategy '#{it.inspect}'" } + + case auth.key + when :bearer_token + AuthenticationStrategies::BearerToken.new(auth.user) + when :internal + AuthenticationStrategies::InternalUser.new(auth.user) + else + raise ArgumentError, "Unknown authentication scheme: #{auth.key}" + end + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/authentication_strategies/bearer_token.rb b/modules/wikis/app/services/wikis/adapters/authentication_strategies/bearer_token.rb new file mode 100644 index 000000000000..9a456fe6e6ce --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/authentication_strategies/bearer_token.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + module AuthenticationStrategies + class BearerToken + def initialize(user) + @user = user + end + + def call(provider:, http_options: {}, **) + token = OAuthClientToken.for_user_and_client(@user, provider.oauth_client).first + yield OpenProject.httpx.bearer_auth(token&.access_token).with(http_options) + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/authentication_strategies/internal_user.rb b/modules/wikis/app/services/wikis/adapters/authentication_strategies/internal_user.rb new file mode 100644 index 000000000000..b7e9d04e4701 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/authentication_strategies/internal_user.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + module AuthenticationStrategies + class InternalUser + def initialize(user) + @user = user + end + + def call(**) + yield @user + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/providers/internal/authentication/user_bound.rb b/modules/wikis/app/services/wikis/adapters/providers/internal/authentication/user_bound.rb new file mode 100644 index 000000000000..e080084deeed --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/providers/internal/authentication/user_bound.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + module Providers + module Internal + module Authentication + class UserBound + def initialize(_model:); end + + def call(user) + Input::Strategy.build(key: :internal, user:) + end + end + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/page_info.rb b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/page_info.rb index 6356e00a5937..4336cb5d1f03 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/page_info.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/page_info.rb @@ -34,23 +34,24 @@ module Providers module Internal module Queries class PageInfo < BaseQuery - def call(input_data) - # TODO: should we accept implicit User.current or do we want to pass in a user explicitly? - wiki_page = WikiPage.visible.find_by(id: input_data.identifier) - return failure(code: :not_found) if wiki_page.nil? + def call(input_data:, auth_strategy:) + Authentication[auth_strategy].call do |user| + wiki_page = WikiPage.visible(user).find_by(id: input_data.identifier) + return failure(code: :not_found) if wiki_page.nil? - success( - Results::PageInfo.new( - identifier: input_data.identifier, - provider:, - title: wiki_page.title, - href: url_for(only_path: true, - controller: "/wiki", - action: "show", - project_id: wiki_page.project.identifier, - id: wiki_page.slug) + success( + Results::PageInfo.new( + identifier: input_data.identifier, + provider:, + title: wiki_page.title, + href: url_for(only_path: true, + controller: "/wiki", + action: "show", + project_id: wiki_page.project.identifier, + id: wiki_page.slug) + ) ) - ) + end end private diff --git a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/referencing_pages.rb b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/referencing_pages.rb index ac1cff8e3944..f6f1f9862765 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/referencing_pages.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/referencing_pages.rb @@ -34,20 +34,22 @@ module Providers module Internal module Queries class ReferencingPages < BaseQuery - def call(input_data) + def call(input_data:, auth_strategy:) success( provider.page_links .merge(ReverseInlinePageLink.all) .where(linkable: input_data.linkable) .order(created_at: :desc) - .map { page_info(provider:, identifier: it.identifier) } + .map { page_info(identifier: it.identifier, auth_strategy:) } ) end private - def page_info(provider:, identifier:) - Adapters::Input::PageInfo.build(identifier:).bind { provider.resolve("queries.page_info").call(it) } + def page_info(identifier:, auth_strategy:) + Adapters::Input::PageInfo.build(identifier:).bind do |input| + provider.resolve("queries.page_info").call(input_data: input, auth_strategy:) + end end end end diff --git a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/relation_page_links.rb b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/relation_page_links.rb index db0e98cc9157..dfb93fc0296b 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/internal/queries/relation_page_links.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/internal/queries/relation_page_links.rb @@ -34,19 +34,21 @@ module Providers module Internal module Queries class RelationPageLinks < BaseQuery - def call(input_data) + def call(input_data:, auth_strategy:) page_link_infos = provider.page_links .merge(RelationPageLink.all) .where(linkable: input_data.linkable) - .map { |page_link| page_info(page_link.identifier) } + .map { |page_link| page_info(page_link.identifier, auth_strategy:) } success(page_link_infos) end private - def page_info(identifier) - Input::PageInfo.build(identifier:).bind { provider.resolve("queries.page_info").call(it) } + def page_info(identifier, auth_strategy:) + Input::PageInfo.build(identifier:).bind do |input| + provider.resolve("queries.page_info").call(input_data: input, auth_strategy:) + end end end end diff --git a/modules/wikis/app/services/wikis/adapters/providers/internal/registry.rb b/modules/wikis/app/services/wikis/adapters/providers/internal/registry.rb index ce4c026d3def..e6c5411b23da 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/internal/registry.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/internal/registry.rb @@ -34,7 +34,7 @@ module Providers module Internal Registry = Dry::Container::Namespace.new("internal") do namespace("authentication") do - # ... + register(:user_bound, Authentication::UserBound) end namespace("commands") do diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/authentication/user_bound.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/authentication/user_bound.rb new file mode 100644 index 000000000000..4e73c0e55926 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/authentication/user_bound.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module Wikis + module Adapters + module Providers + module XWiki + module Authentication + class UserBound + def initialize(_model:); end + + def call(user) + Input::Strategy.build(key: :bearer_token, user:) + end + end + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/page_info.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/page_info.rb index 4fa7e995e742..4d6df109037d 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/page_info.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/page_info.rb @@ -34,7 +34,7 @@ module Providers module XWiki module Queries class PageInfo < BaseQuery - def call(input_data) + def call(input_data:, _auth_strategy:) titles = [ "What makes XWiki special?", "API documentation", diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/referencing_pages.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/referencing_pages.rb index 97772c13e08b..1220269b05d4 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/referencing_pages.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/referencing_pages.rb @@ -34,7 +34,7 @@ module Providers module XWiki module Queries class ReferencingPages < BaseQuery - def call(input_data) + def call(input_data:, _auth_strategy:) # TODO: use real API endpoints once available title = [ diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/relation_page_links.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/relation_page_links.rb index 524270c712b7..4421cac4e454 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/relation_page_links.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/relation_page_links.rb @@ -34,7 +34,7 @@ module Providers module XWiki module Queries class RelationPageLinks < BaseQuery - def call(input_data) # rubocop:disable Metrics/AbcSize + def call(input_data:, _auth_strategy:) # rubocop:disable Metrics/AbcSize # TODO: use real API endpoints once available title = [ diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/user_query.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/user_query.rb index 6e10dbd9722f..7309bc1e4cff 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/user_query.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/queries/user_query.rb @@ -34,9 +34,11 @@ module Providers module XWiki module Queries class UserQuery < BaseQuery - def call(input_data) + def call(auth_strategy:) url = "#{provider.url.chomp('/')}/rest/" - handle_response(OpenProject.httpx.bearer_auth(input_data.access_token).get(url)) + Authentication[auth_strategy].call(provider:) do |http| + handle_response(http.get(url)) + end end private diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb index d05f417febfd..84572258088b 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb @@ -34,7 +34,7 @@ module Providers module XWiki Registry = Dry::Container::Namespace.new("xwiki") do namespace("authentication") do - # ... + register(:user_bound, Authentication::UserBound) end namespace("commands") do diff --git a/modules/wikis/app/services/wikis/page_link_service.rb b/modules/wikis/app/services/wikis/page_link_service.rb index c99015a23c60..bf4e0fdcc20b 100644 --- a/modules/wikis/app/services/wikis/page_link_service.rb +++ b/modules/wikis/app/services/wikis/page_link_service.rb @@ -41,9 +41,10 @@ def count(linkable) end def relation_page_link_infos_for(provider:, linkable:) + auth_strategy = provider.auth_strategy_for(User.current) Adapters::Input::RelationPageLinks.build(linkable:).bind do |input| provider.resolve("queries.relation_page_links") - .call(input) + .call(input_data: input, auth_strategy:) .either( ->(page_link_infos) { page_link_infos }, -> { [] } @@ -62,8 +63,9 @@ def referencing_wiki_page_infos_for(linkable:) Adapters::Input::ReferencingPages.build(linkable:).bind do |input| Provider.enabled.each do |provider| + auth_strategy = provider.auth_strategy_for(User.current) provider.resolve("queries.referencing_pages") - .call(input) + .call(input_data: input, auth_strategy:) # Only return page infos for successful results .fmap { referenced_in.concat(it) } end @@ -75,7 +77,10 @@ def referencing_wiki_page_infos_for(linkable:) private def page_info(provider:, identifier:) - Adapters::Input::PageInfo.build(identifier:).bind { provider.resolve("queries.page_info").call(it) } + auth_strategy = provider.auth_strategy_for(User.current) + Adapters::Input::PageInfo.build(identifier:).bind do |input| + provider.resolve("queries.page_info").call(input_data: input, auth_strategy:) + end end def page_title_service diff --git a/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb b/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb index 3d73ffd08572..2849efd9433d 100644 --- a/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb +++ b/modules/wikis/app/services/wikis/text_formatting/wiki_link_matcher.rb @@ -77,8 +77,10 @@ def process def resolve_page(provider, identifier) return Failure() if provider.nil? + auth_strategy = provider.auth_strategy_for(User.current) + Wikis::Adapters::Input::PageInfo.build(identifier:).bind do |input| - provider.resolve("queries.page_info").call(input) + provider.resolve("queries.page_info").call(input_data: input, auth_strategy:) end end diff --git a/modules/wikis/spec/services/wikis/adapters/authentication_spec.rb b/modules/wikis/spec/services/wikis/adapters/authentication_spec.rb new file mode 100644 index 000000000000..bcd17f5f79b4 --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/authentication_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::Authentication do + describe ".[]" do + subject(:strategy_object) { described_class[strategy] } + + context "with a bearer_token strategy" do + let(:strategy) { Wikis::Adapters::Input::Strategy.build(key: :bearer_token, user: build_stubbed(:user)) } + + it { is_expected.to be_a(Wikis::Adapters::AuthenticationStrategies::BearerToken) } + end + + context "with an internal strategy" do + let(:strategy) { Wikis::Adapters::Input::Strategy.build(key: :internal, user: build_stubbed(:user)) } + + it { is_expected.to be_a(Wikis::Adapters::AuthenticationStrategies::InternalUser) } + end + + context "with an unknown strategy (Failure monad)" do + let(:strategy) { Wikis::Adapters::Input::Strategy.build(key: :unknown) } + + it "raises ArgumentError" do + expect { strategy_object }.to raise_error(ArgumentError) + end + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/authentication_strategies/bearer_token_spec.rb b/modules/wikis/spec/services/wikis/adapters/authentication_strategies/bearer_token_spec.rb new file mode 100644 index 000000000000..da4e87a01f2f --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/authentication_strategies/bearer_token_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::AuthenticationStrategies::BearerToken, :webmock do + let(:url) { "https://xwiki.local/rest/" } + let(:user) { build_stubbed(:user) } + let(:oauth_client) { build_stubbed(:oauth_client) } + let(:provider) { instance_double(Wikis::XWikiProvider, oauth_client:) } + let(:oauth_client_token) { instance_double(OAuthClientToken, access_token: "test-token") } + + subject(:strategy) { described_class.new(user) } + + before do + allow(OAuthClientToken).to receive(:for_user_and_client).with(user, oauth_client) + .and_return(instance_double(ActiveRecord::Relation, first: oauth_client_token)) + end + + describe "#call" do + it "yields an http client configured with the bearer token" do + request_stub = stub_request(:get, url) + .with(headers: { "Authorization" => "Bearer test-token" }) + .to_return(status: 200, body: "") + + strategy.call(provider:) { |http| http.get(url) } + + expect(request_stub).to have_been_requested + end + + it "forwards http_options to the http client" do + request_stub = stub_request(:get, url) + .with(headers: { "Authorization" => "Bearer test-token", "Accept" => "application/json" }) + .to_return(status: 200, body: "") + + strategy.call(provider:, http_options: { headers: { "Accept" => "application/json" } }) { |http| http.get(url) } + + expect(request_stub).to have_been_requested + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/authentication_strategies/internal_user_spec.rb b/modules/wikis/spec/services/wikis/adapters/authentication_strategies/internal_user_spec.rb new file mode 100644 index 000000000000..6f7367c1493d --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/authentication_strategies/internal_user_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::AuthenticationStrategies::InternalUser do + let(:user) { build_stubbed(:user) } + + subject(:strategy) { described_class.new(user) } + + describe "#call" do + it "yields the user" do + yielded = nil + strategy.call { |u| yielded = u } + expect(yielded).to eq(user) + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/input/strategy_spec.rb b/modules/wikis/spec/services/wikis/adapters/input/strategy_spec.rb new file mode 100644 index 000000000000..34e8f1709f43 --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/input/strategy_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::Input::Strategy do + describe ".build" do + let(:user) { build_stubbed(:user) } + + context "with a :bearer_token key and a user" do + subject(:result) { described_class.build(key: :bearer_token, user:) } + + it { is_expected.to be_success.and have_attributes(value!: have_attributes(key: :bearer_token, user:)) } + end + + context "with an :internal key and a user" do + subject(:result) { described_class.build(key: :internal, user:) } + + it { is_expected.to be_success.and have_attributes(value!: have_attributes(key: :internal, user:)) } + end + + context "with an unknown key" do + subject(:result) { described_class.build(key: :unknown) } + + it { is_expected.to be_failure } + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/providers/internal/authentication/user_bound_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/internal/authentication/user_bound_spec.rb new file mode 100644 index 000000000000..d91d12d96297 --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/providers/internal/authentication/user_bound_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::Providers::Internal::Authentication::UserBound do + let(:provider) { build_stubbed(:internal_wiki_provider) } + let(:user) { build_stubbed(:user) } + + subject(:user_bound) { described_class.new(model: provider) } + + it "is registered" do + expect(Wikis::Adapters::Registry.resolve("internal.authentication.user_bound")).to eq(described_class) + end + + describe "#call" do + it "returns a Success with an internal strategy carrying the user" do + result = user_bound.call(user) + expect(result).to be_success + expect(result.value!).to have_attributes(key: :internal, user:) + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/page_info_query_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/page_info_query_spec.rb index 0775a571a856..393446278595 100644 --- a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/page_info_query_spec.rb +++ b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/page_info_query_spec.rb @@ -31,10 +31,11 @@ require "spec_helper" RSpec.describe Wikis::Adapters::Providers::Internal::Queries::PageInfo do - subject { described_class.new(model: provider).call(input_data) } + subject { described_class.new(model: provider).call(input_data:, auth_strategy:) } let(:provider) { create(:internal_wiki_provider) } let(:input_data) { Wikis::Adapters::Input::PageInfo.build(identifier:).value! } + let(:auth_strategy) { provider.auth_strategy_for(current_user) } let(:identifier) { wiki_page.id.to_s } let(:wiki_page) { create(:wiki_page) } diff --git a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/referencing_pages_query_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/referencing_pages_query_spec.rb index b49ed483d0c9..2b1936b359d5 100644 --- a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/referencing_pages_query_spec.rb +++ b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/referencing_pages_query_spec.rb @@ -31,10 +31,11 @@ require "spec_helper" RSpec.describe Wikis::Adapters::Providers::Internal::Queries::ReferencingPages do - subject { described_class.new(model: provider).call(input_data) } + subject { described_class.new(model: provider).call(input_data:, auth_strategy:) } let(:provider) { create(:internal_wiki_provider) } let(:input_data) { Wikis::Adapters::Input::ReferencingPages.build(linkable:).value! } + let(:auth_strategy) { provider.auth_strategy_for(current_user) } let(:linkable) { create(:work_package) } let(:wiki_page) { create(:wiki_page) } diff --git a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/relation_page_links_query_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/relation_page_links_query_spec.rb index fbf168eb3620..22042dac162f 100644 --- a/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/relation_page_links_query_spec.rb +++ b/modules/wikis/spec/services/wikis/adapters/providers/internal/queries/relation_page_links_query_spec.rb @@ -31,10 +31,11 @@ require "spec_helper" RSpec.describe Wikis::Adapters::Providers::Internal::Queries::RelationPageLinks do - subject { described_class.new(model: provider).call(input_data) } + subject { described_class.new(model: provider).call(input_data:, auth_strategy:) } let(:provider) { create(:internal_wiki_provider) } let(:input_data) { Wikis::Adapters::Input::RelationPageLinks.build(linkable: work_package).value! } + let(:auth_strategy) { provider.auth_strategy_for(current_user) } let(:wiki_page) { create(:wiki_page) } let(:project) { wiki_page.project } diff --git a/modules/wikis/spec/services/wikis/adapters/providers/xwiki/authentication/user_bound_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/authentication/user_bound_spec.rb new file mode 100644 index 000000000000..3eca41a4355f --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/authentication/user_bound_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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_module_spec_helper + +RSpec.describe Wikis::Adapters::Providers::XWiki::Authentication::UserBound do + let(:user) { build_stubbed(:user) } + + subject(:user_bound) { described_class.new(model: nil) } + + it "is registered" do + expect(Wikis::Adapters::Registry.resolve("xwiki.authentication.user_bound")).to eq(described_class) + end + + describe "#call" do + it "returns a Success with a bearer_token strategy carrying the user" do + result = user_bound.call(user) + expect(result).to be_success + expect(result.value!).to have_attributes(key: :bearer_token, user:) + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/providers/xwiki/queries/user_query_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/queries/user_query_spec.rb index ea0289699c35..6275e21b2b96 100644 --- a/modules/wikis/spec/services/wikis/adapters/providers/xwiki/queries/user_query_spec.rb +++ b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/queries/user_query_spec.rb @@ -32,12 +32,20 @@ require_module_spec_helper RSpec.describe Wikis::Adapters::Providers::XWiki::Queries::UserQuery, :webmock do + let(:user) { build_stubbed(:user) } + let(:oauth_client) { build_stubbed(:oauth_client) } let(:wiki_provider) { build_stubbed(:xwiki_provider, url: "https://xwiki.local/") } let(:rest_url) { "https://xwiki.local/rest/" } - let(:input_data) { Wikis::Adapters::Input::UserQuery.new(access_token: "some-token") } + let(:auth_strategy) { Wikis::Adapters::Input::Strategy.build(key: :bearer_token, user:) } subject(:query) { described_class.new(model: wiki_provider) } + before do + allow(wiki_provider).to receive(:oauth_client).and_return(oauth_client) + allow(OAuthClientToken).to receive(:for_user_and_client).with(user, oauth_client) + .and_return(instance_double(ActiveRecord::Relation, first: instance_double(OAuthClientToken, access_token: "some-token"))) + end + it "is registered" do expect(Wikis::Adapters::Registry.resolve("xwiki.queries.user")).to eq(described_class) end @@ -46,11 +54,12 @@ context "when the request succeeds with xwiki-user header" do before do stub_request(:get, rest_url) + .with(headers: { "Authorization" => "Bearer some-token" }) .to_return(status: 200, body: "", headers: { "xwiki-user" => "XWiki.admin" }) end it "returns Success with the xwiki-user header value" do - result = query.call(input_data) + result = query.call(auth_strategy:) expect(result).to be_success expect(result.value!).to eq("XWiki.admin") end @@ -63,7 +72,7 @@ end it "returns Failure with :unauthorized code" do - result = query.call(Wikis::Adapters::Input::UserQuery.new(access_token: "invalid-token")) + result = query.call(auth_strategy:) expect(result).to be_failure expect(result.failure).to have_attributes(code: :unauthorized) end @@ -75,7 +84,7 @@ end it "returns Failure with :request_failed code" do - result = query.call(input_data) + result = query.call(auth_strategy:) expect(result).to be_failure expect(result.failure).to have_attributes(code: :request_failed) end @@ -85,7 +94,7 @@ before { stub_request(:get, rest_url).to_timeout } it "returns Failure with :connection_error code" do - result = query.call(input_data) + result = query.call(auth_strategy:) expect(result).to be_failure expect(result.failure).to have_attributes(code: :connection_error) end