Skip to content

Commit

Permalink
Merge pull request #15081 from opf/implementation/53622-use-authentic…
Browse files Browse the repository at this point in the history
…ation-in-files_info-query

[#53622] use authentication in files info queries
  • Loading branch information
Kharonus authored Apr 2, 2024
2 parents c29279d + a2e0524 commit 59d566c
Show file tree
Hide file tree
Showing 26 changed files with 258 additions and 569 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def call(storage:, http_options: {}, &)
data:)
end

opts = http_options.merge({ headers: { "Authorization" => "Bearer #{current_token.access_token}" } })
opts = http_options.deep_merge({ headers: { "Authorization" => "Bearer #{current_token.access_token}" } })
response_with_current_token = yield OpenProject.httpx.with(opts)

if response_with_current_token.success? || response_with_current_token.result != :unauthorized
Expand Down Expand Up @@ -82,10 +82,9 @@ def refresh_and_retry(config, http_options, token, &)
.with_access_token
.with(http_options)
rescue HTTPX::HTTPError => e
data = ::Storages::StorageErrorData.new(source: self.class, payload: e.response.json)
return Failures::Builder.call(code: :unauthorized,
log_message: "Error while refreshing OAuth token.",
data:)
data: error_data_from_response(e.response))
end

response = yield http_session
Expand All @@ -105,6 +104,20 @@ def refresh_and_retry(config, http_options, token, &)

# rubocop:enable Metrics/AbcSize

def error_data_from_response(response)
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
end

::Storages::StorageErrorData.new(source: self.class, payload:)
end

def update_refreshed_token(token, http_session)
oauth = http_session.instance_variable_get(:@options).oauth_session
access_token = oauth.access_token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,41 +33,38 @@ class FileInfoQuery
using Storages::Peripherals::ServiceResultRefinements

FILE_INFO_PATH = "ocs/v1.php/apps/integration_openproject/fileinfo"
Auth = ::Storages::Peripherals::StorageInteraction::Authentication

def initialize(storage)
@uri = storage.uri
@configuration = storage.oauth_configuration
def self.call(storage:, auth_strategy:, file_id:)
new(storage).call(auth_strategy:, file_id:)
end

def self.call(storage:, user:, file_id:)
new(storage).call(user:, file_id:)
def initialize(storage)
@storage = storage
end

def call(user:, file_id:)
Util.token(user:, configuration: @configuration) do |token|
file_info(file_id, token).map(&parse_json) >> handle_failure >> create_storage_file_info
def call(auth_strategy:, file_id:)
http_options = Util.ocs_api_request.deep_merge(Util.accept_json)
Auth[auth_strategy].call(storage: @storage, http_options:) do |http|
file_info(http, file_id).map(&parse_json) >> handle_failure >> create_storage_file_info
end
end

private

def file_info(file_id, token)
response = OpenProject
.httpx
.with(headers: { "Authorization" => "Bearer #{token.access_token}",
"Accept" => "application/json",
"OCS-APIRequest" => "true" })
.get(Util.join_uri_path(@uri, FILE_INFO_PATH, file_id))
def file_info(http, file_id)
response = http.get(Util.join_uri_path(@storage.uri, FILE_INFO_PATH, file_id))
error_data = Storages::StorageErrorData.new(source: self.class, payload: response)

case response
in { status: 200..299 }
ServiceResult.success(result: response.body)
in { status: 404 }
Util.error(:not_found, "Outbound request destination not found!", response)
Util.error(:not_found, "Outbound request destination not found!", error_data)
in { status: 401 }
Util.error(:unauthorized, "Outbound request not authorized!", response)
Util.error(:unauthorized, "Outbound request not authorized!", error_data)
else
Util.error(:error, "Outbound request failed!")
Util.error(:error, "Outbound request failed!", error_data)
end
end

Expand All @@ -81,15 +78,17 @@ def parse_json

def handle_failure
->(response_object) do
error_data = Storages::StorageErrorData.new(source: self.class, payload: response_object)

case response_object.ocs.data.statuscode
when 200..299
ServiceResult.success(result: response_object)
when 403
Util.error(:forbidden, "Access to storage file forbidden!", response_object)
Util.error(:forbidden, "Access to storage file forbidden!", error_data)
when 404
Util.error(:not_found, "Storage file not found!", response_object)
Util.error(:not_found, "Storage file not found!", error_data)
else
Util.error(:error, "Outbound request failed!", response_object)
Util.error(:error, "Outbound request failed!", error_data)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ class FilesInfoQuery
using Storages::Peripherals::ServiceResultRefinements

FILES_INFO_PATH = "ocs/v1.php/apps/integration_openproject/filesinfo"
Auth = ::Storages::Peripherals::StorageInteraction::Authentication

def initialize(storage)
@uri = storage.uri
@configuration = storage.oauth_configuration
def self.call(storage:, auth_strategy:, file_ids:)
new(storage).call(auth_strategy:, file_ids:)
end

def self.call(storage:, user:, file_ids: [])
new(storage).call(user:, file_ids:)
def initialize(storage)
@storage = storage
end

def call(user:, file_ids: [])
def call(auth_strategy:, file_ids:)
if file_ids.nil?
return Util.error(:error, "File IDs can not be nil", file_ids)
end
Expand All @@ -52,32 +52,27 @@ def call(user:, file_ids: [])
return ServiceResult.success(result: [])
end

Util.token(user:, configuration: @configuration) do |token|
files_info(file_ids, token).map(&parse_json) >> handle_failure >> create_storage_file_infos
http_options = Util.ocs_api_request.deep_merge(Util.accept_json)
Auth[auth_strategy].call(storage: @storage, http_options:) do |http|
files_info(http, file_ids).map(&parse_json) >> handle_failure >> create_storage_file_infos
end
end

private

def files_info(file_ids, token)
response = OpenProject
.httpx
.with(headers: { "Authorization" => "Bearer #{token.access_token}",
"Accept" => "application/json",
"Content-Type" => "application/json",
"OCS-APIRequest" => "true" })
.post(Util.join_uri_path(@uri.to_s, FILES_INFO_PATH),
json: { fileIds: file_ids })
def files_info(http, file_ids)
response = http.post(Util.join_uri_path(@storage.uri, FILES_INFO_PATH), json: { fileIds: file_ids })
error_data = Storages::StorageErrorData.new(source: self.class, payload: response)

case response
in { status: 200..299 }
ServiceResult.success(result: response.body.to_s)
ServiceResult.success(result: response.body)
in { status: 404 }
Util.error(:not_found, "Outbound request destination not found!", response)
Util.error(:not_found, "Outbound request destination not found!", error_data)
in { status: 401 }
Util.error(:unauthorized, "Outbound request not authorized!", response)
Util.error(:unauthorized, "Outbound request not authorized!", error_data)
else
Util.error(:error, "Outbound request failed!", response)
Util.error(:error, "Outbound request failed!", error_data)
end
end

Expand All @@ -94,7 +89,8 @@ def handle_failure
if response_object.ocs.meta.status == "ok"
ServiceResult.success(result: response_object)
else
Util.error(:error, "Outbound request failed!", response_object)
error_data = Storages::StorageErrorData.new(source: self.class, payload: response_object)
Util.error(:error, "Outbound request failed!", error_data)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ module Peripherals
module StorageInteraction
module Nextcloud
class OpenFileLinkQuery
def initialize(storage)
@uri = storage.uri
def self.call(storage:, auth_strategy:, file_id:, open_location: false)
new(storage).call(auth_strategy:, file_id:, open_location:)
end

def self.call(storage:, user:, file_id:, open_location: false)
new(storage).call(user:, file_id:, open_location:)
def initialize(storage)
@storage = storage
end

# rubocop:disable Lint/UnusedMethodArgument
def call(user:, file_id:, open_location: false)
def call(auth_strategy:, file_id:, open_location: false)
location_flag = open_location ? 0 : 1
ServiceResult.success(result: Util.join_uri_path(@uri, "index.php/f/#{file_id}?openfile=#{location_flag}"))
ServiceResult.success(result: Util.join_uri_path(@storage.uri, "index.php/f/#{file_id}?openfile=#{location_flag}"))
end

# rubocop:enable Lint/UnusedMethodArgument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ module Storages::Peripherals::StorageInteraction::Nextcloud::Util

class << self
def escape_path(path)
escaped_path = path.split('/').map { |i| CGI.escapeURIComponent(i) }.join('/')
escaped_path << '/' if path[-1] == '/'
escaped_path = path.split("/").map { |i| CGI.escapeURIComponent(i) }.join("/")
escaped_path << "/" if path[-1] == "/"
escaped_path
end

def ocs_api_request
{ headers: { 'OCS-APIRequest' => 'true' } }
{ headers: { "OCS-APIRequest" => "true" } }
end

def accept_json
{ headers: { "Accept" => "application/json" } }
end

def webdav_request_with_depth(number)
{ headers: { 'Depth' => number } }
{ headers: { "Depth" => number } }
end

def error(code, log_message = nil, data = nil)
Expand All @@ -69,7 +73,7 @@ def token(user:, configuration:, &)
end,
on_failure: ->(_) do
error(:unauthorized,
'Query could not be created! No access token found!',
"Query could not be created! No access token found!",
Storages::StorageErrorData.new(source: connection_manager))
end
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,29 @@ module StorageInteraction
module OneDrive
class FileInfoQuery
FIELDS = %w[id name fileSystemInfo file folder size createdBy lastModifiedBy parentReference].freeze
Auth = ::Storages::Peripherals::StorageInteraction::Authentication

def self.call(storage:, user:, file_id:)
new(storage).call(user:, file_id:)
def self.call(storage:, auth_strategy:, file_id:)
new(storage).call(auth_strategy:, file_id:)
end

def initialize(storage)
@storage = storage
@delegate = Internal::DriveItemQuery.new(storage)
end

def call(user:, file_id:)
def call(auth_strategy:, file_id:)
if file_id.nil?
return ServiceResult.failure(
result: :error,
errors: ::Storages::StorageError.new(code: :error,
data: StorageErrorData.new(source: self.class),
log_message: 'File ID can not be nil')
log_message: "File ID can not be nil")
)
end

Util.using_user_token(@storage, user) do |token|
@delegate.call(token:, drive_item_id: file_id, fields: FIELDS).map(&storage_file_infos)
Auth[auth_strategy].call(storage: @storage) do |http|
@delegate.call(http:, drive_item_id: file_id, fields: FIELDS).map(&storage_file_infos)
end
end

Expand All @@ -65,7 +66,7 @@ def call(user:, file_id:)
def storage_file_infos
->(json) do
StorageFileInfo.new(
status: 'ok',
status: "ok",
status_code: 200,
id: json[:id],
name: json[:name],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,27 @@ module OneDrive
class FilesInfoQuery
using ServiceResultRefinements

def self.call(storage:, user:, file_ids: [])
new(storage).call(user:, file_ids:)
def self.call(storage:, auth_strategy:, file_ids: [])
new(storage).call(auth_strategy:, file_ids:)
end

def initialize(storage)
@storage = storage
@uri = storage.uri
end

def call(user:, file_ids:)
def call(auth_strategy:, file_ids:)
if file_ids.nil?
return ServiceResult.failure(
result: :error,
errors: ::Storages::StorageError.new(code: :error, log_message: 'File IDs can not be nil')
errors: ::Storages::StorageError.new(code: :error, log_message: "File IDs can not be nil")
)
end

result = file_ids.map do |file_id|
file_info_result = FileInfoQuery.call(storage: @storage, user:, file_id:)
file_info_result = FileInfoQuery.call(storage: @storage, auth_strategy:, file_id:)
if file_info_result.failure? &&
file_info_result.error_source.is_a?(::OAuthClients::ConnectionManager)
# errors in the connection manager must short circuit the query and return the error
file_info_result.error_source.module_parent == AuthenticationStrategies
# errors in the authentication strategies must short circuit the query and return the error
return file_info_result
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ module StorageInteraction
module OneDrive
module Internal
class ChildrenQuery
UTIL = ::Storages::Peripherals::StorageInteraction::OneDrive::Util

def self.call(storage:, http:, folder:, fields: [])
new(storage).call(http:, folder:, fields:)
end
Util = ::Storages::Peripherals::StorageInteraction::OneDrive::Util

def initialize(storage)
@storage = storage
Expand All @@ -47,7 +43,7 @@ def initialize(storage)

def call(http:, folder:, fields: [])
select_url_query = if fields.empty?
''
""
else
"?$select=#{fields.join(',')}"
end
Expand All @@ -58,7 +54,7 @@ def call(http:, folder:, fields: [])
private

def make_children_request(folder, http, select_url_query)
response = http.get(UTIL.join_uri_path(@uri, uri_path_for(folder) + select_url_query))
response = http.get(Util.join_uri_path(@uri, uri_path_for(folder) + select_url_query))
handle_responses(response)
end

Expand All @@ -68,13 +64,13 @@ def handle_responses(response)
ServiceResult.success(result: response.json(symbolize_keys: true))
in { status: 404 }
ServiceResult.failure(result: :not_found,
errors: UTIL.storage_error(response:, code: :not_found, source: self))
errors: Util.storage_error(response:, code: :not_found, source: self))
in { status: 403 }
ServiceResult.failure(result: :forbidden,
errors: UTIL.storage_error(response:, code: :forbidden, source: self))
errors: Util.storage_error(response:, code: :forbidden, source: self))
in { status: 401 }
ServiceResult.failure(result: :unauthorized,
errors: UTIL.storage_error(response:, code: :unauthorized, source: self))
errors: Util.storage_error(response:, code: :unauthorized, source: self))
else
data = ::Storages::StorageErrorData.new(source: self.class, payload: response)
ServiceResult.failure(result: :error, errors: ::Storages::StorageError.new(code: :error, data:))
Expand Down
Loading

0 comments on commit 59d566c

Please sign in to comment.