Skip to content

Commit

Permalink
[#48096] Create a storage command to create a (project) folder
Browse files Browse the repository at this point in the history
  • Loading branch information
ba1ash committed May 4, 2023
1 parent 433af27 commit 45ae800
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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::Peripherals::StorageInteraction::Nextcloud
class CreateFolderCommand < Storages::Peripherals::StorageInteraction::StorageCommand
include API::V3::Utilities::PathHelper
include Errors
using Storages::Peripherals::ServiceResultRefinements

def initialize(storage)
super()

@uri = URI(storage.host).normalize
@base_path = api_v3_paths.join_uri_path(@uri.path, "remote.php/dav/files", escape_whitespace(storage.username))
@groupfolder = storage.groupfolder
@username = storage.username
@password = storage.password
end

# rubocop:disable Metrics/AbcSize
def execute(folder:)
http = Net::HTTP.new(@uri.host, @uri.port)
http.use_ssl = @uri.scheme == 'https'

response = http.mkcol(
"#{@base_path}/#{@groupfolder}/#{requested_folder(folder)}",
nil,
{
'Authorization' => "Basic #{Base64::encode64("#{@username}:#{@password}")}"
}
)

case response
when Net::HTTPSuccess
ServiceResult.success(message: 'Folder was successfully created.')
when Net::HTTPMethodNotAllowed
if error_text_from_response(response) == 'The resource you tried to create already exists'
ServiceResult.success(message: 'Folder already exists.')
else
error(:not_allowed)
end
when Net::HTTPUnauthorized
error(:not_authorized)
when Net::HTTPNotFound
error(:not_found)
when Net::HTTPConflict
error(:conflict, error_text_from_response(response))
else
error(:error)
end
end
# rubocop:enable Metrics/AbcSize

private

def error_text_from_response(response)
Nokogiri::XML(response.body).xpath("//s:message").text
end

def escape_whitespace(value)
value.gsub(' ', '%20')
end

def requested_folder(folder)
raise ArgumentError.new("Folder can't be nil or empty string!") if folder.blank?

escape_whitespace(folder)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ def execute(folder:, permissions:)
}
)

response.is_a?(Net::HTTPSuccess) ? ServiceResult.success : error(response)
case response
when Net::HTTPSuccess
ServiceResult.success
when Net::HTTPNotFound
error(:not_found)
when Net::HTTPUnauthorized
error(:not_authorized)
else
error(:error)
end
end

private
Expand All @@ -78,24 +87,6 @@ def requested_folder(folder)
escape_whitespace(folder)
end

def error(response)
case response
when Net::HTTPNotFound
error_result(:not_found)
when Net::HTTPUnauthorized
error_result(:not_authorized)
else
error_result(:error)
end
end

def error_result(code, log_message = nil, data = nil)
ServiceResult.failure(
result: code, # This is needed to work with the ConnectionManager token refresh mechanism.
errors: Storages::StorageError.new(code:, log_message:, data:)
)
end

def converted_permissions(permissions:)
Nokogiri::XML::Builder.new do |xml|
xml['d'].propertyupdate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,16 @@ def set_permissions_command
raise ArgumentError
end
end

def create_folder_command
case @storage.provider_type
when ::Storages::Storage::PROVIDER_TYPE_NEXTCLOUD
ServiceResult.success(
result: ::Storages::Peripherals::StorageInteraction::Nextcloud::CreateFolderCommand.new(@storage)
)
else
raise ArgumentError
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ def set_permissions_command
.map { |command| command.method(:execute).to_proc }
end

def create_folder_command
storage_commands
.create_folder_command
.map { |command| command.method(:execute).to_proc }
end

private

def storage_queries(user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,28 +149,37 @@ def set_permissions
find_model_object(:projects_storage_id)
storage = @projects_storage.storage
project = @projects_storage.project
command = Storages::Peripherals::StorageRequests.new(storage:).set_permissions_command
folder = @projects_storage.project_folder_id
project_users = project.users
oauth_client = OAuthClient.where(integration_id: @projects_storage.storage_id, integration_type: 'Storages::Storage').first
nextcloud_users = OAuthClientToken.where(oauth_client:, user: project_users)
permissions = nextcloud_users.map do |token|
user = token.user
{
origin_user_id: token.origin_user_id,
permissions: {
read_files: user.allowed_to?(:read_files, @project),
write_files: user.allowed_to?(:write_files, @project),
create_files: user.allowed_to?(:create_files, @project),
share_files: user.allowed_to?(:share_files, @project),
delete_files: user.allowed_to?(:delete_files, @project)
}
}
end

command.result.call(folder:, permissions:).match(
on_success: ->(_) { flash[:notice] = 'Permissions were successfuly updated on the NextCloud side' }, # rubocop:disable Rails/I18nLocaleTexts
on_failure: ->(error) { flash[:error] = "Error: #{error}" }
create_folder_command = Storages::Peripherals::StorageRequests.new(storage:).create_folder_command
create_folder_command.result.call(folder:).match(
on_success: ->(_) do
project_users = project.users
oauth_client = OAuthClient.where(integration_id: @projects_storage.storage_id,
integration_type: 'Storages::Storage').first
nextcloud_users = OAuthClientToken.where(oauth_client:, user: project_users)
permissions = nextcloud_users.map do |token|
user = token.user
{
origin_user_id: token.origin_user_id,
permissions: {
read_files: user.allowed_to?(:read_files, @project),
write_files: user.allowed_to?(:write_files, @project),
create_files: user.allowed_to?(:create_files, @project),
share_files: user.allowed_to?(:share_files, @project),
delete_files: user.allowed_to?(:delete_files, @project)
}
}
end

set_permissions_command = Storages::Peripherals::StorageRequests.new(storage:).set_permissions_command
set_permissions_command.result.call(folder:, permissions:).match(
on_success: ->(_) { flash[:notice] = 'Permissions were successfuly updated on the NextCloud side' }, # rubocop:disable Rails/I18nLocaleTexts
on_failure: ->(error) { flash[:error] = "Error: #{error}" }
)
end,
on_failure: ->(error) do
flash[:error] = "Error: #{error}"
end
)
end
redirect_back(fallback_location: project_settings_projects_storages_path(project_id: project.id))
Expand Down
106 changes: 106 additions & 0 deletions modules/storages/spec/common/peripherals/storage_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,112 @@

subject { described_class.new(storage:) }

describe '#create_folder_command' do
before do
stub_request(:mkcol, "https://example.com/remote.php/dav/files/OpenProject/OpenProject/JediProject")
.with(
headers: {
'Authorization' => 'Basic T3BlblByb2plY3Q6T3BlblByb2plY3RTZWN1cmVQYXNzd29yZA=='
}
)
.to_return(expected_response)
end

context 'when folder does not exist yet' do
let(:expected_response) do
{
status: 201,
body: '',
headers: {}
}
end

it 'creates a folder and responds with a success' do
subject
.create_folder_command
.match(
on_success: ->(command) do
result = command.call(folder: 'JediProject')
expect(result).to be_success
expect(result.message).to eq("Folder was successfully created.")
end,
on_failure: ->(error) do
raise "Create folder command could not be executed: #{error}"
end
)
end
end

context 'when folder exists already' do
let(:expected_response_body) do
<<~XML
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
<s:exception>Sabre\\DAV\\Exception\\MethodNotAllowed</s:exception>
<s:message>The resource you tried to create already exists</s:message>
</d:error>
XML
end
let(:expected_response) do
{
status: 405,
body: expected_response_body,
headers: {}
}
end

it 'does not create a folder and responds with a success' do
subject
.create_folder_command
.match(
on_success: ->(command) do
result = command.call(folder: 'JediProject')
expect(result).to be_success
expect(result.message).to eq("Folder already exists.")
end,
on_failure: ->(error) do
raise "Create folder command could not be executed: #{error}"
end
)
end
end

context 'when parent folder is missing for any reason' do
let(:expected_response_body) do
<<~XML
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
<s:exception>Sabre\\DAV\\Exception\\Conflict</s:exception>
<s:message>Parent node does not exist</s:message>
</d:error>
XML
end
let(:expected_response) do
{
status: 409,
body: expected_response_body,
headers: {}
}
end

it 'does not create a folder and responds with a failure' do
subject
.create_folder_command
.match(
on_success: ->(command) do
result = command.call(folder: 'JediProject')
expect(result).to be_failure
expect(result.result).to eq(:conflict)
expect(result.errors.log_message).to eq('Parent node does not exist')
end,
on_failure: ->(error) do
raise "Create folder command could not be executed: #{error}"
end
)
end
end
end

describe '#set_permissions_command' do
let(:permissions) do
[
Expand Down

0 comments on commit 45ae800

Please sign in to comment.