mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Rework of Storages Registry based approach (#17881)
* Re-creates the Registry and Errors under the Adapters namespace. * Bring Authentication and Strategies to Adapters * Make Strategies work with Result and clean up a bit of the code * Setup SetPermissions Command and tests * Moves create folder, need to add the input value * Adds the create folder input * RenameFile migrated * Files Query and some Result Objects * Gets the sync service working with the new commands/query * UploadLinkQuery ported * FileInfoQuery ported * FilePathToIdMap moved * Cleanup unused files and warnings * Moves DeleteFolder. Updates tests of OneDriveSyncService * Add some tests for the the inputs * Start moving the bare minimum for the NextcloudSync * Moves nextcloud FilePathToIdMap * Create and Delete Folder nextcloud commands * Port Nextcloud FileInfo and RenameFile * Implements the changes necessary for create folder on the file picker * Moves the CreateFolderService to the Adapters * Move Nextcloud SetPermissions * AuthCheck moved. Missing teests. Slowly moving the API to Adapters * Adds note to figure out the open queries * Move the user and group manipulation to adapters * Moves Nextcloud FilesQuery * Makes NextcloudSync to run on top of the new Adapter namespace * Disable Peripherals::Registry * Update CopyTemplateFolderService * Makes services green again. Moves the new Nextcloud contract to Adapters * Moves the new nextcloud contracts and fixes some the now broken tests * Reintroduces the Internal namespace in OneDrive. Updates the contracts for Strategy to optionally take a storage (OIDC issues) * Moves User and DownloadLink Queries and supporting code. * Start to move the API over the new commands/queries * Migrates the StorgeFilesAPI to the adapters * FileLinksAPI cleared * Updates the Storages API specs and implementations * OpenStorage API done * Update capabilities query * Move connection validators and fix some broken tests * Delete old code, update hidden dependencies. * Adds missing handling for sso tokens
This commit is contained in:
@@ -211,7 +211,6 @@ gem "mini_magick", "~> 5.2.0", require: false
|
||||
gem "validate_url"
|
||||
|
||||
# Storages support code
|
||||
gem "dry-auto_inject"
|
||||
gem "dry-container"
|
||||
gem "dry-monads"
|
||||
gem "dry-validation"
|
||||
|
||||
@@ -481,9 +481,6 @@ GEM
|
||||
dotenv (= 3.1.8)
|
||||
railties (>= 6.1)
|
||||
drb (2.2.3)
|
||||
dry-auto_inject (1.1.0)
|
||||
dry-core (~> 1.1)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-configurable (1.3.0)
|
||||
dry-core (~> 1.1)
|
||||
zeitwerk (~> 2.6)
|
||||
@@ -1382,7 +1379,6 @@ DEPENDENCIES
|
||||
disposable (~> 0.6.2)
|
||||
doorkeeper (~> 5.8.0)
|
||||
dotenv-rails
|
||||
dry-auto_inject
|
||||
dry-container
|
||||
dry-monads
|
||||
dry-validation
|
||||
@@ -1655,7 +1651,6 @@ CHECKSUMS
|
||||
dotenv (3.1.8) sha256=9e1176060ced581f8e6ce4384e91361817763a76e3c625c8bddc18b35bd392c3
|
||||
dotenv-rails (3.1.8) sha256=46c9d1226a8b58a83b5f61325aa8cffd25cea1c0fafdfbbbee1e5dfea77980c4
|
||||
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||
dry-auto_inject (1.1.0) sha256=f9276cb5d15a3ef138e1f1149e289e287f636de57ef4a6decd233542eb708f78
|
||||
dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8
|
||||
dry-container (0.11.0) sha256=23be9381644d47343f3bf13b082b4255994ada0bfd88e0737eaaadc99d035229
|
||||
dry-core (1.1.0) sha256=0903821a9707649a7da545a2cd88e20f3a663ab1c5288abd7f914fa7751ab195
|
||||
|
||||
@@ -82,8 +82,7 @@ class OAuthClientsController < ApplicationController
|
||||
storage = oauth_client.integration
|
||||
# check if the origin is the same
|
||||
destination_url = destination_url(params.fetch(:destination_url, ""))
|
||||
auth_state = ::Storages::Peripherals::StorageInteraction::Authentication
|
||||
.authorization_state(storage:, user: User.current)
|
||||
auth_state = ::Storages::Adapters::Authentication.authorization_state(storage:, user: User.current)
|
||||
|
||||
if auth_state == :connected
|
||||
redirect_to(destination_url)
|
||||
|
||||
@@ -35,4 +35,7 @@ class RemoteIdentity < ApplicationRecord
|
||||
|
||||
validates :user, uniqueness: { scope: %i[auth_source integration] }
|
||||
validates :origin_user_id, :user, :auth_source, :integration, presence: true
|
||||
|
||||
# FIXME: This needs a better name - 2025.03.18 @mereghost
|
||||
scope :of_user_and_client, ->(user, client, integration) { find_by(user:, auth_source: client, integration:) }
|
||||
end
|
||||
|
||||
@@ -317,6 +317,10 @@ class User < Principal
|
||||
authentication_provider&.display_name
|
||||
end
|
||||
|
||||
def provided_by_oidc?
|
||||
authentication_provider.is_a?(OpenIDConnect::Provider)
|
||||
end
|
||||
|
||||
##
|
||||
# Allows the API and other sources to determine locking actions
|
||||
# on represented collections of children of Principals.
|
||||
|
||||
@@ -47,10 +47,11 @@ module RemoteIdentities
|
||||
|
||||
def call
|
||||
if @model.new_record? || @force_update
|
||||
user_id = @integration.extract_origin_user_id(@token)
|
||||
return user_id if user_id.failure?
|
||||
origin_result = @integration.extract_origin_user_id(@token)
|
||||
|
||||
@model.origin_user_id = user_id.result
|
||||
user_id = origin_result.value_or { return ServiceResult.failure(errors: it) }
|
||||
|
||||
@model.origin_user_id = user_id
|
||||
return success unless @model.changed?
|
||||
return failure unless @model.save
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ module API
|
||||
code 500
|
||||
|
||||
def initialize(message = I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 403))
|
||||
super
|
||||
super(message || I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 403))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ module API
|
||||
code 500
|
||||
|
||||
def initialize(message = I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 404))
|
||||
super
|
||||
super(message || I18n.t("api_v3.errors.code_500_outbound_request_failure", status_code: 404))
|
||||
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
|
||||
@@ -33,7 +35,7 @@ module API
|
||||
code 401
|
||||
|
||||
def initialize(message = I18n.t("api_v3.errors.code_401"))
|
||||
super
|
||||
super(message || I18n.t("api_v3.errors.code_401"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module AdapterTypes
|
||||
include Dry.Types()
|
||||
|
||||
# We need to move the definition of ParentFolder to mean something like Folder
|
||||
Location = AdapterTypes.Constructor(Peripherals::ParentFolder)
|
||||
StorageFileInstance = AdapterTypes.Instance(Results::StorageFile)
|
||||
SemanticVersionType = AdapterTypes.Constructor(SemanticVersion, SemanticVersion.method(:parse))
|
||||
HTTPVerb = AdapterTypes::Nominal::Symbol.constrained(included_in: %i(post put))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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 Adapters
|
||||
class Authentication
|
||||
class << self
|
||||
# @param strategy [Input::Strategy]
|
||||
# @return [AuthenticationStrategy]
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def [](strategy)
|
||||
auth = strategy.value_or { |it| raise ArgumentError, "Invalid authentication strategy '#{it.inspect}'" }
|
||||
|
||||
case auth.key
|
||||
when :noop
|
||||
AuthenticationStrategies::Noop.new
|
||||
when :basic_auth
|
||||
AuthenticationStrategies::BasicAuth.new
|
||||
when :bearer_token
|
||||
AuthenticationStrategies::BearerToken.new(auth.token)
|
||||
when :oauth_user_token
|
||||
AuthenticationStrategies::OAuthUserToken.new(auth.user)
|
||||
when :oauth_client_credentials
|
||||
AuthenticationStrategies::OAuthClientCredentials.new(auth.use_cache)
|
||||
when :sso_user_token
|
||||
AuthenticationStrategies::SsoUserToken.new(auth.user)
|
||||
else
|
||||
raise Errors::UnknownAuthenticationStrategy, "Unknown #{auth.key} authentication scheme"
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# TODO: Needs update for OIDC. Add tests for this.
|
||||
# Used only on the API. Should it become a service? - 2025-01-15 @mereghost
|
||||
def authorization_state(storage:, user:)
|
||||
auth_strategy = Registry["#{storage}.authentication.user_bound"].call(user, storage)
|
||||
|
||||
Registry.resolve("#{storage}.queries.user")
|
||||
.call(storage:, auth_strategy:)
|
||||
.either(
|
||||
->(*) { :connected },
|
||||
->(error) { handle_error(error) }
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_error(error)
|
||||
case error.code
|
||||
when :unauthorized
|
||||
:failed_authorization
|
||||
when :missing_token
|
||||
:not_connected
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+44
@@ -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 Storages
|
||||
module Adapters
|
||||
module AuthenticationStrategies
|
||||
class AuthenticationStrategy
|
||||
include TaggedLogging
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(**)
|
||||
raise Errors::SubclassResponsibility
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+15
-16
@@ -29,25 +29,24 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module AuthenticationStrategies
|
||||
class SpecificBearerToken
|
||||
def self.strategy
|
||||
Strategy.new(:bearer_token)
|
||||
end
|
||||
module Adapters
|
||||
module AuthenticationStrategies
|
||||
# Authenticates on a Storage Provider using Basic Auth.
|
||||
# It expects that the [Storage] to have a [#username] and [#password] set onto it.
|
||||
class BasicAuth < AuthenticationStrategy
|
||||
def call(storage:, http_options: {})
|
||||
username = storage.username
|
||||
password = storage.password
|
||||
|
||||
attr_reader :bearer_token
|
||||
return build_failure(storage) if username.blank? || password.blank?
|
||||
|
||||
def initialize(bearer_token)
|
||||
@bearer_token = bearer_token
|
||||
end
|
||||
yield OpenProject.httpx.basic_auth(username, password).with(http_options)
|
||||
end
|
||||
|
||||
# rubocop:disable Lint/UnusedMethodArgument
|
||||
def call(storage:, http_options: {})
|
||||
yield OpenProject.httpx.bearer_auth(bearer_token).with(http_options)
|
||||
end
|
||||
# rubocop:enable Lint/UnusedMethodArgument
|
||||
private
|
||||
|
||||
def build_failure(storage)
|
||||
Failure(Results::Error.new(source: self.class, payload: storage, code: :missing_credentials))
|
||||
end
|
||||
end
|
||||
end
|
||||
+46
@@ -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 Storages
|
||||
module Adapters
|
||||
module AuthenticationStrategies
|
||||
class BearerToken < AuthenticationStrategy
|
||||
def initialize(token)
|
||||
super()
|
||||
@token = token
|
||||
end
|
||||
|
||||
def call(http_options: {}, **)
|
||||
yield OpenProject.httpx.bearer_auth(@token).with(http_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
-14
@@ -29,20 +29,11 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module AuthenticationStrategies
|
||||
class Noop
|
||||
def self.strategy
|
||||
Strategy.new(:noop)
|
||||
end
|
||||
|
||||
# rubocop:disable Lint/UnusedMethodArgument
|
||||
def call(storage:, http_options: {})
|
||||
yield OpenProject.httpx.with(http_options)
|
||||
end
|
||||
|
||||
# rubocop:enable Lint/UnusedMethodArgument
|
||||
module Adapters
|
||||
module AuthenticationStrategies
|
||||
class Noop < AuthenticationStrategy
|
||||
def call(http_options: {}, **)
|
||||
yield OpenProject.httpx.with(http_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# 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 Adapters
|
||||
module AuthenticationStrategies
|
||||
class OAuthClientCredentials < AuthenticationStrategy
|
||||
TOKEN_CACHE_KEY = "storage.%s.httpx_access_token"
|
||||
|
||||
def initialize(use_cache)
|
||||
super()
|
||||
@use_cache = use_cache
|
||||
end
|
||||
|
||||
def call(storage:, http_options: {})
|
||||
config = validate_configuration(storage).value_or { return Failure(it) }
|
||||
|
||||
token_cache_key = TOKEN_CACHE_KEY % storage.id
|
||||
access_token = @use_cache ? Rails.cache.read(token_cache_key) : nil
|
||||
|
||||
http = build_http_session(access_token, config, http_options).value_or { return Failure(it) }
|
||||
|
||||
operation_result = yield http
|
||||
|
||||
return operation_result unless @use_cache
|
||||
|
||||
case operation_result
|
||||
in Success if @use_cache && access_token.blank?
|
||||
write_cache(token_cache_key, http)
|
||||
in Failure(code: :forbidden)
|
||||
clear_cache(token_cache_key)
|
||||
else
|
||||
return operation_result
|
||||
end
|
||||
|
||||
operation_result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_configuration(storage)
|
||||
config = storage.oauth_configuration.to_httpx_oauth_config
|
||||
return Success(config) if config.valid?
|
||||
|
||||
Failure(Results::Error.new(source: self.class, payload: storage, code: :storage_not_configured))
|
||||
end
|
||||
|
||||
def write_cache(key, httpx_session)
|
||||
access_token = httpx_session.instance_variable_get(:@options).oauth_session.access_token
|
||||
Rails.cache.write(key, access_token, expires_in: 50.minutes)
|
||||
end
|
||||
|
||||
def clear_cache(key) = Rails.cache.delete(key)
|
||||
|
||||
def build_http_session(access_token, config, http_options)
|
||||
if access_token.present?
|
||||
http_with_current_token(access_token:, http_options:)
|
||||
else
|
||||
http_with_new_token(config:, http_options:)
|
||||
end
|
||||
end
|
||||
|
||||
def http_with_current_token(access_token:, http_options:)
|
||||
opts = http_options.deep_merge({ headers: { "Authorization" => "Bearer #{access_token}" } })
|
||||
Success(OpenProject.httpx.with(opts))
|
||||
end
|
||||
|
||||
def http_with_new_token(config:, http_options:)
|
||||
http = OpenProject.httpx
|
||||
.oauth_auth(**config.to_h, token_endpoint_auth_method: "client_secret_post")
|
||||
.with_access_token
|
||||
.with(http_options)
|
||||
Success(http)
|
||||
rescue HTTPX::HTTPError => e
|
||||
Failure(Results::Error.new(code: :unauthorized, payload: e.response, source: self.class))
|
||||
rescue HTTPX::TimeoutError => e
|
||||
Failure(Results::Error.new(code: :timeout, payload: e.to_s, source: self.class))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# 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 Adapters
|
||||
module AuthenticationStrategies
|
||||
class OAuthConfiguration
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :scope, :issuer, :client_secret, :client_id
|
||||
|
||||
validates_presence_of :client_id, :client_secret, :issuer
|
||||
|
||||
# FIXME: Move to Data and a Validator
|
||||
def initialize(client_id: nil, client_secret: nil, issuer: nil, scope: nil)
|
||||
@client_id = client_id
|
||||
@client_secret = client_secret
|
||||
@issuer = issuer
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ issuer:, client_id:, client_secret:, scope: }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# 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 Adapters
|
||||
module AuthenticationStrategies
|
||||
class OAuthUserToken < AuthenticationStrategy
|
||||
def initialize(user)
|
||||
super()
|
||||
@user = user
|
||||
@retried = false
|
||||
@error_data = Results::Error.new(source: self.class, code: :error)
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def call(storage:, http_options: {}, &)
|
||||
oauth_client = validate_oauth_client(storage).value_or { return Failure(it) }
|
||||
token = current_token(oauth_client).value_or { return Failure(it) }
|
||||
|
||||
options = http_options.deep_merge(headers: { "Authorization" => "Bearer #{token.access_token}" })
|
||||
|
||||
original_response = yield OpenProject.httpx.with(options)
|
||||
|
||||
case original_response
|
||||
in Failure(code: :unauthorized)
|
||||
refresh_and_retry(storage, token, options, &)
|
||||
else
|
||||
original_response
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError => e
|
||||
raise e if @retried
|
||||
|
||||
Rails.logger.error("#{e.inspect} happened for User ##{@user.id} #{@user.name}")
|
||||
@retried = true
|
||||
retry
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
private
|
||||
|
||||
def validate_oauth_client(storage)
|
||||
return Success(storage.oauth_client) if storage.oauth_client
|
||||
|
||||
Failure(@error_data.with(code: :missing_oauth_client, payload: storage))
|
||||
end
|
||||
|
||||
def refresh_and_retry(storage, token, options, &)
|
||||
config = storage.oauth_configuration.to_httpx_oauth_config.to_h
|
||||
refreshed_session = refresh_token(config, options, token).value_or { return Failure(it) }
|
||||
update_token(token, refreshed_session)
|
||||
|
||||
yield refreshed_session
|
||||
end
|
||||
|
||||
def update_token(token, http_session)
|
||||
oauth_session = http_session.instance_variable_get(:@options).oauth_session
|
||||
token.update(access_token: oauth_session.access_token, refresh_token: oauth_session.refresh_token)
|
||||
end
|
||||
|
||||
def refresh_token(oauth_config, options, token)
|
||||
httpx_config = oauth_config.merge(refresh_token: token.refresh_token, token_endpoint_auth_method: "client_secret_post")
|
||||
Success(OpenProject.httpx.oauth_auth(**httpx_config).with(options).with_access_token)
|
||||
rescue HTTPX::HTTPError => e
|
||||
handle_http_error(token, e)
|
||||
rescue HTTPX::TimeoutError => e
|
||||
handle_timeout(token, e)
|
||||
end
|
||||
|
||||
def handle_timeout(token, exception)
|
||||
Rails.logger.error("Timeout while refreshing OAuth token. - Payload: #{exception.message}")
|
||||
token.destroy
|
||||
Failure(@error_data.with(error: :timeout_on_refresh, payload: exception))
|
||||
end
|
||||
|
||||
def handle_http_error(token, error)
|
||||
Rails.logger.error("Error while refreshing OAuth token - Payload: #{error.response}")
|
||||
token.destroy
|
||||
Failure(@error_data.with(code: :unauthorized, payload: error.response))
|
||||
end
|
||||
|
||||
def current_token(client)
|
||||
token = OAuthClientToken.find_by(user: @user, oauth_client: client)
|
||||
token ? Success(token) : Failure(@error_data.with(code: :missing_token))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+14
-18
@@ -29,29 +29,25 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module AuthenticationStrategies
|
||||
class OAuthConfiguration
|
||||
include ActiveModel::Validations
|
||||
module Adapters
|
||||
module AuthenticationStrategies
|
||||
class SsoUserToken < AuthenticationStrategy
|
||||
def initialize(user)
|
||||
super()
|
||||
@user = user
|
||||
end
|
||||
|
||||
attr_reader :scope, :issuer, :client_secret, :client_id
|
||||
def call(storage:, http_options: {}, &)
|
||||
result = OpenIDConnect::UserTokens::FetchService.new(user: @user).access_token_for(audience: storage.audience)
|
||||
|
||||
validates_presence_of :client_id, :client_secret, :issuer
|
||||
token = result.value_or do |failure|
|
||||
error("Failed to fetch access token for user #{@user}. Error: #{failure.inspect}")
|
||||
|
||||
def initialize(client_id: nil,
|
||||
client_secret: nil,
|
||||
issuer: nil,
|
||||
scope: nil)
|
||||
@client_id = client_id
|
||||
@client_secret = client_secret
|
||||
@issuer = issuer
|
||||
@scope = scope
|
||||
return Failure(failure.with(code: :unauthorized))
|
||||
end
|
||||
|
||||
def to_h
|
||||
{ issuer:, client_id:, client_secret:, scope: }
|
||||
end
|
||||
opts = http_options.deep_merge({ headers: { "Authorization" => "Bearer #{token}" } })
|
||||
yield OpenProject.httpx.with(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module Adapters
|
||||
module ConnectionValidators
|
||||
class BaseConnectionValidator
|
||||
class << self
|
||||
+3
-3
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module Adapters
|
||||
module ConnectionValidators
|
||||
class BaseValidatorGroup
|
||||
include TaggedLogging
|
||||
@@ -38,7 +38,7 @@ module Storages
|
||||
new(storage).call
|
||||
end
|
||||
|
||||
def self.key = raise Errors::SubclassResponsibility
|
||||
def self.key = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def initialize(storage)
|
||||
@storage = storage
|
||||
@@ -55,7 +55,7 @@ module Storages
|
||||
|
||||
private
|
||||
|
||||
def validate = raise Errors::SubclassResponsibility
|
||||
def validate = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def register_checks(*keys)
|
||||
keys.each { @results.register_check(it) }
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module Adapters
|
||||
module ConnectionValidators
|
||||
CheckResult = Data.define(:key, :state, :code, :timestamp, :context) do
|
||||
private_class_method :new
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module Adapters
|
||||
module ConnectionValidators
|
||||
class ValidationGroupResult
|
||||
delegate :[], :each_pair, to: :@results
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module Adapters
|
||||
module ConnectionValidators
|
||||
class ValidatorResult
|
||||
private attr_reader :group_results
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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 Adapters
|
||||
module Errors
|
||||
ResolverStandardError = Class.new(::Storages::Errors::BaseError)
|
||||
MissingContract = Class.new(ResolverStandardError)
|
||||
OperationNotSupported = Class.new(ResolverStandardError)
|
||||
MissingModel = Class.new(ResolverStandardError)
|
||||
UnknownProvider = Class.new(ResolverStandardError)
|
||||
UnknownAuthenticationStrategy = Class.new(ArgumentError)
|
||||
|
||||
def self.registry_error_for(key)
|
||||
case key.split(".")
|
||||
in [storage, *] if Registry.known_providers.exclude?(storage)
|
||||
UnknownProvider.new(storage)
|
||||
in [storage, "contracts", model]
|
||||
MissingContract.new("No #{model} contract defined for provider: #{storage.camelize}")
|
||||
in [storage, "commands" | "queries" => type, operation]
|
||||
OperationNotSupported.new(
|
||||
"#{type.singularize.capitalize} #{operation} not supported by provider: #{storage.camelize}"
|
||||
)
|
||||
in [storage, "models", object]
|
||||
MissingModel.new("Model #{object} not registered for provider: #{storage.camelize}")
|
||||
else
|
||||
ResolverStandardError.new("Cannot resolve key #{key}.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
AddUserToGroup = Data.define(:group, :user) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(group:, user:, contract: AddUserToGroupContract.new)
|
||||
contract.call(group:, user:).to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+6
-5
@@ -29,11 +29,12 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module ResultData
|
||||
CopyTemplateFolder = Data.define(:id, :polling_url, :requires_polling) do
|
||||
def requires_polling? = !!requires_polling
|
||||
module Adapters
|
||||
module Input
|
||||
class AddUserToGroupContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:user).filled(:string)
|
||||
required(:group).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
+9
-11
@@ -28,18 +28,16 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Storages::Peripherals::StorageInteraction::Nextcloud
|
||||
class DeleteFolderCommand
|
||||
def self.call(storage:, auth_strategy:, location:)
|
||||
new(storage).call(auth_strategy:, location:)
|
||||
end
|
||||
module Storages
|
||||
module Adapters
|
||||
module Input
|
||||
CopyTemplateFolder = Data.define(:source, :destination) do
|
||||
private_class_method :new
|
||||
|
||||
def initialize(storage)
|
||||
@delegate = Internal::DeleteEntityCommand.new(storage)
|
||||
end
|
||||
|
||||
def call(auth_strategy:, location:)
|
||||
@delegate.call(auth_strategy:, location:)
|
||||
def self.build(source:, destination:, contract: CopyTemplateFolderContract.new)
|
||||
contract.call(source:, destination:).to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class CopyTemplateFolderContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:source).filled(:string)
|
||||
required(:destination).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
CreateFolder = Data.define(:folder_name, :parent_location) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(folder_name:, parent_location:, contract: CreateFolderContract.new)
|
||||
contract.call(folder_name:, parent_location:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class CreateFolderContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:folder_name).filled(:string)
|
||||
required(:parent_location).filter(:filled?, :str?).value(AdapterTypes::Location)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
DeleteFolder = Data.define(:location) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(location:, contract: DeleteFolderContract.new)
|
||||
contract.call(location:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class DeleteFolderContract < Dry::Validation::Contract
|
||||
params do
|
||||
# FIXME: Should this be a Location?
|
||||
required(:location).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
DownloadLink = Data.define(:file_link) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(file_link:, contract: DownloadLinkContract.new)
|
||||
contract.call(file_link:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+9
-5
@@ -28,10 +28,14 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Storages::Storages
|
||||
class NextcloudContract < ComposedContract
|
||||
include_contract NextcloudGeneralInformationContract
|
||||
include_contract NextcloudAudienceContract
|
||||
include_contract NextcloudAutomaticManagementContract
|
||||
module Storages
|
||||
module Adapters
|
||||
module Input
|
||||
class DownloadLinkContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:file_link).filled(type?: FileLink)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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 Storages
|
||||
module Adapters
|
||||
module Input
|
||||
# FIXME: Should FileID become a Location?
|
||||
FileInfo = Data.define(:file_id) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(file_id:, contract: FileInfoContract.new)
|
||||
contract.call(file_id:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class FileInfoContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:file_id).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
FilePathToIdMap = Data.define(:folder, :depth) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(folder:, depth: Float::INFINITY, contract: FilePathToIdMapContract.new)
|
||||
contract.call(folder:, depth:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class FilePathToIdMapContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:folder).filter(:filled?, :str?).value(AdapterTypes::Location)
|
||||
required(:depth).filled { (int? & gteq?(0)) | eql?(Float::INFINITY) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
Files = Data.define(:folder) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(folder:, contract: FilesContract.new)
|
||||
contract.call(folder:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class FilesContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:folder).filter(:filled?, :str?).value(AdapterTypes::Location)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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 Storages
|
||||
module Adapters
|
||||
module Input
|
||||
# FIXME: Should FileIDs become a Array(Location)?
|
||||
FilesInfo = Data.define(:file_ids) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(file_ids:, contract: FilesInfoContract.new)
|
||||
contract.call(file_ids:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class FilesInfoContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:file_ids).array(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
GroupUsers = Data.define(:group) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(group:, contract: GroupUsersContract.new)
|
||||
contract.call(group:).to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class GroupUsersContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:group).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
OpenFileLink = Data.define(:file_id, :open_location) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(file_id:, open_location: false, contract: OpenFileLinkContract.new)
|
||||
contract.call(file_id:, open_location:).to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class OpenFileLinkContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:file_id).filled(:string)
|
||||
required(:open_location).maybe(:bool)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
RemoveUserFromGroup = Data.define(:group, :user) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(group:, user:, contract: RemoveUserFromGroupContract.new)
|
||||
contract.call(group:, user:).to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class RemoveUserFromGroupContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:user).filled(:string)
|
||||
required(:group).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
RenameFile = Data.define(:location, :new_name) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(location:, new_name:, contract: RenameFileContract.new)
|
||||
contract.call(location:, new_name:).to_monad.fmap { |it| new(**it.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class RenameFileContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:new_name).filled(:string)
|
||||
required(:location).filter(:filled?, :str?).value(AdapterTypes::Location)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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.
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Adapters
|
||||
module Input
|
||||
# user_permissions - A list of user specific file permissions.
|
||||
# IMPORTANT: the user ids are considered to be the ids of the remote identities. If user permissions should be
|
||||
# set via a group, a `group_id` must be provided instead of a `user_id`.
|
||||
# Example:
|
||||
# [
|
||||
# {user_id: "d6e00f6d-1ae7-43e6-b0af-15d99a56d4ce", permissions: [ :read_files,
|
||||
# :write_files,
|
||||
# :create_files,
|
||||
# :delete_files,
|
||||
# :share_files ]},
|
||||
# {user_id: "f6e00f6d-1ae7-43e6-b0af-15d99a56d4ce", permissions: [:read_files, :write_files]},
|
||||
# {group_id: "fee9cd49-17e2-4430-9235-2060e7372568", permissions: [:read_files]},
|
||||
# ]
|
||||
SetPermissions = ::Data.define(:file_id, :user_permissions) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(file_id:, user_permissions:, contract: SetPermissionsContract.new)
|
||||
contract.call(file_id:, user_permissions:).to_monad.fmap { |result| new(**result.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class SetPermissionsContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:file_id).filled(:string)
|
||||
required(:user_permissions).array(:hash) do
|
||||
optional(:user_id).filled(:string)
|
||||
optional(:group_id).filled(:string)
|
||||
required(:permissions)
|
||||
.array(:symbol, included_in?: OpenProject::Storages::Engine.external_file_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
rule(:user_permissions).each do
|
||||
both = value.key?(:user_id) && value.key?(:group_id)
|
||||
none = !value.key?(:user_id) && !value.key?(:group_id)
|
||||
|
||||
key.failure("must have either user_id or group_id") if both || none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
Strategy = Data.define(:key, :user, :storage, :use_cache, :token) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(key:, user: nil, storage: nil, use_cache: true, token: nil, contract: StrategyContract.new)
|
||||
contract.call(key:, user:, use_cache:, storage:, token:).to_monad.fmap { |result| new(**result.to_h) }
|
||||
end
|
||||
|
||||
def with_user(user)
|
||||
with(user:)
|
||||
end
|
||||
|
||||
def with_cache(use_cache)
|
||||
with(use_cache:)
|
||||
end
|
||||
|
||||
def with_token(token)
|
||||
with(token:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+10
-19
@@ -29,26 +29,17 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module ManagedFolderIdentifier
|
||||
class OneDrive
|
||||
CHARACTER_BLOCKLIST = /[\\<>+?:"|\/]/
|
||||
module Adapters
|
||||
module Input
|
||||
class StrategyContract < Dry::Validation::Contract
|
||||
AUTH_METHODS = %i[noop basic_auth oauth_client_credentials oauth_user_token sso_user_token bearer_token].to_set.freeze
|
||||
|
||||
def initialize(project_storage)
|
||||
@project_storage = project_storage
|
||||
@project = project_storage.project
|
||||
end
|
||||
|
||||
def name
|
||||
path
|
||||
end
|
||||
|
||||
def path
|
||||
"#{@project.name.gsub(CHARACTER_BLOCKLIST, '_')} (#{@project.id})"
|
||||
end
|
||||
|
||||
def location
|
||||
@project_storage.project_folder_id
|
||||
params do
|
||||
required(:key).filled(:symbol, included_in?: AUTH_METHODS)
|
||||
optional(:user).maybe(type?: User)
|
||||
optional(:storage).maybe(type?: Storage)
|
||||
optional(:use_cache).maybe(:bool)
|
||||
optional(:token).maybe(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
UploadLink = Data.define(:folder_id, :file_name) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(folder_id:, file_name:, contract: UploadLinkContract.new)
|
||||
contract.call(folder_id:, file_name:).to_monad.fmap { |result| new(**result.to_h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Adapters
|
||||
module Input
|
||||
class UploadLinkContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:folder_id).filled(:string)
|
||||
required(:file_name).filled(:string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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 Storages
|
||||
module Adapters
|
||||
class OAuthConfigurationBase
|
||||
def scope = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def basic_rack_oauth_client = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def to_httpx_oauth_config = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def authorization_uri(state: nil)
|
||||
basic_rack_oauth_client.authorization_uri(scope:, state:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
class Base
|
||||
include TaggedLogging
|
||||
include Dry::Monads::Result(Results::Error)
|
||||
|
||||
def self.call(storage:, auth_strategy:, input_data:)
|
||||
new(storage).call(auth_strategy:, input_data:)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ocs_api_request_headers = { headers: { "OCS-APIRequest" => "true" } }
|
||||
def depth_header(depth) = { headers: { "Depth" => depth.to_s } }
|
||||
|
||||
def origin_user_id(auth_strategy:)
|
||||
error = Results::Error.new(source: self.class, code: :error)
|
||||
|
||||
auth_strategy.bind do |strategy|
|
||||
case strategy.key
|
||||
when :basic_auth
|
||||
Success(@storage.username)
|
||||
when :oauth_user_token, :sso_user_token
|
||||
fetch_remote_identity(strategy.user, strategy.key)
|
||||
else
|
||||
Failure(
|
||||
error.with(
|
||||
payload: "authentication strategy with user context found. Cannot execute query without user context."
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_remote_identity(user, auth_key)
|
||||
integration = auth_key == :sso_user_token ? user.authentication_provider : @storage.oauth_client
|
||||
remote_id = RemoteIdentity.of_user_and_client(user, integration, @storage)
|
||||
|
||||
if remote_id.present?
|
||||
Success(remote_id.origin_user_id)
|
||||
else
|
||||
Failure(error.with(payload: "No origin user ID or user token found. Cannot execute query without user context."))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class AddUserToGroupCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: ocs_api_request_headers) do |http|
|
||||
url = UrlBuilder.url(@storage.uri, "ocs/v1.php/cloud/users", input_data.user, "groups")
|
||||
info "Adding #{input_data.user} to #{input_data.group} through #{url}"
|
||||
|
||||
response = http.post(url, form: { "groupid" => input_data.group })
|
||||
handle_response(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
handle_success_response(response, error)
|
||||
in { status: 405 }
|
||||
Failure(error.with(code: :not_allowed))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def handle_success_response(response, error)
|
||||
status_code = Nokogiri::XML(response.body.to_s).xpath("/ocs/meta/statuscode").text
|
||||
|
||||
case status_code
|
||||
when "100"
|
||||
info "User has been added to the group"
|
||||
Success()
|
||||
when "101"
|
||||
Failure(error.with(code: :no_group_specified))
|
||||
when "102"
|
||||
Failure(error.with(code: :group_does_not_exist))
|
||||
when "103"
|
||||
Failure(error.with(code: :user_does_not_exist))
|
||||
when "104"
|
||||
Failure(error.with(code: :insufficient_privileges))
|
||||
when "105"
|
||||
Failure(error.with(code: :failed_to_add))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class CopyTemplateFolderCommand < Base
|
||||
def initialize(storage)
|
||||
super
|
||||
@data = Results::CopyTemplateFolder.new(id: nil, polling_url: nil, requires_polling: false)
|
||||
end
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
remote_urls = build_origin_urls(input_data)
|
||||
|
||||
ensure_remote_folder_does_not_exist(http, remote_urls[:destination_url]).bind do
|
||||
copy_folder(http, **remote_urls).bind do
|
||||
get_folder_id(auth_strategy, input_data.destination)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_inputs(source_path, destination_path)
|
||||
info "Validating #{source_path} and #{destination_path}"
|
||||
if source_path.blank? || destination_path.blank?
|
||||
return Util.error(:missing_paths, "Source and destination paths must be present.")
|
||||
end
|
||||
|
||||
ServiceResult.success(result: { source_path:, destination_path: })
|
||||
end
|
||||
|
||||
def build_origin_urls(input_data)
|
||||
source_url = UrlBuilder.url(@storage.uri, "remote.php/dav/files", @storage.username, input_data.source)
|
||||
destination_url = UrlBuilder.url(@storage.uri, "remote.php/dav/files", @storage.username, input_data.destination)
|
||||
|
||||
{ source_url:, destination_url: }
|
||||
end
|
||||
|
||||
def ensure_remote_folder_does_not_exist(http, destination_url)
|
||||
info "Checking if #{destination_url} does not already exists."
|
||||
response = http.head(destination_url)
|
||||
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Failure(error.with(code: :conflict))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 }
|
||||
Success()
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def copy_folder(http, source_url:, destination_url:)
|
||||
info "Copying #{source_url} to #{destination_url}"
|
||||
handle_response http.request("COPY",
|
||||
source_url,
|
||||
headers: { "Destination" => destination_url, "Depth" => "infinity" })
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success()
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 403 }
|
||||
Failure(error.with(code: :forbidden))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def get_folder_id(auth_strategy, destination_path)
|
||||
# file_path_to_id_map query returns keys without trailing slashes
|
||||
# TODO: Harden this with https://community.openproject.org/wp/57850
|
||||
sanitized_path = destination_path.chomp("/")
|
||||
|
||||
Input::FilePathToIdMap.build(folder: sanitized_path, depth: 0).bind do |input_data|
|
||||
Registry.resolve("nextcloud.queries.file_path_to_id_map")
|
||||
.call(storage: @storage, auth_strategy:, input_data:)
|
||||
.fmap { @data.with(id: it.fetch(sanitized_path).id) }
|
||||
end
|
||||
end
|
||||
|
||||
def source = self.class
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class CreateFolderCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
info "Trying to create folder #{input_data.folder_name} under #{input_data.parent_location}"
|
||||
origin_user_id(auth_strategy:).bind do |origin_user|
|
||||
path_prefix = UrlBuilder.path(@storage.uri.path, "remote.php/dav/files", origin_user)
|
||||
request_url = UrlBuilder.url(@storage.uri,
|
||||
"remote.php/dav/files",
|
||||
origin_user,
|
||||
input_data.parent_location.path,
|
||||
input_data.folder_name)
|
||||
|
||||
create_folder_request(auth_strategy, request_url, path_prefix)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_folder_request(auth_strategy, request_url, path_prefix)
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response(http.mkcol(request_url)).bind do
|
||||
handle_response(http.propfind(request_url, requested_properties)).bind do |response|
|
||||
info "Folder successfully created"
|
||||
storage_file(path_prefix, response)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(payload: response, source: self.class)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success(response)
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 | 409 } # webDAV endpoint returns 409 if path does not exist
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 405 } # webDAV endpoint returns 405 if folder already exists
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def requested_properties
|
||||
Nokogiri::XML::Builder.new do |xml|
|
||||
xml["d"].propfind("xmlns:d" => "DAV:", "xmlns:oc" => "http://owncloud.org/ns") do
|
||||
xml["d"].prop do
|
||||
xml["oc"].fileid
|
||||
xml["oc"].size
|
||||
xml["d"].getlastmodified
|
||||
xml["oc"].permissions
|
||||
xml["oc"].send(:"owner-display-name")
|
||||
end
|
||||
end
|
||||
end.to_xml
|
||||
end
|
||||
|
||||
# FIXME: Move this to a transformer?
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def storage_file(path_prefix, response)
|
||||
xml = response.xml
|
||||
path = xml.xpath("//d:response/d:href/text()").to_s
|
||||
timestamp = xml.xpath("//d:response/d:propstat/d:prop/d:getlastmodified/text()").to_s
|
||||
creator = xml.xpath("//d:response/d:propstat/d:prop/oc:owner-display-name/text()").to_s
|
||||
location = CGI.unescapeURIComponent(
|
||||
UrlBuilder.path(CGI.unescapeURIComponent(path)).gsub(path_prefix, "")
|
||||
).delete_suffix("/")
|
||||
|
||||
Results::StorageFile.build(
|
||||
id: xml.xpath("//d:response/d:propstat/d:prop/oc:fileid/text()").to_s,
|
||||
name: location.split("/").last,
|
||||
size: xml.xpath("//d:response/d:propstat/d:prop/oc:size/text()").to_s,
|
||||
mime_type: "application/x-op-directory",
|
||||
created_at: Time.zone.parse(timestamp),
|
||||
last_modified_at: Time.zone.parse(timestamp),
|
||||
created_by_name: creator,
|
||||
last_modified_by_name: creator,
|
||||
location:
|
||||
)
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class DeleteFolderCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
origin_user_id(auth_strategy:).bind do |origin_user_id|
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.delete(
|
||||
UrlBuilder.url(@storage.uri, "remote.php/dav/files", origin_user_id, input_data.location)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(payload: response, source: self.class)
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success()
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class RemoveUserFromGroupCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: ocs_api_request_headers) do |http|
|
||||
url = UrlBuilder.url(@storage.uri, "ocs/v1.php/cloud/users", input_data.user, "groups")
|
||||
url << "?groupid=#{CGI.escapeURIComponent(input_data.group)}"
|
||||
|
||||
info "Removing #{input_data.user} from #{input_data.group} through #{url}"
|
||||
handle_response(http.delete(url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
handle_success_response(response.xml, error)
|
||||
in { status: 405 }
|
||||
Failure(error.with(code: :not_allowed))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def handle_success_response(response, error)
|
||||
status_code = response.xpath("/ocs/meta/statuscode").text
|
||||
case status_code
|
||||
when "100"
|
||||
info "User has been removed from group"
|
||||
Success()
|
||||
when "101"
|
||||
Failure(error.with(code: :no_group_specified))
|
||||
when "102"
|
||||
Failure(error.with(code: :group_does_not_exist))
|
||||
when "103"
|
||||
Failure(error.with(code: :user_does_not_exist))
|
||||
when "104"
|
||||
Failure(error.with(code: :insufficient_privileges))
|
||||
when "105"
|
||||
Failure(error.with(code: :failed_to_remove))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class RenameFileCommand < Base
|
||||
def initialize(*)
|
||||
super
|
||||
@file_info_query = Queries::FileInfoQuery.new(@storage)
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
info "Validating user remote ID"
|
||||
origin_user_id(auth_strategy:).bind do |origin_user_id|
|
||||
info "Getting the folder information"
|
||||
Input::FileInfo.build(file_id: input_data.location.path).bind do |info_input_data|
|
||||
@file_info_query.call(auth_strategy:, input_data: info_input_data).bind do |fileinfo|
|
||||
info "Renaming the folder #{fileinfo.location} to #{input_data.new_name}"
|
||||
make_request(auth_strategy, origin_user_id, fileinfo, input_data.new_name).bind do
|
||||
info "Retrieving updated file info for the #{input_data.new_name} folder"
|
||||
@file_info_query.call(auth_strategy:, input_data: info_input_data).bind(&:to_storage_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
private
|
||||
|
||||
def make_request(auth_strategy, user, file_info, name)
|
||||
source_path = UrlBuilder.url(@storage.uri,
|
||||
"remote.php/dav/files",
|
||||
user,
|
||||
CGI.unescape(file_info.location))
|
||||
|
||||
destination = UrlBuilder.path(@storage.uri.path,
|
||||
"remote.php/dav/files",
|
||||
user,
|
||||
CGI.unescape(target_path(file_info, name)))
|
||||
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.request("MOVE", source_path, headers: { "Destination" => destination, "Overwrite" => "F" })
|
||||
end
|
||||
end
|
||||
|
||||
def target_path(info, name)
|
||||
info.location.gsub(CGI.escapeURIComponent(info.name), CGI.escapeURIComponent(name))
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success()
|
||||
in { status: 412 }
|
||||
Failure(error.with(code: :conflict))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Commands
|
||||
class SetPermissionsCommand < Base
|
||||
PERMISSIONS_MAP = { read_files: 1, write_files: 2, create_files: 4, delete_files: 8, share_files: 16 }.freeze
|
||||
PERMISSIONS_KEYS = OpenProject::Storages::Engine.external_file_permissions
|
||||
SUCCESS_XPATH = "/d:multistatus/d:response/d:propstat[d:status[text() = 'HTTP/1.1 200 OK']]/d:prop/nc:acl-list"
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def call(auth_strategy:, input_data:)
|
||||
username = origin_user_id(auth_strategy:).value_or { return Failure(it) }
|
||||
|
||||
permissions = parse_permission_mask(input_data.user_permissions)
|
||||
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
with_tagged_logger do
|
||||
info "Getting the folder information"
|
||||
Input::FileInfo.build(file_id: input_data.file_id).bind do |file_data|
|
||||
Queries::FileInfoQuery.call(storage: @storage, auth_strategy:, input_data: file_data).bind do |folder_info|
|
||||
info "Setting permissions #{permissions.inspect} on #{folder_info.location}"
|
||||
body = request_xml_body(permissions[:groups], permissions[:users])
|
||||
# This can raise KeyErrors, we probably should just default to empty Arrays.
|
||||
response = http.request("PROPPATCH",
|
||||
UrlBuilder.url(@storage.uri,
|
||||
"remote.php/dav/files",
|
||||
username,
|
||||
CGI.unescape(folder_info.location)),
|
||||
xml: body)
|
||||
|
||||
handle_response(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
private
|
||||
|
||||
def parse_permission_mask(user_permissions)
|
||||
user_permissions.each_with_object({ groups: {}, users: {} }) do |entry, aggregate|
|
||||
if entry.key?(:user_id)
|
||||
aggregate[:users][entry[:user_id]] =
|
||||
PERMISSIONS_MAP.values_at(*(PERMISSIONS_KEYS & entry[:permissions])).sum
|
||||
else
|
||||
aggregate[:groups][entry[:group_id]] =
|
||||
PERMISSIONS_MAP.values_at(*(PERMISSIONS_KEYS & entry[:permissions])).sum
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
doc = Nokogiri::XML(response.body.to_s)
|
||||
if doc.xpath(SUCCESS_XPATH).present?
|
||||
info "Permissions set"
|
||||
Success(:success)
|
||||
else
|
||||
Failure(error.with(code: :permission_not_set))
|
||||
end
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def request_xml_body(groups_permissions, users_permissions)
|
||||
Nokogiri::XML::Builder.new do |xml|
|
||||
xml["d"].propertyupdate(
|
||||
"xmlns:d" => "DAV:",
|
||||
"xmlns:nc" => "http://nextcloud.org/ns"
|
||||
) do
|
||||
xml["d"].set do
|
||||
xml["d"].prop do
|
||||
xml["nc"].send(:"acl-list") do
|
||||
groups_permissions.each do |group, group_permissions|
|
||||
xml["nc"].acl do
|
||||
xml["nc"].send(:"acl-mapping-type", "group")
|
||||
xml["nc"].send(:"acl-mapping-id", group)
|
||||
xml["nc"].send(:"acl-mask", "31")
|
||||
xml["nc"].send(:"acl-permissions", group_permissions.to_s)
|
||||
end
|
||||
end
|
||||
users_permissions.each do |user, user_permissions|
|
||||
xml["nc"].acl do
|
||||
xml["nc"].send(:"acl-mapping-type", "user")
|
||||
xml["nc"].send(:"acl-mapping-id", user)
|
||||
xml["nc"].send(:"acl-mask", "31")
|
||||
xml["nc"].send(:"acl-permissions", user_permissions.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end.to_xml
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Contracts
|
||||
class AudienceContract < ::ModelContract
|
||||
attribute :storage_audience
|
||||
validates :storage_audience, presence: true, if: -> { nextcloud_storage_authenticate_via_idp? }
|
||||
|
||||
# Adding this to allow writing the storage_audience
|
||||
attribute :provider_fields
|
||||
|
||||
private
|
||||
|
||||
def nextcloud_storage_authenticate_via_idp?
|
||||
nextcloud_storage? && @model.authenticate_via_idp?
|
||||
end
|
||||
|
||||
def nextcloud_storage?
|
||||
@model.is_a?(NextcloudStorage)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Contracts
|
||||
class AutomaticManagementContract < ::ModelContract
|
||||
attribute :automatically_managed
|
||||
|
||||
attribute :username
|
||||
validates :username, presence: true, if: :nextcloud_storage_automatic_management_enabled?
|
||||
validates :username,
|
||||
absence: true,
|
||||
unless: -> { nextcloud_storage_automatic_management_enabled? || nextcloud_default_storage_username? }
|
||||
|
||||
attribute :password
|
||||
validates :password, presence: true, if: :nextcloud_storage_automatic_management_enabled?
|
||||
validates :password, absence: true, unless: :nextcloud_storage_automatic_management_enabled?
|
||||
|
||||
validate do
|
||||
if nextcloud_storage_automatic_management_enabled? && errors.exclude?(:password) && model.host.present?
|
||||
# FIXME: Mode this to Adapters - 2025-03-18 @mereghost
|
||||
NextcloudApplicationCredentialsValidator.new(self).call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def nextcloud_storage_automatic_management_enabled?
|
||||
return false unless nextcloud_storage?
|
||||
|
||||
@model.automatic_management_enabled?
|
||||
end
|
||||
|
||||
def nextcloud_default_storage_username?
|
||||
return false unless nextcloud_storage?
|
||||
|
||||
@model.username == @model.provider_fields_defaults[:username]
|
||||
end
|
||||
|
||||
def nextcloud_storage?
|
||||
@model.is_a?(NextcloudStorage)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Contracts
|
||||
class GeneralInformationContract < ::ModelContract
|
||||
attribute :name
|
||||
validates :name, presence: true, length: { maximum: 255 }
|
||||
attribute :host
|
||||
validates :host, url: { message: :invalid_host_url }, length: { maximum: 255 }
|
||||
# Check that a host actually is a storage server.
|
||||
# But only do so if the validations above for URL were successful.
|
||||
validates :host, secure_context_uri: true, nextcloud_compatible_host: true, unless: -> { errors.include?(:host) }
|
||||
|
||||
attribute :authentication_method
|
||||
validates :authentication_method, presence: true, inclusion: { in: NextcloudStorage::AUTHENTICATION_METHODS }
|
||||
|
||||
validate :require_ee_token_for_sso
|
||||
|
||||
def require_ee_token_for_sso
|
||||
return if EnterpriseToken.allows_to?(:nextcloud_sso)
|
||||
return unless model.authenticate_via_idp?
|
||||
return unless model.authentication_method_changed?
|
||||
|
||||
plan_name = I18n.t("ee.upsell.plan_name", plan: OpenProject::Token.lowest_plan_for(:nextcloud_sso)&.capitalize)
|
||||
errors.add(:authentication_method, :enterprise_plan_required, plan_name:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+45
@@ -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 Storages
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Contracts
|
||||
class StorageContract < ComposedContract
|
||||
include_contract GeneralInformationContract
|
||||
include_contract AudienceContract
|
||||
include_contract AutomaticManagementContract
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+17
-10
@@ -29,19 +29,26 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module OAuthConfigurations
|
||||
class ConfigurationInterface
|
||||
using ServiceResultRefinements
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
class ManagedFolderIdentifier
|
||||
def initialize(project_storage)
|
||||
@storage = project_storage.storage
|
||||
@project = project_storage.project
|
||||
end
|
||||
|
||||
def scope = raise ::Storages::Errors::SubclassResponsibility
|
||||
def name
|
||||
"#{@project.name.tr('/', '|')} (#{@project.id})"
|
||||
end
|
||||
|
||||
def basic_rack_oauth_client = raise ::Storages::Errors::SubclassResponsibility
|
||||
def path
|
||||
"/#{@storage.group_folder}/#{name}/"
|
||||
end
|
||||
|
||||
def to_httpx_oauth_config = raise ::Storages::Errors::SubclassResponsibility
|
||||
|
||||
def authorization_uri(state: nil)
|
||||
basic_rack_oauth_client.authorization_uri(scope:, state:)
|
||||
def location
|
||||
path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
NextcloudRegistry = Dry::Container::Namespace.new("nextcloud") do
|
||||
namespace("authentication") do
|
||||
register(:userless, ->(*) { Input::Strategy.build(key: :basic_auth) })
|
||||
register(:user_bound, UserBoundAuthentication)
|
||||
end
|
||||
|
||||
namespace("commands") do
|
||||
register(:add_user_to_group, Commands::AddUserToGroupCommand)
|
||||
register(:copy_template_folder, Commands::CopyTemplateFolderCommand)
|
||||
register(:create_folder, Commands::CreateFolderCommand)
|
||||
register(:delete_folder, Commands::DeleteFolderCommand)
|
||||
register(:remove_user_from_group, Commands::RemoveUserFromGroupCommand)
|
||||
register(:rename_file, Commands::RenameFileCommand)
|
||||
register(:set_permissions, Commands::SetPermissionsCommand)
|
||||
end
|
||||
|
||||
namespace("components") do
|
||||
namespace("forms") do
|
||||
register(:automatically_managed_folders, Admin::Forms::AutomaticallyManagedProjectFoldersFormComponent)
|
||||
register(:general_information, Admin::Forms::GeneralInfoFormComponent)
|
||||
register(:storage_audience, Admin::Forms::StorageAudienceFormComponent)
|
||||
register(:oauth_application, Admin::OAuthApplicationInfoCopyComponent)
|
||||
register(:oauth_client, Admin::Forms::OAuthClientFormComponent)
|
||||
end
|
||||
|
||||
register(:setup_wizard, StorageWizard)
|
||||
|
||||
register(:automatically_managed_folders, Admin::AutomaticallyManagedProjectFoldersInfoComponent)
|
||||
register(:general_information, Admin::GeneralInfoComponent)
|
||||
register(:storage_audience, Admin::StorageAudienceInfoComponent)
|
||||
register(:oauth_application, Admin::OAuthApplicationInfoComponent)
|
||||
register(:oauth_client, Admin::OAuthClientInfoComponent)
|
||||
end
|
||||
|
||||
namespace("contracts") do
|
||||
register(:storage, Contracts::StorageContract)
|
||||
register(:general_information, Contracts::GeneralInformationContract)
|
||||
register(:storage_audience, Contracts::AudienceContract)
|
||||
end
|
||||
|
||||
namespace("models") do
|
||||
register(:managed_folder_identifier, ManagedFolderIdentifier)
|
||||
end
|
||||
|
||||
namespace("queries") do
|
||||
register(:capabilities, Queries::CapabilitiesQuery)
|
||||
register(:download_link, Queries::DownloadLinkQuery)
|
||||
register(:file_info, Queries::FileInfoQuery)
|
||||
register(:file_path_to_id_map, Queries::FilePathToIdMapQuery)
|
||||
register(:files, Queries::FilesQuery)
|
||||
register(:files_info, Queries::FilesInfoQuery)
|
||||
register(:group_users, Queries::GroupUsersQuery)
|
||||
register(:open_file_link, Queries::OpenFileLinkQuery)
|
||||
register(:open_storage, Queries::OpenStorageQuery)
|
||||
register(:upload_link, Queries::UploadLinkQuery)
|
||||
register(:user, Queries::UserQuery)
|
||||
end
|
||||
|
||||
# Move Services to the Providers folder.
|
||||
namespace("services") do
|
||||
register(:upkeep_managed_folders, NextcloudManagedFolderCreateService)
|
||||
register(:upkeep_managed_folder_permissions, NextcloudManagedFolderPermissionsService)
|
||||
end
|
||||
|
||||
namespace("validators") do
|
||||
register("connection", Validators::ConnectionValidator)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+31
-25
@@ -29,40 +29,46 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
class FilePathToIdMapQuery
|
||||
def self.call(storage:, auth_strategy:, folder:, depth: Float::INFINITY)
|
||||
new(storage).call(auth_strategy:, folder:, depth:)
|
||||
end
|
||||
class OAuthConfiguration < OAuthConfigurationBase
|
||||
attr_reader :oauth_client
|
||||
|
||||
def initialize(storage)
|
||||
super()
|
||||
@storage = storage
|
||||
@propfind_query = Internal::PropfindQuery.new(storage)
|
||||
raise(ArgumentError, "Storage must have configured OAuth client credentials") if storage.oauth_client.blank?
|
||||
|
||||
@oauth_client = storage.oauth_client.freeze
|
||||
end
|
||||
|
||||
def call(auth_strategy:, folder:, depth:)
|
||||
origin_user_id = Util.origin_user_id(caller: self.class, storage: @storage, auth_strategy:)
|
||||
.on_failure { return it }
|
||||
.result
|
||||
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: headers(depth)) do |http|
|
||||
# nc:acl-list is only required to avoid https://community.openproject.org/wp/49628. See comment #4.
|
||||
@propfind_query.call(http:,
|
||||
username: origin_user_id,
|
||||
path: folder.path,
|
||||
props: %w[oc:fileid nc:acl-list])
|
||||
.map do |obj|
|
||||
obj.transform_values { |value| StorageFileId.new(id: value["fileid"]) }
|
||||
end
|
||||
end
|
||||
def to_httpx_oauth_config
|
||||
AuthenticationStrategies::OAuthConfiguration.new(
|
||||
client_id: @oauth_client.client_id,
|
||||
client_secret: @oauth_client.client_secret,
|
||||
issuer: URI(UrlBuilder.url(@storage.uri, "/index.php/apps/oauth2/api/v1")).normalize,
|
||||
scope: []
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def scope
|
||||
[]
|
||||
end
|
||||
|
||||
def headers(depth)
|
||||
Util.webdav_request_with_depth(depth.to_s.downcase)
|
||||
def basic_rack_oauth_client
|
||||
uri = @storage.uri
|
||||
|
||||
Rack::OAuth2::Client.new(
|
||||
identifier: @oauth_client.client_id,
|
||||
secret: @oauth_client.client_secret,
|
||||
redirect_uri: @oauth_client.redirect_uri,
|
||||
scheme: uri.scheme,
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
authorization_endpoint: UrlBuilder.path(uri.path, "/index.php/apps/oauth2/authorize"),
|
||||
token_endpoint: UrlBuilder.path(uri.path, "/index.php/apps/oauth2/api/v1/token")
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module ProviderResults
|
||||
Capabilities = Data.define(:app_enabled, :group_folder_enabled, :app_version, :group_folder_version) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(app_enabled:, group_folder_enabled:, app_version:,
|
||||
group_folder_version:, contract: CapabilitiesContract.new)
|
||||
contract.call(app_enabled:, group_folder_enabled:, app_version:, group_folder_version:)
|
||||
.to_monad.fmap { new(**it.to_h) }
|
||||
end
|
||||
|
||||
def self.empty
|
||||
new(app_enabled: false, group_folder_enabled: false, app_version: nil, group_folder_version: nil)
|
||||
end
|
||||
|
||||
alias_method :app_enabled?, :app_enabled
|
||||
alias_method :group_folder_enabled?, :group_folder_enabled
|
||||
|
||||
def app_disabled? = !app_enabled?
|
||||
def group_folder_disabled? = !group_folder_enabled?
|
||||
|
||||
def with(...)
|
||||
args = to_h.merge(...)
|
||||
self.class.build(**args).either(-> { it }, -> { raise ArgumentError, it.errors })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module ProviderResults
|
||||
class CapabilitiesContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:app_enabled).filled(:bool)
|
||||
required(:group_folder_enabled).filled(:bool)
|
||||
required(:app_version).maybe(AdapterTypes::SemanticVersionType)
|
||||
required(:group_folder_version).maybe(AdapterTypes::SemanticVersionType)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class CapabilitiesQuery < Base
|
||||
def self.call(storage:, auth_strategy:)
|
||||
new(storage).call(auth_strategy:)
|
||||
end
|
||||
|
||||
def call(auth_strategy:)
|
||||
http_options = { headers: { Accept: "application/json" } }.deep_merge(ocs_api_request_headers)
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options:) do |http|
|
||||
handle_response(http.get(url))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def url = UrlBuilder.url(@storage.uri, "/ocs/v2.php/cloud/capabilities")
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
json = response.json(symbolize_keys: true)
|
||||
parse_capabilities(json)
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def parse_capabilities(json)
|
||||
app_json = json.dig(:ocs, :data, :capabilities, :integration_openproject)
|
||||
|
||||
ProviderResults::Capabilities.build(
|
||||
app_enabled: app_json.present?,
|
||||
app_version: version(app_json&.dig(:app_version)),
|
||||
group_folder_enabled: !!app_json&.dig(:groupfolders_enabled),
|
||||
group_folder_version: version(app_json&.dig(:groupfolder_version))
|
||||
)
|
||||
end
|
||||
|
||||
def version(str)
|
||||
return if str.nil?
|
||||
|
||||
major, minor, patch = str.split(".").map(&:to_i)
|
||||
return if major.nil? || minor.nil? || patch.nil?
|
||||
|
||||
SemanticVersion.new(major:, minor:, patch:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class DownloadLinkQuery < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options:) do |http|
|
||||
handle_response(http.post(request_url, json: { fileId: input_data.file_link.origin_id })).fmap do |token|
|
||||
URI(download_link(token, input_data.file_link.origin_name))
|
||||
end
|
||||
end
|
||||
|
||||
# direct_download_request(auth_strategy:, file_link: input_data.file_link)
|
||||
# .bind { |response_body| direct_download_token(body: response_body) }
|
||||
# .map { |download_token| download_link(download_token, file_link.origin_name) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request_url = UrlBuilder.url(@storage.uri, "/ocs/v2.php/apps/dav/api/v1/direct")
|
||||
|
||||
def http_options = { headers: { "Accept" => "application/json" } }.deep_merge(ocs_api_request_headers)
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
if response.body.blank?
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
build_download_link(response, error)
|
||||
end
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: error))
|
||||
end
|
||||
end
|
||||
|
||||
def build_download_link(response, error)
|
||||
parsing_error = Failure(error.with(code: :invalid_response, payload: response.body))
|
||||
|
||||
json = response.json(symbolize_keys: true)
|
||||
url = json.dig(:ocs, :data, :url)
|
||||
return parsing_error if url.blank?
|
||||
|
||||
path = URI.parse(url).path
|
||||
return parsing_error if path.blank?
|
||||
|
||||
token = path.split("/").last
|
||||
return parsing_error if token.blank?
|
||||
|
||||
Success(token)
|
||||
end
|
||||
|
||||
def download_link(token, origin_name)
|
||||
UrlBuilder.url(@storage.uri, "index.php/apps/integration_openproject/direct", token, origin_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class FileInfoQuery < Base
|
||||
FILE_INFO_PATH = "ocs/v1.php/apps/integration_openproject/fileinfo"
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
http_options = ocs_api_request_headers.deep_merge(headers: { "Accept" => "application/json" })
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options:) do |http|
|
||||
file_info(http, input_data.file_id).bind do |json|
|
||||
validate_response_object(json).bind do |valid_response|
|
||||
create_storage_file_info(valid_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def file_info(http, file_id)
|
||||
response = http.get(UrlBuilder.url(@storage.uri, FILE_INFO_PATH, file_id))
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success(response.json(symbolize_keys: true))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def validate_response_object(json)
|
||||
error = Results::Error.new(source: self.class, payload: json)
|
||||
|
||||
case json.dig(:ocs, :data, :statuscode)
|
||||
when 200..299
|
||||
Success(json)
|
||||
when 403
|
||||
Failure(error.with(code: :forbidden))
|
||||
when 404
|
||||
Failure(error.with(code: :not_found))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def create_storage_file_info(json) # rubocop:disable Metrics/AbcSize
|
||||
data = json.dig(:ocs, :data)
|
||||
Results::StorageFileInfo.build(
|
||||
status: data[:status]&.downcase,
|
||||
status_code: data[:statuscode],
|
||||
id: data[:id]&.to_s,
|
||||
name: data[:name],
|
||||
last_modified_at: Time.zone.at(data[:mtime]).iso8601,
|
||||
created_at: Time.zone.at(data[:ctime]).iso8601,
|
||||
mime_type: data[:mimetype],
|
||||
size: data[:size],
|
||||
owner_name: data[:owner_name],
|
||||
owner_id: data[:owner_id],
|
||||
last_modified_by_name: data[:modifier_name],
|
||||
last_modified_by_id: data[:modifier_id],
|
||||
permissions: data[:dav_permissions],
|
||||
location: location(data[:path])
|
||||
)
|
||||
end
|
||||
|
||||
def location(file_path)
|
||||
prefix = "files/"
|
||||
idx = file_path.index(prefix)
|
||||
return "/" if idx == nil
|
||||
|
||||
idx += prefix.length - 1
|
||||
|
||||
UrlBuilder.path(file_path[idx..])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class FilePathToIdMapQuery < Base
|
||||
def initialize(*)
|
||||
super
|
||||
@propfind_query = PropfindQuery.new(@storage)
|
||||
end
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
origin_user_id(auth_strategy:).bind do |origin_user_id|
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: headers(input_data.depth)) do |http|
|
||||
# nc:acl-list is only required to avoid https://community.openproject.org/wp/49628. See comment #4.
|
||||
@propfind_query.call(http:,
|
||||
username: origin_user_id,
|
||||
path: input_data.folder.path,
|
||||
props: %w[oc:fileid nc:acl-list])
|
||||
.fmap do |obj|
|
||||
obj.transform_values { |value| StorageFileId.new(id: value["fileid"]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def headers(depth)
|
||||
{ headers: { "Depth" => depth.to_s.downcase } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class FilesInfoQuery < Base
|
||||
FILES_INFO_PATH = "ocs/v1.php/apps/integration_openproject/filesinfo"
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
return Success([]) if input_data.file_ids.empty?
|
||||
|
||||
with_tagged_logger do
|
||||
info "Retrieving file information for #{input_data.file_ids.join(', ')}"
|
||||
|
||||
http_options = ocs_api_request_headers.deep_merge(headers: { "Accept" => "application/json" })
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options:) do |http|
|
||||
files_info(http, input_data.file_ids).fmap { create_storage_file_infos(it) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def files_info(http, file_ids)
|
||||
response = http.post(UrlBuilder.url(@storage.uri, FILES_INFO_PATH), json: { fileIds: file_ids })
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
verify_successful_response(response.json(symbolize_keys: true), error)
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def verify_successful_response(json, error)
|
||||
if json.dig(:ocs, :meta, :status) == "ok"
|
||||
Success(json)
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def create_storage_file_infos(parsed_json)
|
||||
parsed_json.dig(:ocs, :data)&.map do |(key, value)|
|
||||
if value[:statuscode] == 200
|
||||
build_file_info(value).bind { it }
|
||||
else
|
||||
Results::StorageFileInfo.new(
|
||||
status: value[:status],
|
||||
status_code: value[:statuscode],
|
||||
id: key.to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def build_file_info(value)
|
||||
Results::StorageFileInfo.build(
|
||||
status: value[:status],
|
||||
status_code: value[:statuscode],
|
||||
id: value[:id].to_s,
|
||||
name: value[:name],
|
||||
last_modified_at: Time.zone.at(value[:mtime]),
|
||||
created_at: Time.zone.at(value[:ctime]),
|
||||
mime_type: value[:mimetype],
|
||||
size: value[:size],
|
||||
owner_name: value[:owner_name],
|
||||
owner_id: value[:owner_id],
|
||||
last_modified_by_name: value[:modifier_name],
|
||||
last_modified_by_id: value[:modifier_id],
|
||||
permissions: value[:dav_permissions],
|
||||
location: location(value[:path], value[:mimetype])
|
||||
)
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def location(file_path, mimetype)
|
||||
prefix = "files/"
|
||||
idx = file_path.index(prefix)
|
||||
return "/" if idx == nil
|
||||
|
||||
idx += prefix.length - 1
|
||||
# Remove the following when /filesinfo starts responding with a trailing slash for directory paths
|
||||
# in all supported versions of OpenProjectIntegation Nextcloud App.
|
||||
file_path << "/" if mimetype == "application/x-op-directory" && file_path[-1] != "/"
|
||||
|
||||
UrlBuilder.path(file_path[idx..])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class FilesQuery < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
origin_user_id(auth_strategy:).bind do |origin_user|
|
||||
@location_prefix = CGI.unescape(UrlBuilder.path(@storage.uri.path, "remote.php/dav/files", origin_user))
|
||||
make_request(auth_strategy:, folder: input_data.folder, origin_user:).bind do |xml|
|
||||
storage_files(xml)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_request(auth_strategy:, folder:, origin_user:)
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: depth_header(1)) do |http|
|
||||
response = http.request("PROPFIND",
|
||||
UrlBuilder.url(@storage.uri,
|
||||
"remote.php/dav/files",
|
||||
origin_user,
|
||||
folder.path),
|
||||
xml: requested_properties)
|
||||
handle_response(response)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success(response.xml)
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def requested_properties
|
||||
Nokogiri::XML::Builder.new do |xml|
|
||||
xml["d"].propfind(
|
||||
"xmlns:d" => "DAV:",
|
||||
"xmlns:oc" => "http://owncloud.org/ns"
|
||||
) do
|
||||
xml["d"].prop do
|
||||
xml["oc"].fileid
|
||||
xml["oc"].size
|
||||
xml["d"].getcontenttype
|
||||
xml["d"].getlastmodified
|
||||
xml["oc"].permissions
|
||||
xml["oc"].send(:"owner-display-name")
|
||||
end
|
||||
end
|
||||
end.to_xml
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def storage_files(xml)
|
||||
parent, *files = xml.xpath("//d:response").to_a.map { |file_element| storage_file(file_element) }
|
||||
|
||||
Results::StorageFileCollection.build(files:, parent:, ancestors: ancestors(parent.location))
|
||||
end
|
||||
|
||||
def ancestors(parent_location)
|
||||
path = parent_location.split("/")
|
||||
return [] if path.none?
|
||||
|
||||
path.take(path.count - 1).reduce([]) do |list, item|
|
||||
last = list.last
|
||||
prefix = last.nil? || last.location[-1] != "/" ? "/" : ""
|
||||
location = "#{last&.location}#{prefix}#{item}"
|
||||
list.append(forge_ancestor(location))
|
||||
end
|
||||
end
|
||||
|
||||
# The ancestors are simply derived objects from the parents location string. Until we have real information
|
||||
# from the nextcloud API about the path to the parent, we need to derive name, location and forge an ID.
|
||||
def forge_ancestor(location)
|
||||
Results::StorageFile.new(id: Digest::SHA256.hexdigest(location), name: name(location), location:)
|
||||
end
|
||||
|
||||
def name(location)
|
||||
location == "/" ? "Root" : CGI.unescape(location.split("/").last)
|
||||
end
|
||||
|
||||
def storage_file(file_element)
|
||||
location = location(file_element)
|
||||
|
||||
Results::StorageFile.new(
|
||||
id: id(file_element),
|
||||
name: name(location),
|
||||
size: size(file_element),
|
||||
mime_type: mime_type(file_element),
|
||||
last_modified_at: last_modified_at(file_element),
|
||||
created_by_name: created_by(file_element),
|
||||
location:,
|
||||
permissions: permissions(file_element)
|
||||
)
|
||||
end
|
||||
|
||||
def id(element)
|
||||
element
|
||||
.xpath(".//oc:fileid")
|
||||
.map(&:inner_text)
|
||||
.reject(&:empty?)
|
||||
.first
|
||||
end
|
||||
|
||||
def location(element)
|
||||
texts = element
|
||||
.xpath("d:href")
|
||||
.map(&:inner_text)
|
||||
|
||||
return nil if texts.empty?
|
||||
|
||||
element_name = texts.first.delete_prefix(@location_prefix)
|
||||
|
||||
return element_name if element_name == "/"
|
||||
|
||||
element_name.delete_suffix("/")
|
||||
end
|
||||
|
||||
def size(element)
|
||||
element
|
||||
.xpath(".//oc:size")
|
||||
.map(&:inner_text)
|
||||
.map { |e| Integer(e) }
|
||||
.first
|
||||
end
|
||||
|
||||
def mime_type(element)
|
||||
element
|
||||
.xpath(".//d:getcontenttype")
|
||||
.map(&:inner_text)
|
||||
.reject(&:empty?)
|
||||
.first || "application/x-op-directory"
|
||||
end
|
||||
|
||||
def last_modified_at(element)
|
||||
element
|
||||
.xpath(".//d:getlastmodified")
|
||||
.map { |e| DateTime.parse(e) }
|
||||
.first
|
||||
end
|
||||
|
||||
def created_by(element)
|
||||
element
|
||||
.xpath(".//oc:owner-display-name")
|
||||
.map(&:inner_text)
|
||||
.reject(&:empty?)
|
||||
.first
|
||||
end
|
||||
|
||||
def permissions(element)
|
||||
permissions_string =
|
||||
element
|
||||
.xpath(".//oc:permissions")
|
||||
.map(&:inner_text)
|
||||
.reject(&:empty?)
|
||||
.first
|
||||
|
||||
# Nextcloud Dav permissions:
|
||||
# https://github.com/nextcloud/server/blob/66648011c6bc278ace57230db44fd6d63d67b864/lib/public/Files/DavUtil.php
|
||||
result = []
|
||||
result << :readable if permissions_string&.include?("G")
|
||||
result << :writeable if permissions_string&.match?(/W|CK/)
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class GroupUsersQuery < Base
|
||||
include TaggedLogging
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: ocs_api_request_headers) do |http|
|
||||
url = UrlBuilder.url(@storage.uri, "ocs/v1.php/cloud/groups", input_data.group)
|
||||
info "Requesting user list for group #{input_data.group} via url #{url} "
|
||||
|
||||
handle_response(http.get(url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
handle_success_response(response, error)
|
||||
in { status: 405 }
|
||||
Failure(error.with(code: :not_allowed))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_success_response(response, error)
|
||||
xml = Nokogiri::XML(response.body.to_s)
|
||||
status_code = xml.xpath("/ocs/meta/statuscode").text
|
||||
|
||||
case status_code
|
||||
when "100"
|
||||
group_users = xml.xpath("/ocs/data/users/element").map(&:text)
|
||||
info "#{group_users.size} users found"
|
||||
Success(group_users)
|
||||
when "404"
|
||||
Failure(error.with(code: :group_does_not_exist))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+10
-10
@@ -29,16 +29,16 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Inputs
|
||||
UploadData = Data.define(:folder_id, :file_name) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(folder_id:, file_name:, contract: UploadDataContract.new)
|
||||
contract.call(folder_id:, file_name:)
|
||||
.to_monad
|
||||
.fmap { |result| new(file_name: result[:file_name], folder_id: result[:folder_id]) }
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class OpenFileLinkQuery < Base
|
||||
def call(input_data:, **)
|
||||
location_flag = input_data.open_location ? 0 : 1
|
||||
url = UrlBuilder.url(@storage.uri, "index.php/f/#{input_data.file_id}") + "?openfile=#{location_flag}"
|
||||
Success(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+8
-7
@@ -29,13 +29,14 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Inputs
|
||||
class UploadDataContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:folder_id).filled(:string)
|
||||
required(:file_name).filled(:string)
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class OpenStorageQuery < Base
|
||||
def call(**)
|
||||
Success(UrlBuilder.url(@storage.uri, "index.php/apps/files"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+11
-22
@@ -29,11 +29,11 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Internal
|
||||
class PropfindQuery
|
||||
module Queries
|
||||
class PropfindQuery < Base
|
||||
# Only for information purposes currently.
|
||||
# Probably a bit later we could validate `#call` parameters.
|
||||
#
|
||||
@@ -71,10 +71,6 @@ module Storages
|
||||
new(storage).call(http:, username:, path:, props:)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
def call(http:, username:, path:, props:)
|
||||
request_uri = UrlBuilder.url(@storage.uri, "remote.php/dav/files", username, path)
|
||||
response = http.request(:propfind, request_uri, xml: request_body(props))
|
||||
@@ -85,26 +81,19 @@ module Storages
|
||||
private
|
||||
|
||||
def handle_response(response, username)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
success_result(response, username)
|
||||
in { status: 401 }
|
||||
Util.failure(code: :unauthorized,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request not authorized!")
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 }
|
||||
Util.failure(code: :not_found,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request destination not found!")
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 405 }
|
||||
Util.failure(code: :not_allowed,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request method not allowed!")
|
||||
|
||||
Failure(error.with(code: :not_allowed))
|
||||
else
|
||||
Util.failure(code: :error,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request failed with unknown error!")
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -123,7 +112,7 @@ module Storages
|
||||
end
|
||||
end
|
||||
|
||||
ServiceResult.success(result:)
|
||||
Success(result)
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Queries
|
||||
class UploadLinkQuery < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
response = http.post(base_uri, json: payload_from(input_data))
|
||||
|
||||
handle_response(response).bind do |rsp|
|
||||
Results::UploadLink.build(destination: "#{upload_base_uri}/#{rsp[:token]}", method: :post)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_uri
|
||||
UrlBuilder.url(@storage.uri, "index.php/apps/integration_openproject/direct-upload-token")
|
||||
end
|
||||
|
||||
def upload_base_uri
|
||||
UrlBuilder.url(@storage.uri, "index.php/apps/integration_openproject/direct-upload")
|
||||
end
|
||||
|
||||
def payload_from(upload_data)
|
||||
{ folder_id: upload_data.folder_id }
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
info "Upload link generated successfully."
|
||||
Success(response.json(symbolize_keys: true))
|
||||
in { status: 404 }
|
||||
info "The parent folder was not found."
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
info "User authorization failed."
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
info "Unknown error happened."
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+26
-30
@@ -29,49 +29,45 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Internal
|
||||
class DeleteEntityCommand
|
||||
def self.call(storage:, auth_strategy:, location:)
|
||||
new(storage).call(auth_strategy:, location:)
|
||||
module Queries
|
||||
class UserQuery < Base
|
||||
def self.call(storage:, auth_strategy:)
|
||||
new(storage).call(auth_strategy:)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
def call(auth_strategy:, location:)
|
||||
origin_user_id = Util.origin_user_id(caller: self.class, storage: @storage, auth_strategy:)
|
||||
.on_failure { |error| return error }
|
||||
.result
|
||||
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.delete(
|
||||
UrlBuilder.url(@storage.uri, "remote.php/dav/files", origin_user_id, location)
|
||||
)
|
||||
def call(auth_strategy:)
|
||||
Authentication[auth_strategy].call(storage: @storage, http_options: ocs_api_request_headers) do |http|
|
||||
handle_response(http.get(UrlBuilder.url(@storage.uri, "/ocs/v1.php/cloud/user")))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
ServiceResult.success
|
||||
in { status: 404 }
|
||||
Util.failure(code: :not_found,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request destination not found!")
|
||||
handle_success_response(response)
|
||||
in { status: 401 }
|
||||
Util.failure(code: :unauthorized,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request not authorized!")
|
||||
Failure(error.with(code: :unauthorized))
|
||||
else
|
||||
Util.failure(code: :error,
|
||||
data: Util.error_data_from_response(caller: self.class, response:),
|
||||
log_message: "Outbound request failed with unknown error!")
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def handle_success_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
xml = Nokogiri::XML(response.body.to_s)
|
||||
statuscode = xml.xpath("/ocs/meta/statuscode").text
|
||||
|
||||
case statuscode
|
||||
when "100"
|
||||
Success({ id: xml.xpath("/ocs/data/id").text })
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
class StorageWizard < Wizard
|
||||
step :general_information, completed_if: ->(storage) { storage.host.present? && storage.name.present? }
|
||||
|
||||
# OAuth 2.0 SSO
|
||||
|
||||
step :storage_audience,
|
||||
section: :oauth_configuration,
|
||||
if: ->(storage) { storage.authenticate_via_idp? },
|
||||
completed_if: ->(storage) { storage.storage_audience.present? }
|
||||
|
||||
# Two-Way OAuth 2.0
|
||||
|
||||
step :oauth_application,
|
||||
section: :oauth_configuration,
|
||||
if: ->(storage) { storage.authenticate_via_storage? },
|
||||
completed_if: ->(storage) { storage.oauth_application.present? },
|
||||
preparation: :prepare_oauth_application
|
||||
|
||||
step :oauth_client,
|
||||
section: :oauth_configuration,
|
||||
if: ->(storage) { storage.authenticate_via_storage? },
|
||||
completed_if: ->(storage) { storage.oauth_client.present? },
|
||||
preparation: ->(storage) { storage.build_oauth_client }
|
||||
|
||||
step :automatically_managed_folders,
|
||||
completed_if: ->(storage) { !storage.automatic_management_unspecified? },
|
||||
preparation: :prepare_storage_for_automatic_management_form
|
||||
|
||||
private
|
||||
|
||||
def prepare_oauth_application(storage)
|
||||
create_result = ::Storages::OAuthApplications::CreateService.new(storage:, user:).call
|
||||
storage.oauth_application = create_result.result if create_result.success?
|
||||
end
|
||||
|
||||
def prepare_storage_for_automatic_management_form(storage)
|
||||
::Storages::Storages::SetProviderFieldsAttributesService
|
||||
.new(user:, model: storage, contract_class: EmptyContract)
|
||||
.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+15
-11
@@ -29,24 +29,28 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
class OpenStorageQuery
|
||||
def self.call(storage:, auth_strategy:)
|
||||
new(storage).call(auth_strategy:)
|
||||
class UserBoundAuthentication
|
||||
def self.call(user, storage)
|
||||
new(user, storage).call
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
def initialize(user, storage)
|
||||
@user = user
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
# rubocop:disable Lint/UnusedMethodArgument
|
||||
def call(auth_strategy:)
|
||||
ServiceResult.success(result: UrlBuilder.url(@storage.uri, "index.php/apps/files"))
|
||||
end
|
||||
def call
|
||||
key = if @storage.authenticate_via_idp? && @user.provided_by_oidc?
|
||||
:sso_user_token
|
||||
else
|
||||
:oauth_user_token
|
||||
end
|
||||
|
||||
# rubocop:enable Lint/UnusedMethodArgument
|
||||
Input::Strategy.build(key:, user: @user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Validators
|
||||
class AmpfConfigurationValidator < ConnectionValidators::BaseValidatorGroup
|
||||
def self.key = :ampf_configuration
|
||||
|
||||
private
|
||||
|
||||
def validate
|
||||
register_checks(
|
||||
:group_folder_app, :files_request, :userless_access, :group_folder_presence, :group_folder_contents
|
||||
)
|
||||
|
||||
group_folder_app_checks
|
||||
files_request_failed_with_unknown_error
|
||||
userless_access_denied
|
||||
group_folder_not_found
|
||||
with_unexpected_content
|
||||
end
|
||||
|
||||
def userless_access_denied
|
||||
files.or { it.code == :unauthorized and fail_check(:userless_access, :nc_userless_access_denied) }
|
||||
pass_check(:userless_access)
|
||||
end
|
||||
|
||||
def group_folder_app_checks
|
||||
required_version = SemanticVersion.parse(
|
||||
nextcloud_dependencies.dig("dependencies", "group_folders_app", "min_version")
|
||||
)
|
||||
|
||||
capabilities = Registry["nextcloud.queries.capabilities"].call(storage: @storage, auth_strategy: noop).value!
|
||||
dependency = I18n.t("storages.dependencies.nextcloud.group_folders_app")
|
||||
|
||||
if capabilities.group_folder_disabled?
|
||||
fail_check(:group_folder_app, :nc_dependency_missing, context: { dependency: })
|
||||
elsif capabilities.group_folder_version < required_version
|
||||
fail_check(:group_folder_app, :nc_dependency_version_mismatch, context: { dependency: })
|
||||
else
|
||||
pass_check(:group_folder_app)
|
||||
end
|
||||
end
|
||||
|
||||
def group_folder_not_found
|
||||
files.or { it.code == :not_found and fail_check(:group_folder_presence, :nc_group_folder_not_found) }
|
||||
pass_check(:group_folder_presence)
|
||||
end
|
||||
|
||||
def files_request_failed_with_unknown_error
|
||||
files.or do |failure|
|
||||
if failure.code == :error
|
||||
error "Connection validation failed with unknown error:\n" \
|
||||
"\tstorage: ##{@storage.id} #{@storage.name}\n" \
|
||||
"\trequest: Group folder content\n" \
|
||||
"\tstatus: #{failure}\n" \
|
||||
"\tresponse: #{failure.payload}"
|
||||
|
||||
fail_check(:files_request, :unknown_error)
|
||||
end
|
||||
end
|
||||
pass_check(:files_request)
|
||||
end
|
||||
|
||||
def with_unexpected_content
|
||||
unexpected_files = files.value!.reject { managed_project_folder_ids.include?(it.id) }
|
||||
return pass_check(:group_folder_contents) if unexpected_files.empty?
|
||||
|
||||
log_extraneous_files(unexpected_files)
|
||||
warn_check(:group_folder_contents, :nc_unexpected_content)
|
||||
end
|
||||
|
||||
def log_extraneous_files(unexpected_files)
|
||||
file_representation = unexpected_files.map do |file|
|
||||
"Name: #{file.name}, ID: #{file.id}, Location: #{file.location}"
|
||||
end
|
||||
|
||||
warn "Unexpected files/folder found in group folder:\n\t#{file_representation.join("\n\t")}"
|
||||
end
|
||||
|
||||
def auth_strategy = Registry["nextcloud.authentication.userless"].call
|
||||
|
||||
def managed_project_folder_ids
|
||||
@managed_project_folder_ids ||= ProjectStorage.automatic.where(storage: @storage)
|
||||
.pluck(:project_folder_id).to_set
|
||||
end
|
||||
|
||||
def files
|
||||
@files ||= Input::Files.build(folder: @storage.group_folder).bind do |input_data|
|
||||
Registry.resolve("#{@storage}.queries.files").call(storage: @storage, auth_strategy:, input_data:)
|
||||
end
|
||||
end
|
||||
|
||||
def noop = Input::Strategy.build(key: :noop)
|
||||
|
||||
def nextcloud_dependencies
|
||||
@nextcloud_dependencies ||= YAML.load_file(path_to_config).deep_stringify_keys
|
||||
end
|
||||
|
||||
def path_to_config = Rails.root.join("modules/storages/config/nextcloud_dependencies.yml")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Validators
|
||||
class AuthenticationValidator < ConnectionValidators::BaseValidatorGroup
|
||||
def self.key = :authentication
|
||||
|
||||
def initialize(storage)
|
||||
super
|
||||
@user = User.current
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate
|
||||
@storage.authenticate_via_idp? ? validate_sso : validate_oauth
|
||||
end
|
||||
|
||||
def validate_oauth
|
||||
register_checks(:existing_token, :user_bound_request)
|
||||
|
||||
oauth_token
|
||||
user_bound_request
|
||||
end
|
||||
|
||||
def oauth_token
|
||||
if OAuthClientToken.for_user_and_client(@user, @storage.oauth_client).exists?
|
||||
pass_check(:existing_token)
|
||||
else
|
||||
warn_check(:existing_token, :nc_oauth_token_missing, halt_validation: true)
|
||||
end
|
||||
end
|
||||
|
||||
def user_bound_request
|
||||
Registry["nextcloud.queries.user"]
|
||||
.call(storage: @storage, auth_strategy:)
|
||||
.or { fail_check(:user_bound_request, :"nc_oauth_request_#{it.code}") }
|
||||
|
||||
pass_check(:user_bound_request)
|
||||
end
|
||||
|
||||
def auth_strategy = Registry["nextcloud.authentication.user_bound"].call(@user, @storage)
|
||||
|
||||
def validate_sso
|
||||
register_checks(
|
||||
:non_provisioned_user,
|
||||
:provisioned_user_provider,
|
||||
:token_negotiable,
|
||||
:user_bound_request,
|
||||
:offline_access
|
||||
)
|
||||
|
||||
non_provisioned_user
|
||||
non_oidc_provisioned_user
|
||||
token_negotiable
|
||||
user_bound_request
|
||||
offline_access
|
||||
end
|
||||
|
||||
def non_provisioned_user
|
||||
if @user.identity_url.present?
|
||||
pass_check(:non_provisioned_user)
|
||||
else
|
||||
warn_check(:non_provisioned_user, :oidc_non_provisioned_user, halt_validation: true)
|
||||
end
|
||||
end
|
||||
|
||||
def non_oidc_provisioned_user
|
||||
if @user.authentication_provider.is_a?(OpenIDConnect::Provider)
|
||||
pass_check(:provisioned_user_provider)
|
||||
else
|
||||
warn_check(:provisioned_user_provider, :oidc_non_oidc_user, halt_validation: true)
|
||||
end
|
||||
end
|
||||
|
||||
def token_negotiable
|
||||
service = OpenIDConnect::UserTokens::FetchService.new(user: @user)
|
||||
|
||||
result = service.access_token_for(audience: @storage.audience)
|
||||
return pass_check(:token_negotiable) if result.success?
|
||||
|
||||
error_code =
|
||||
case result.failure
|
||||
in { code: /token_exchange/ | :unable_to_exchange_token }
|
||||
:oidc_token_exchange_failed
|
||||
in { code: /token_refresh/ }
|
||||
:oidc_token_refresh_failed
|
||||
in { code: :no_token_for_audience }
|
||||
:oidc_token_acquisition_failed
|
||||
else
|
||||
:unknown_error
|
||||
end
|
||||
|
||||
fail_check(:token_negotiable, error_code)
|
||||
end
|
||||
|
||||
def offline_access
|
||||
if @user.authentication_provider.scopes.include?("offline_access")
|
||||
pass_check(:offline_access)
|
||||
else
|
||||
warn_check(:offline_access, :offline_access_scope_missing)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Validators
|
||||
class ConnectionValidator < ConnectionValidators::BaseConnectionValidator
|
||||
register_group StorageConfigurationValidator
|
||||
register_group AuthenticationValidator, precondition: ->(_, result) { result.group(:base_configuration).non_failure? }
|
||||
register_group AmpfConfigurationValidator,
|
||||
precondition: ->(storage, result) do
|
||||
result.group(:base_configuration).non_failure? && storage.automatic_management_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module Nextcloud
|
||||
module Validators
|
||||
class StorageConfigurationValidator < ConnectionValidators::BaseValidatorGroup
|
||||
def self.key = :base_configuration
|
||||
|
||||
private
|
||||
|
||||
def validate
|
||||
register_checks(:storage_configured, :host_url_accessible, :capabilities_request,
|
||||
:dependencies_check, :dependencies_versions)
|
||||
|
||||
storage_configuration_status
|
||||
host_url_not_found
|
||||
capabilities_request_status
|
||||
missing_dependencies
|
||||
version_mismatch
|
||||
end
|
||||
|
||||
def storage_configuration_status
|
||||
if @storage.configured?
|
||||
pass_check(:storage_configured)
|
||||
else
|
||||
fail_check(:storage_configured, :not_configured)
|
||||
end
|
||||
end
|
||||
|
||||
def capabilities_request_status
|
||||
if capabilities.failure? && capabilities.failure.code != :not_found
|
||||
fail_check(:capabilities_request, :unknown_error)
|
||||
else
|
||||
pass_check(:capabilities_request)
|
||||
end
|
||||
end
|
||||
|
||||
def version_mismatch
|
||||
min_app_version = SemanticVersion.parse(nextcloud_dependencies.dig("dependencies", "integration_app",
|
||||
"min_version"))
|
||||
capabilities_result = capabilities.value!
|
||||
dependency = I18n.t("storages.dependencies.nextcloud.integration_app")
|
||||
|
||||
if capabilities_result.app_version < min_app_version
|
||||
fail_check(:dependencies_versions, :nc_dependency_version_mismatch, context: { dependency: })
|
||||
else
|
||||
pass_check(:dependencies_versions)
|
||||
end
|
||||
end
|
||||
|
||||
def missing_dependencies
|
||||
capabilities_result = capabilities.value!
|
||||
dependency = I18n.t("storages.dependencies.nextcloud.integration_app")
|
||||
|
||||
if capabilities_result.app_disabled?
|
||||
fail_check(:dependencies_check, :nc_dependency_missing, context: { dependency: })
|
||||
else
|
||||
pass_check(:dependencies_check)
|
||||
end
|
||||
end
|
||||
|
||||
def host_url_not_found
|
||||
if capabilities.failure? && capabilities.failure.code == :not_found
|
||||
fail_check(:host_url_accessible, :nc_host_not_found)
|
||||
else
|
||||
pass_check(:host_url_accessible)
|
||||
end
|
||||
end
|
||||
|
||||
def noop = Input::Strategy.build(key: :noop)
|
||||
|
||||
def capabilities
|
||||
@capabilities ||= Registry.resolve("#{@storage}.queries.capabilities")
|
||||
.call(storage: @storage, auth_strategy: noop)
|
||||
end
|
||||
|
||||
def nextcloud_dependencies
|
||||
@nextcloud_dependencies ||= YAML.load_file(path_to_config).deep_stringify_keys!
|
||||
end
|
||||
|
||||
def path_to_config = Rails.root.join("modules/storages/config/nextcloud_dependencies.yml")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+23
-23
@@ -29,38 +29,38 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
class UserQuery
|
||||
def self.call(storage:, auth_strategy:)
|
||||
new(storage).call(auth_strategy:)
|
||||
class Base
|
||||
include TaggedLogging
|
||||
include Dry::Monads::Result(Results::Error)
|
||||
|
||||
def self.call(storage:, auth_strategy:, input_data:)
|
||||
new(storage).call(auth_strategy:, input_data:)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
def call(auth_strategy:)
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.get(UrlBuilder.url(@storage.uri, "/v1.0/me"))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
ServiceResult.success(result: { id: response.json["id"] })
|
||||
in { status: 401 }
|
||||
ServiceResult.failure(result: :unauthorized, errors: StorageError.new(code: :unauthorized))
|
||||
in { status: 403 }
|
||||
ServiceResult.failure(result: :forbidden, errors: StorageError.new(code: :forbidden))
|
||||
else
|
||||
data = StorageErrorData.new(source: self.class, payload: response)
|
||||
ServiceResult.failure(result: :error, errors: StorageError.new(code: :error, data:))
|
||||
end
|
||||
# @param error [Results::Error]
|
||||
def log_storage_error(error, context = {})
|
||||
data = case error.payload
|
||||
in { status: Integer }
|
||||
{ status: error.payload&.status, body: error.payload&.body.to_s }
|
||||
else
|
||||
error.payload.to_s
|
||||
end
|
||||
|
||||
error_message = context.merge({ error_code: error.code, data: })
|
||||
error error_message
|
||||
end
|
||||
|
||||
def base_uri
|
||||
URI.parse(UrlBuilder.url(@storage.uri, "/v1.0/drives", @storage.drive_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
module Commands
|
||||
class CopyTemplateFolderCommand < Base
|
||||
def initialize(storage)
|
||||
super
|
||||
@data = Results::CopyTemplateFolder.new(id: nil, polling_url: nil, requires_polling: true)
|
||||
end
|
||||
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
info "Requesting Copy of folder #{input_data.source} to #{input_data.destination}"
|
||||
Authentication[auth_strategy].call(storage: @storage) do |httpx|
|
||||
handle_response(
|
||||
httpx.post(url_for(input_data.source) + query, json: { name: input_data.destination })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 202 }
|
||||
Success(@data.with(polling_url: response.headers[:location]))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 403 }
|
||||
Failure(error.with(code: :forbidden))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def url_for(source_location)
|
||||
UrlBuilder.url(base_uri, "/items", source_location, "/copy")
|
||||
end
|
||||
|
||||
def query = "?@microsoft.graph.conflictBehavior=fail"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
module Commands
|
||||
class CreateFolderCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
info "Creating folder with args: #{input_data.to_h} | #{auth_strategy.value_or({}).to_h}"
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.post(url_for(input_data.parent_location), body: payload(input_data.folder_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def url_for(parent_location)
|
||||
if parent_location.root?
|
||||
UrlBuilder.url(base_uri, "/root/children")
|
||||
else
|
||||
UrlBuilder.url(base_uri, "/items", parent_location.path, "/children")
|
||||
end
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(payload: response, source: self.class)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
info "Folder successfully created."
|
||||
StorageFileTransformer.new.transform(response.json(symbolize_keys: true))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def payload(folder_name)
|
||||
{ name: folder_name, folder: {}, "@microsoft.graph.conflictBehavior" => "fail" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+29
-23
@@ -1,4 +1,4 @@
|
||||
# frozen_string_literal:true
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
@@ -29,31 +29,37 @@
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Peripherals
|
||||
module StorageInteraction
|
||||
module AuthenticationStrategies
|
||||
module Failures
|
||||
Builder = ->(code:, log_message:, data:) do
|
||||
storage_error = StorageError.new(code:, log_message:, data:)
|
||||
ServiceResult.failure(result: code, errors: storage_error)
|
||||
end
|
||||
|
||||
ErrorData = ->(response:, source:) do
|
||||
payload =
|
||||
case response
|
||||
in { content_type: { mime_type: "application/json" } }
|
||||
response.json
|
||||
in { content_type: { mime_type: "text/xml" } }
|
||||
response.xml
|
||||
else
|
||||
response.body.to_s
|
||||
module Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
module Commands
|
||||
class DeleteFolderCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response http.delete(
|
||||
UrlBuilder.url(base_uri, "items", input_data.location)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
StorageErrorData.new(source:, payload:)
|
||||
end
|
||||
private
|
||||
|
||||
TimeoutErrorData = ->(error:, source:) do
|
||||
StorageErrorData.new(source:, payload: error.to_s)
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
Success()
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
module Commands
|
||||
class RenameFileCommand < Base
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
info "Renaming file: #{input_data.inspect}"
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
handle_response(
|
||||
http.patch(UrlBuilder.url(base_uri, "items", input_data.location),
|
||||
body: { name: input_data.new_name })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(source: self.class, payload: response)
|
||||
|
||||
case response
|
||||
in { status: 200..299 }
|
||||
StorageFileTransformer.new.transform(response.json(symbolize_keys: true))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 403 }
|
||||
Failure(error.with(code: :forbidden))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
in { status: 409 }
|
||||
Failure(error.with(code: :conflict))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
# 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 Adapters
|
||||
module Providers
|
||||
module OneDrive
|
||||
module Commands
|
||||
class SetPermissionsCommand < Base
|
||||
include Dry::Monads::Do.for(:call)
|
||||
|
||||
PermissionUpdateData = ::Data.define(:role, :permission_ids, :user_ids, :drive_item_id) do
|
||||
def create? = permission_ids.empty? && user_ids.any?
|
||||
|
||||
def delete? = permission_ids.any? && user_ids.empty?
|
||||
|
||||
def update? = permission_ids.any? && user_ids.any?
|
||||
end
|
||||
|
||||
PermissionFilter = lambda do |role, permission|
|
||||
next unless permission[:roles].member?(role)
|
||||
|
||||
permission[:id]
|
||||
end.curry
|
||||
|
||||
private_constant :PermissionFilter, :PermissionUpdateData
|
||||
|
||||
# @param auth_strategy [AuthenticationStrategy] The authentication strategy to use.
|
||||
# @param input_data [Inputs::SetPermissions] The data needed for setting permissions, containing the file id
|
||||
# and the permissions for an array of users.
|
||||
def call(auth_strategy:, input_data:)
|
||||
with_tagged_logger do
|
||||
Authentication[auth_strategy].call(storage: @storage) do |http|
|
||||
item = input_data.file_id
|
||||
yield item_exists?(http, item)
|
||||
|
||||
current_permissions = yield get_current_permissions(http, item)
|
||||
info "Read and write permissions found: #{current_permissions}"
|
||||
|
||||
role_to_user_map(input_data).each_pair do |role, user_ids|
|
||||
apply_permission_changes(
|
||||
PermissionUpdateData.new(role:,
|
||||
user_ids:,
|
||||
permission_ids: current_permissions[role],
|
||||
drive_item_id: item),
|
||||
http
|
||||
)
|
||||
end
|
||||
|
||||
Success()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def role_to_user_map(input_data)
|
||||
input_data.user_permissions
|
||||
.each_with_object({ read: [], write: [] }) do |user_permission_set, map|
|
||||
if user_permission_set[:permissions].include?(:write_files)
|
||||
map[:write] << user_permission_set[:user_id]
|
||||
elsif user_permission_set[:permissions].include?(:read_files)
|
||||
map[:read] << user_permission_set[:user_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def item_exists?(http, item_id)
|
||||
info "Checking if folder #{item_id} exists"
|
||||
handle_response(http.get(item_path(item_id)))
|
||||
end
|
||||
|
||||
def get_current_permissions(http, path)
|
||||
info "Getting current permissions for #{path}"
|
||||
handle_response(http.get(permissions_path(path))).fmap { |result| extract_permission_ids(result[:value]) }
|
||||
end
|
||||
|
||||
def apply_permission_changes(update_data, http)
|
||||
return delete_permissions(update_data, http) if update_data.delete?
|
||||
return create_permissions(update_data, http) if update_data.create?
|
||||
|
||||
update_permissions(update_data, http) if update_data.update?
|
||||
end
|
||||
|
||||
def update_permissions(update_data, http)
|
||||
info "Updating permissions on #{update_data.drive_item_id}"
|
||||
delete_permissions(update_data, http)
|
||||
create_permissions(update_data, http)
|
||||
end
|
||||
|
||||
def create_permissions(update_data, http)
|
||||
drive_recipients = update_data.user_ids.map { |id| { objectId: id } }
|
||||
|
||||
info "Creating #{update_data.role} permissions on #{update_data.drive_item_id} for #{drive_recipients}"
|
||||
response = http.post(invite_path(update_data.drive_item_id),
|
||||
json: {
|
||||
requireSignIn: true,
|
||||
sendInvitation: false,
|
||||
roles: [update_data.role],
|
||||
recipients: drive_recipients
|
||||
})
|
||||
|
||||
handle_response(response).or { |error| log_storage_error(error) }
|
||||
end
|
||||
|
||||
def delete_permissions(update_data, http)
|
||||
info "Removing permissions on #{update_data.drive_item_id}"
|
||||
|
||||
update_data.permission_ids.each do |permission_id|
|
||||
handle_response(
|
||||
http.delete(permission_path(update_data.drive_item_id, permission_id))
|
||||
).or { |error| log_storage_error(error) }
|
||||
end
|
||||
end
|
||||
|
||||
def extract_permission_ids(permission_set)
|
||||
write_permissions = permission_set.filter_map(&PermissionFilter.call("write"))
|
||||
read_permissions = permission_set.filter_map(&PermissionFilter.call("read"))
|
||||
|
||||
{ read: read_permissions, write: write_permissions }
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
error = Results::Error.new(payload: response, source: self.class)
|
||||
|
||||
case response
|
||||
in { status: 200 }
|
||||
Success(response.json(symbolize_keys: true))
|
||||
in { status: 204 }
|
||||
Success(result: response)
|
||||
in { status: 400 }
|
||||
Failure(error.with(code: :bad_request))
|
||||
in { status: 401 }
|
||||
Failure(error.with(code: :unauthorized))
|
||||
in { status: 403 }
|
||||
Failure(error.with(code: :forbidden))
|
||||
in { status: 404 }
|
||||
Failure(error.with(code: :not_found))
|
||||
else
|
||||
Failure(error.with(code: :error))
|
||||
end
|
||||
end
|
||||
|
||||
def permission_path(item_id, permission_id) = "#{permissions_path(item_id)}/#{permission_id}"
|
||||
|
||||
def permissions_path(item_id) = "#{item_path(item_id)}/permissions"
|
||||
|
||||
def invite_path(item_id) = "#{item_path(item_id)}/invite"
|
||||
|
||||
def item_path(item_id)
|
||||
UrlBuilder.url(base_uri, "/items", item_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user