diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/internal/propfind_query_legacy.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/internal/propfind_query_legacy.rb deleted file mode 100644 index 4214f3114773..000000000000 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/internal/propfind_query_legacy.rb +++ /dev/null @@ -1,124 +0,0 @@ -# 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 Storages - module Peripherals - module StorageInteraction - module Nextcloud - module Internal - class PropfindQueryLegacy - include TaggedLogging - def self.call(storage:, depth:, path:, props:) - new(storage).call(depth:, path:, props:) - end - - def initialize(storage) - @storage = storage - @username = storage.username - @password = storage.password - @group = storage.group - end - - # rubocop:disable Metrics/AbcSize - def call(depth:, path:, props:) - with_tagged_logger do - body = Nokogiri::XML::Builder.new do |xml| - xml["d"].propfind( - "xmlns:d" => "DAV:", - "xmlns:oc" => "http://owncloud.org/ns", - "xmlns:nc" => "http://nextcloud.org/ns" - ) do - xml["d"].prop do - props.each do |prop| - namespace, property = prop.split(":") - xml[namespace].public_send(property) - end - end - end - end.to_xml - - response = OpenProject - .httpx - .basic_auth(@username, @password) - .with(headers: { "Depth" => depth }) - .request( - "PROPFIND", - UrlBuilder.url(@storage.uri, "remote.php/dav/files", @username, path), - xml: body - ) - - error_data = StorageErrorData.new(source: self.class, payload: response) - - case response - in { status: 200..299 } - log_response(response) - info "Parsing XML response body" - doc = Nokogiri::XML(response.body.to_s) - info "Parsing response body" - result = doc.xpath("/d:multistatus/d:response").each_with_object({}) do |resource_section, hash| - source_path = UrlBuilder.path(@storage.uri.path, "/remote.php/dav/files", @username) - resource = CGI.unescape(resource_section.xpath("d:href").text.strip).gsub!(source_path, "") - - hash[resource] = {} - - # In future it could be useful to respond not only with found, but not found props as well - # resource_section.xpath("d:propstat[d:status[text() = 'HTTP/1.1 404 Not Found']]/d:prop/*") - resource_section.xpath("d:propstat[d:status[text() = 'HTTP/1.1 200 OK']]/d:prop/*").each do |node| - hash[resource][node.name.to_s] = node.text.strip - end - end - - info "Response parsed found: #{result.inspect}" - ServiceResult.success(result:) - in { status: 405 } - log_response(response) - Util.error(:not_allowed, "Outbound request method not allowed", error_data) - in { status: 401 } - log_response(response) - Util.error(:unauthorized, "Outbound request not authorized", error_data) - in { status: 404 } - log_response(response) - Util.error(:not_found, "Outbound request destination not found", error_data) - else - Util.error(:error, "Outbound request failed", error_data) - end - end - end - # rubocop:enable Metrics/AbcSize - - def log_response(response) - info "Storage responded with a #{response.status} code." - end - end - end - end - end - end -end diff --git a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/remove_user_from_group_command.rb b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/remove_user_from_group_command.rb index 4d15239c1b06..d25ddcff0e91 100644 --- a/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/remove_user_from_group_command.rb +++ b/modules/storages/app/common/storages/peripherals/storage_interaction/nextcloud/remove_user_from_group_command.rb @@ -34,61 +34,73 @@ module StorageInteraction module Nextcloud class RemoveUserFromGroupCommand include TaggedLogging - def self.call(storage:, user:, group: storage.group) - new(storage).call(user:, group:) + + def self.call(storage:, auth_strategy:, user:, group:) + new(storage).call(auth_strategy:, user:, group:) end def initialize(storage) @storage = storage - @username = storage.username - @password = storage.password - @group = storage.group end - # rubocop:disable Metrics/AbcSize - def call(user:, group: @group) + def call(auth_strategy:, user:, group:) with_tagged_logger do - url = UrlBuilder.url(@storage.uri, "ocs/v1.php/cloud/users", user, "groups") + - "?groupid=#{CGI.escapeURIComponent(group)}" + Authentication[auth_strategy].call(storage: @storage, http_options:) do |http| + url = UrlBuilder.url(@storage.uri, "ocs/v1.php/cloud/users", user, "groups") + url += "?groupid=#{CGI.escapeURIComponent(group)}" - info "Removing #{user} from #{group} through #{url}" + info "Removing #{user} from #{group} through #{url}" - response = OpenProject.httpx.basic_auth(@username, @password) - .with(headers: { "OCS-APIRequest" => "true" }) - .delete(url) + handle_response(http.delete(url)) + end + end + end - error_data = StorageErrorData.new(source: self.class, payload: response) + private - case response - in { status: 200..299 } - statuscode = Nokogiri::XML(response.body.to_s).xpath("/ocs/meta/statuscode").text - case statuscode - when "100" - info "User has been removed from group" - ServiceResult.success - when "101" - Util.error(:error, "No group specified", error_data) - when "102" - Util.error(:group_does_not_exist, "Group does not exist", error_data) - when "103" - Util.error(:user_does_not_exist, "User does not exist", error_data) - when "104" - Util.error(:insufficient_privileges, "Insufficient privileges", error_data) - when "105" - message = Nokogiri::XML(response.body).xpath("/ocs/meta/message").text - Util.error(:failed_to_remove, message, error_data) - end - in { status: 405 } - Util.error(:not_allowed, "Outbound request method not allowed", error_data) - in { status: 401 } - Util.error(:unauthorized, "Outbound request not authorized", error_data) - in { status: 404 } - Util.error(:not_found, "Outbound request destination not found", error_data) - in { status: 409 } - Util.error(:conflict, Util.error_text_from_response(response), error_data) - else - Util.error(:error, "Outbound request failed", error_data) - end + def http_options + Util.ocs_api_request + end + + def handle_response(response) + error_data = StorageErrorData.new(source: self.class, payload: response) + + case response + in { status: 200..299 } + handle_success_response(response) + in { status: 405 } + Util.error(:not_allowed, "Outbound request method not allowed", error_data) + in { status: 401 } + Util.error(:unauthorized, "Outbound request not authorized", error_data) + in { status: 404 } + Util.error(:not_found, "Outbound request destination not found", error_data) + in { status: 409 } + Util.error(:conflict, Util.error_text_from_response(response), error_data) + else + Util.error(:error, "Outbound request failed", error_data) + end + end + + # rubocop:disable Metrics/AbcSize + def handle_success_response(response) + error_data = StorageErrorData.new(source: self.class, payload: response) + + statuscode = Nokogiri::XML(response.body.to_s).xpath("/ocs/meta/statuscode").text + case statuscode + when "100" + info "User has been removed from group" + ServiceResult.success + when "101" + Util.error(:error, "No group specified", error_data) + when "102" + Util.error(:group_does_not_exist, "Group does not exist", error_data) + when "103" + Util.error(:user_does_not_exist, "User does not exist", error_data) + when "104" + Util.error(:insufficient_privileges, "Insufficient privileges", error_data) + when "105" + message = Nokogiri::XML(response.body).xpath("/ocs/meta/message").text + Util.error(:failed_to_remove, message, error_data) end end diff --git a/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb b/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb index 617ab1aec04d..45e8798aa045 100644 --- a/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb +++ b/modules/storages/app/services/storages/nextcloud_managed_folder_sync_service.rb @@ -122,10 +122,12 @@ def add_users_to_remote_group(users_to_add) end def remove_users_from_remote_group(users_to_remove) + group = @storage.group + users_to_remove.each do |user| - remove_user_from_group.call(storage: @storage, user:).error_and do |error| - add_error(:remove_user_from_group, error, options: { user:, group: @storage.group, reason: error.log_message }) - log_storage_error(error, group: @storage.group, user:, reason: error.log_message) + remove_user_from_group.call(storage: @storage, auth_strategy:, user:, group:).error_and do |error| + add_error(:remove_user_from_group, error, options: { user:, group:, reason: error.log_message }) + log_storage_error(error, group:, user:, reason: error.log_message) end end end diff --git a/modules/storages/spec/common/storages/peripherals/registry_spec.rb b/modules/storages/spec/common/storages/peripherals/registry_spec.rb index 4f614910bb0e..bcf7ee97c0f5 100644 --- a/modules/storages/spec/common/storages/peripherals/registry_spec.rb +++ b/modules/storages/spec/common/storages/peripherals/registry_spec.rb @@ -147,72 +147,6 @@ end end - describe "#remove_user_from_group" do - let(:expected_response) do - { - status: 200, - body: expected_response_body, - headers: {} - } - end - let(:expected_response_body) do - <<~XML - - - - ok - 100 - OK - - - - - - XML - end - - before do - stub_request(:delete, "https://example.com/ocs/v1.php/cloud/users/#{origin_user_id}/groups?groupid=#{storage.group}") - .with( - headers: { - "Authorization" => "Basic T3BlblByb2plY3Q6T3BlblByb2plY3RTZWN1cmVQYXNzd29yZA==", - "OCS-APIRequest" => "true" - } - ) - .to_return(expected_response) - end - - it "removes user from the group" do - result = registry.resolve("nextcloud.commands.remove_user_from_group").call(storage:, user: origin_user_id) - expect(result).to be_success - end - - context "when Nextcloud reponds with 105 code in the response body" do - let(:expected_response_body) do - <<~XML - - - - failure - 105 - Not viable to remove user from the last group you are SubAdmin of - - - - - - XML - end - - it "responds with a failure and parses message from the xml response" do - result = registry.resolve("nextcloud.commands.remove_user_from_group").call(storage:, user: origin_user_id) - expect(result).to be_failure - expect(result.errors.log_message) - .to eq("Not viable to remove user from the last group you are SubAdmin of") - end - end - end - describe "#delete_folder_command" do let(:auth_strategy) { Storages::Peripherals::StorageInteraction::AuthenticationStrategies::BasicAuth.strategy } diff --git a/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb b/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb index ee1a1a10edbe..0e9c207829af 100644 --- a/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb +++ b/modules/storages/spec/services/storages/nextcloud_managed_folder_sync_service_spec.rb @@ -188,7 +188,11 @@ module Storages allow(group_users).to receive(:call).with(storage:, group: storage.group).and_return(group_users_result) # Updating the group users allow(add_user).to receive(:call).with(storage:, user: "single_project_user").and_return(add_user_result) - allow(remove_user).to receive(:call).with(storage:, user: "cookiemonster").and_return(remove_user_result) + allow(remove_user).to receive(:call).with(storage:, + auth_strategy:, + user: "cookiemonster", + group: storage.group) + .and_return(remove_user_result) end it "applies changes to all project storages linked to the passed storage" do