Adds the copyright blocks

This commit is contained in:
Marcello Rocha
2024-09-10 15:24:03 +02:00
committed by Marcello Rocha
parent ac08605ef6
commit fbe1215365
23 changed files with 251 additions and 97 deletions
+6 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -33,10 +35,10 @@ module API
code 500
def initialize(error_message = nil, exception: nil, **)
error = I18n.t("api_v3.errors.code_500")
error = error_message
if error_message && visible_exception?(exception)
error += " #{error_message}"
error.prepend(I18n.t("api_v3.errors.code_500"))
end
super(error)
@@ -47,6 +49,8 @@ module API
##
# Hide internal database errors in production
def visible_exception?(exception)
return false if exception.nil?
exception_blacklist.none? do |clz|
exception.is_a?(clz)
end
@@ -67,7 +67,7 @@ module Storages
namespace("authentication") do
register(:userless, StorageInteraction::AuthenticationStrategies::NextcloudStrategies::UserLess, call: false)
register(:userbound, StorageInteraction::AuthenticationStrategies::NextcloudStrategies::UserBound)
register(:user_bound, StorageInteraction::AuthenticationStrategies::NextcloudStrategies::UserBound)
end
end
end
@@ -61,7 +61,7 @@ module Storages
namespace("authentication") do
register(:userless, StorageInteraction::AuthenticationStrategies::OneDriveStrategies::UserLess, call: false)
register(:userbound, StorageInteraction::AuthenticationStrategies::OneDriveStrategies::UserBound)
register(:user_bound, StorageInteraction::AuthenticationStrategies::OneDriveStrategies::UserBound)
end
end
end
@@ -37,22 +37,22 @@ module Storages::Peripherals
fail ::API::Errors::MultipleErrors.create_if_many(api_errors)
end
# rubocop:disable Metrics/AbcSize
def handle_base_errors(errors)
base_errors = errors.symbols_for(:base)
message = errors.full_messages_for(:base)&.first
if base_errors.include? :not_found
fail API::Errors::OutboundRequestNotFound.new(errors.full_messages_for(:base).first)
fail API::Errors::OutboundRequestNotFound.new(message)
elsif base_errors.include? :unauthorized
fail ::API::Errors::Unauthenticated.new(errors.full_messages_for(:base).first)
fail ::API::Errors::Unauthenticated.new(message)
elsif base_errors.include? :forbidden
fail API::Errors::OutboundRequestForbidden.new(errors.full_messages_for(:base).first)
fail API::Errors::OutboundRequestForbidden.new(message)
elsif base_errors.include? :error
fail API::Errors::InternalError.new(errors.full_messages)
fail API::Errors::InternalError.new(message)
else
base_errors
end
end
# rubocop:enable Metrics/AbcSize
def raise_error(error)
Rails.logger.error(error)
@@ -1,6 +1,31 @@
# 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
@@ -46,6 +46,7 @@ module Storages
def call(auth_strategy:, upload_data:)
with_tagged_logger do
Authentication[auth_strategy].call(storage: @storage) do |http|
info "Requesting an upload link on folder #{upload_data.folder_id}"
handle_response http.post(url(upload_data.folder_id, upload_data.file_name),
json: payload(upload_data.file_name))
end
@@ -67,6 +68,7 @@ module Storages
case response
in { status: 200..299 }
upload_url = response.json(symbolize_keys: true)[:uploadUrl]
info "Upload link generated successfully."
ServiceResult.success(result: UploadLink.new(URI(upload_url), :put))
in { status: 404 | 400 } # not existent parent folder in request url is responded with 400
info "The parent folder was not found."
@@ -1,16 +0,0 @@
# frozen_string_literal: true
#-- copyright
#++
module Storages
module Peripherals
module StorageInteraction
module Types
include Dry::Types()
ParentFolderType = Constructor(ParentFolder, ParentFolder.method(:build))
end
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,6 +31,8 @@
module Storages
module Peripherals
ParentFolder = Data.define(:path) do
delegate :split, :empty?, to: :path
def root?
path == "/"
end
@@ -188,6 +188,10 @@ module Storages
@short_provider_type ||= self.class.shorten_provider_type(provider_type)
end
def to_s
short_provider_type
end
def provider_type_nextcloud?
provider_type == ::Storages::Storage::PROVIDER_TYPE_NEXTCLOUD
end
@@ -39,6 +39,8 @@ module Storages
def call(file_links)
with_tagged_logger do
info "Starting File Link remote synchronization"
resulting_file_links = file_links
.group_by(&:storage_id)
.map { |storage_id, storage_file_links| sync_storage_data(storage_id, storage_file_links) }
@@ -49,17 +51,21 @@ module Storages
)
end
ServiceResult.success(result: resulting_file_links)
@result.result = resulting_file_links
info "File Link Synchronization successful"
@result
end
end
private
def sync_storage_data(storage_id, file_links)
storage = ::Storages::Storage.find(storage_id)
::Storages::Peripherals::Registry
.resolve("#{storage.short_provider_type}.queries.files_info")
.call(storage:, auth_strategy:, file_ids: file_links.map(&:origin_id))
storage = Storage.find(storage_id)
info "Retrieving file link information from #{storage.name}"
Peripherals::Registry
.resolve("#{storage}.queries.files_info")
.call(storage:, auth_strategy: strategy(storage), file_ids: file_links.map(&:origin_id))
.map { |file_infos| to_hash(file_infos) }
.match(
on_success: set_file_link_status(file_links),
@@ -72,10 +78,8 @@ module Storages
)
end
def auth_strategy
Storages::Peripherals::StorageInteraction::AuthenticationStrategies::OAuthUserToken
.strategy
.with_user(@user)
def strategy(storage)
Peripherals::Registry.resolve("#{storage}.authentication.user_bound").call(user: @user)
end
def to_hash(file_infos)
@@ -83,24 +87,24 @@ module Storages
end
def set_file_link_status(file_links)
info "Updating file link status..."
lambda do |file_infos|
resulting_file_links = []
file_links.each do |file_link|
file_info = file_infos[file_link.origin_id]
case file_info.status_code
when 200
update_file_link(file_link, file_info)
file_link.origin_status = :view_allowed
when 403
file_link.origin_status = :view_not_allowed
when 404
file_link.origin_status = :not_found
else
file_link.origin_status = :error
end
file_link.origin_status = case file_info.status_code
when 200
update_file_link(file_link, file_info)
:view_allowed
when 403
:view_not_allowed
when 404
:not_found
else
:error
end
resulting_file_links << file_link
file_link.save
@@ -1,6 +1,31 @@
# 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
@@ -50,7 +75,7 @@ module Storages
end
def auth_strategy(user)
Peripherals::Registry.resolve("#{@storage.short_provider_type}.authentication.userbound").call(user:)
Peripherals::Registry.resolve("#{@storage.short_provider_type}.authentication.user_bound").call(user:)
end
end
end
+3 -2
View File
@@ -95,10 +95,9 @@ en:
error: An unexpected error occurred. Please check OpenProject logs for more information or contact an administrator
unauthorized: OpenProject could not authenticate with the Storage Provider. Please ensure that you have access to it.
models:
upload_link_service:
not_found: The destination folder %{folder} could not be found on %{storage_name}.
copy_project_folders_service:
conflict: The folder %{destination_path} already exists. Interrupting process to avoid overwrites.
error: An unexpected error occurred. Please check OpenProject logs for more information or contact an administrator
forbidden: OpenProject could not access the source folder. Please check your permissions configuration on the Storage Provider
not_found: The source template location %{source_path} wasn't found.
unauthorized: OpenProject could not authenticate with the Storage Provider. Please check your storage configuration
@@ -151,6 +150,8 @@ en:
not_allowed: OpenProject wasn't allowed to access your OneDrive drive. Please check the permissions set on the Azure Application.
unauthorized: OpenProject could not sync with OneDrive. Please check you storage and Azure Application configuration.
user_does_not_exist: "%{user} does not exist in Nextcloud."
upload_link_service:
not_found: The destination folder %{folder} could not be found on %{storage_name}.
storages:
buttons:
complete_without_setup: Complete without it
@@ -59,7 +59,7 @@ module API::V3::StorageFiles
def auth_strategy
Storages::Peripherals::Registry
.resolve("#{@storage.short_provider_type}.authentication.userbound")
.resolve("#{@storage.short_provider_type}.authentication.user_bound")
.call(user: current_user)
end
end
@@ -0,0 +1,103 @@
# 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 Storages::Peripherals::StorageInteraction::Inputs::UploadData do
subject(:input) { described_class }
it "has .new as a private method" do
expect { input.new(nil, nil) }.to raise_error NoMethodError
end
it "returns a Success(UploadData)" do
result = input.build(folder_id: "/Folder/Subfolder", file_name: "i_am_file_with_a_name.txt")
expect(result).to be_success
upload_data = result.value!
expect(upload_data.folder_id).to eq(Storages::Peripherals::ParentFolder.new("/Folder/Subfolder"))
expect(upload_data.file_name).to eq("i_am_file_with_a_name.txt")
end
context "when invalid" do
context "with a nil file name" do
let(:kwargs) { { folder_id: "/folder", file_name: nil } }
it "returns a failure" do
result = input.build(**kwargs)
expect(result).to be_failure
end
it "contains the specific error" do
validation_result = input.build(**kwargs).failure
expect(validation_result.errors[:file_name]).to eq(["must be filled"])
end
end
context "with a empty file name" do
let(:kwargs) { { folder_id: "/folder", file_name: "" } }
it "returns a failure" do
result = input.build(**kwargs)
expect(result).to be_failure
end
it "contains the specific error" do
validation_result = input.build(**kwargs).failure
expect(validation_result.errors[:file_name]).to eq(["must be filled"])
end
end
context "with an empty folder_id" do
let(:kwargs) { { folder_id: "", file_name: "file_name.txt" } }
it "returns a failure" do
result = input.build(**kwargs)
expect(result).to be_failure
end
it "contains the specific error" do
validation_result = input.build(**kwargs).failure
expect(validation_result.errors[:folder_id]).to eq(["must be filled"])
end
end
context "with a nil folder id" do
let(:kwargs) { { folder_id: nil, file_name: "file_name.txt" } }
it "returns a failure" do
result = input.build(**kwargs)
expect(result).to be_failure
expect(result.failure.errors[:folder_id]).to eq(["must be filled"])
end
end
end
end
@@ -36,7 +36,7 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::RenameFileC
let(:storage) do
create(:nextcloud_storage_with_local_connection, :as_not_automatically_managed, oauth_client_token_user: user)
end
let(:auth_strategy) { Storages::Peripherals::Registry.resolve("nextcloud.authentication.userbound").call(user:) }
let(:auth_strategy) { Storages::Peripherals::Registry.resolve("nextcloud.authentication.user_bound").call(user:) }
it_behaves_like "rename_file_command: basic command setup"
@@ -42,11 +42,10 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::UploadLinkQ
it_behaves_like "upload_link_query: basic query setup"
it_behaves_like "upload_link_query: validating input data"
context "when requesting an upload link for an existing file", vcr: "nextcloud/upload_link_success" do
let(:upload_data) do
Storages::UploadData.new(folder_id: "169", file_name: "DeathStart_blueprints.tiff")
Storages::Peripherals::StorageInteraction::Inputs::UploadData
.build(folder_id: "169", file_name: "DeathStart_blueprints.tiff").value!
end
let(:token) { "SrQJeC5zM3B5Gw64d7dEQFQpFw8YBAtZWoxeLb59AR7PpGPyoGAkAko5G6ZiZ2HA" }
let(:upload_url) { "https://nextcloud.local/index.php/apps/integration_openproject/direct-upload/#{token}" }
@@ -57,7 +56,8 @@ RSpec.describe Storages::Peripherals::StorageInteraction::Nextcloud::UploadLinkQ
context "when requesting an upload link for a not existing file", vcr: "nextcloud/upload_link_not_found" do
let(:upload_data) do
Storages::UploadData.new(folder_id: "1337", file_name: "DeathStart_blueprints.tiff")
Storages::Peripherals::StorageInteraction::Inputs::UploadData
.build(folder_id: "1337", file_name: "DeathStart_blueprints.tiff").value!
end
let(:error_source) { described_class }
@@ -145,7 +145,7 @@ RSpec.describe Storages::Peripherals::StorageInteraction::OneDrive::CopyTemplate
subfolder = command.call(auth_strategy:, folder_name: "Subfolder with File", parent_location:).result
file_name = "files_query_root.yml"
upload_data = Storages::UploadData.new(folder_id: subfolder.id, file_name:)
upload_data = Storages::Peripherals::StorageInteraction::Inputs::UploadData.build(folder_id: subfolder.id, file_name:).value!
upload_link = Storages::Peripherals::Registry
.resolve("one_drive.queries.upload_link")
.call(storage:, auth_strategy:, upload_data:)
@@ -36,7 +36,7 @@ RSpec.describe Storages::Peripherals::StorageInteraction::OneDrive::FileInfoQuer
let(:storage) { create(:sharepoint_dev_drive_storage, oauth_client_token_user: user) }
let(:auth_strategy) do
Storages::Peripherals::Registry["one_drive.authentication.userbound"].call(user:)
Storages::Peripherals::Registry["one_drive.authentication.user_bound"].call(user:)
end
it_behaves_like "file_info_query: basic query setup"
@@ -38,7 +38,7 @@ RSpec.describe Storages::Peripherals::StorageInteraction::OneDrive::OpenFileLink
let(:storage) { create(:sharepoint_dev_drive_storage, oauth_client_token_user: user) }
let(:file_id) { "01AZJL5PJTICED3C5YSVAY6NWTBNA2XERU" }
let(:auth_strategy) do
Storages::Peripherals::Registry.resolve("one_drive.authentication.userbound").call(user:)
Storages::Peripherals::Registry.resolve("one_drive.authentication.user_bound").call(user:)
end
subject { described_class.new(storage) }
@@ -39,11 +39,10 @@ RSpec.describe Storages::Peripherals::StorageInteraction::OneDrive::UploadLinkQu
it_behaves_like "upload_link_query: basic query setup"
it_behaves_like "upload_link_query: validating input data"
context "when requesting an upload link for an existing file", vcr: "one_drive/upload_link_success" do
let(:upload_data) do
Storages::UploadData.new(folder_id: "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", file_name: "DeathStart_blueprints.tiff")
Storages::Peripherals::StorageInteraction::Inputs::UploadData
.build(folder_id: "01AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", file_name: "DeathStart_blueprints.tiff").value!
end
let(:token) do
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfZGlzcGxheW5hbWUiOiJPcGVuUHJvamVjdCBEZXYgQXBwIiwiYXVkIjoiMDAwMDA" \
@@ -69,7 +68,8 @@ RSpec.describe Storages::Peripherals::StorageInteraction::OneDrive::UploadLinkQu
context "when requesting an upload link for a not existing file", vcr: "one_drive/upload_link_not_found" do
let(:upload_data) do
Storages::UploadData.new(folder_id: "04AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", file_name: "DeathStart_blueprints.tiff")
Storages::Peripherals::StorageInteraction::Inputs::UploadData
.build(folder_id: "04AZJL5PN6Y2GOVW7725BZO354PWSELRRZ", file_name: "DeathStart_blueprints.tiff").value!
end
let(:error_source) { described_class }
@@ -291,7 +291,13 @@ RSpec.describe "API v3 storage files", :webmock, content_type: :json do
describe "due to internal error" do
let(:error) { :error }
it { expect(last_response).to have_http_status(:internal_server_error) }
it "fails with an internal error" do
expect(last_response).to have_http_status(:internal_server_error)
body = MultiJson.load(last_response.body, symbolize_keys: true)
expect(body[:message]).to eq(I18n.t("services.errors.messages.error"))
expect(body[:errorIdentifier]).to eq("urn:openproject-org:api:v3:errors:InternalServerError")
end
end
describe "due to not found" do
@@ -1,4 +1,29 @@
#-- 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"
@@ -81,36 +81,3 @@ RSpec.shared_examples_for "upload_link_query: error" do
expect(error.data.source).to eq(error_source)
end
end
RSpec.shared_examples_for "upload_link_query: validating input data" do
let(:upload_data) { Storages::UploadData.new(folder_id:, file_name:) }
let(:error_source) { described_class }
context "if folder id being empty" do
let(:folder_id) { "" }
let(:file_name) { "DeathStart_blueprints.tiff" }
it_behaves_like "upload_link_query: error"
end
context "if folder id being nil" do
let(:folder_id) { nil }
let(:file_name) { "DeathStart_blueprints.tiff" }
it_behaves_like "upload_link_query: error"
end
context "if file name being empty" do
let(:folder_id) { "42" }
let(:file_name) { "" }
it_behaves_like "upload_link_query: error"
end
context "if file name being nil" do
let(:folder_id) { "42" }
let(:file_name) { nil }
it_behaves_like "upload_link_query: error"
end
end