From 269a04f403c55d6d9d3f71b3c7bcb053ca98d510 Mon Sep 17 00:00:00 2001 From: Giacomo Furlan Date: Mon, 4 Nov 2024 10:58:38 +0200 Subject: [PATCH] Fix webui file not deleted bug - Remove object-delete-version check - ISSUE: CSCEUDAT-1504 Co-authored-by: Johannes Lares --- b2share/modules/files/ext.py | 8 +- b2share/modules/files/views.py | 121 +++++++++++++++++++++-------- webui/src/components/editfiles.jsx | 4 +- webui/src/data/server.js | 12 ++- 4 files changed, 103 insertions(+), 42 deletions(-) diff --git a/b2share/modules/files/ext.py b/b2share/modules/files/ext.py index daa6e0065e..2cec200f9f 100644 --- a/b2share/modules/files/ext.py +++ b/b2share/modules/files/ext.py @@ -26,7 +26,7 @@ from __future__ import absolute_import, print_function from .cli import files as files_cmd - +from .views import object_view_object_resources class B2ShareFiles(object): """B2Share Files extension.""" @@ -40,7 +40,11 @@ def init_app(self, app): """Flask application initialization.""" self.init_config(app) app.cli.add_command(files_cmd) - app.extensions['b2share-files'] = self + app.extensions['b2share-files-rest'] = self + @app.before_first_request + def replace_files_rest_object_view(): + app.view_functions['invenio_files_rest.object_api'] = object_view_object_resources + def init_config(self, app): """Initialize configuration.""" diff --git a/b2share/modules/files/views.py b/b2share/modules/files/views.py index e6ef34b872..0cebd345fd 100644 --- a/b2share/modules/files/views.py +++ b/b2share/modules/files/views.py @@ -21,42 +21,95 @@ from __future__ import absolute_import -from flask import Blueprint, abort, request, jsonify -from invenio_rest import ContentNegotiatedMethodView -# from b2handle.handleclient import EUDATHandleClient +from invenio_files_rest.views import ( + ObjectResource, + invalid_subresource_validator, + need_permissions +) +from invenio_files_rest.serializer import json_serializer +from invenio_files_rest.models import ObjectVersion +from webargs import fields +from invenio_files_rest.tasks import remove_file_data +from invenio_db import db -blueprint = Blueprint( - 'handle', - __name__, -) -# class B2HandleRetriever(ContentNegotiatedMethodView): -# """Class for handling the b2handle record retrieval.""" -# view_name = 'b2handle_retriever' -# -# def __init__(self, **kwargs): -# -# default_media_type = 'application/json' -# -# super(B2HandleRetriever, self).__init__( -# serializers={ -# default_media_type: lambda response: jsonify(response) -# }, -# default_method_media_type={ -# 'GET': default_media_type, -# }, -# default_media_type=default_media_type, -# **kwargs -# ) -# -# def get(self, prefix, file_pid, **kwargs): -# eudat_handle_client = EUDATHandleClient() -# return eudat_handle_client.retrieve_handle_record(prefix + "/" + file_pid) -# -# -# blueprint.add_url_rule('/handle//', -# view_func=B2HandleRetriever.as_view( -# B2HandleRetriever.view_name)) +class B2ShareObjectResource(ObjectResource): + """Object item resource for B2SHARE. + + Adds capability to enable user to download of a file with a JWT token, + without explicitly giving this user permission to file. + Enables to sharing of restricted files or files from draft records + to anonymous (i.e. not logged in) users. + """ + + view_name = 'b2share_files_rest_object_view' + + get_args = { + 'version_id': fields.UUID( + location='query', + load_from='versionId', + missing=None, + ), + 'upload_id': fields.UUID( + location='query', + load_from='uploadId', + missing=None, + ), + 'uploads': fields.Raw( + location='query', + validate=invalid_subresource_validator, + ), + 'encoded_jwt': fields.Str( + location='query', + load_from='jwt', + missing=None, + ), + } + + def __init__(self, *args, **kwargs): + """Constructor.""" + super(B2ShareObjectResource, self).__init__(*args, **kwargs) + + + @need_permissions( + lambda self, bucket, obj, *args: obj, + 'object-delete', + hidden=False, # Because get_object permission check has already run + ) + def delete_object(self, bucket, obj, version_id): + """Delete an existing object. + + :param bucket: The bucket (instance or id) to get the object from. + :param obj: A :class:`invenio_files_rest.models.ObjectVersion` + instance. + :param version_id: The version ID. + :returns: A Flask response. + """ + if version_id is None: + # Create a delete marker. + with db.session.begin_nested(): + ObjectVersion.delete(bucket, obj.key) + db.session.commit() + else: + # Permanently delete specific object version. + # check_permission( + # current_permission_factory(bucket, 'object-delete-version'), + # hidden=False, + #) + obj.remove() + db.session.commit() + if obj.file_id: + remove_file_data.delay(str(obj.file_id)) + + return self.make_response('', 204) + + +object_view_object_resources = B2ShareObjectResource.as_view( + 'b2share_object_api', + serializers={ + 'application/json': json_serializer, + } +) \ No newline at end of file diff --git a/webui/src/components/editfiles.jsx b/webui/src/components/editfiles.jsx index 061a143877..0c0d3e51fe 100644 --- a/webui/src/components/editfiles.jsx +++ b/webui/src/components/editfiles.jsx @@ -446,7 +446,7 @@ export const EditFiles = React.createClass({ }, removeRecordFile: function(f) { - serverCache.deleteFile(this.props.record, f.key); + serverCache.deleteFile(this.props.record, f.key, f.version_id); }, transferFileCallback(file, status, param) { @@ -821,7 +821,7 @@ export const PersistentIdentifier = React.createClass({ let prefix = ""; let pid = this.props.pid; - + for (const p of this.KNOWN_PREFIXES) { if (pid.indexOf(p) === 0) { prefix = p; diff --git a/webui/src/data/server.js b/webui/src/data/server.js index db0a71e360..5ff9d395b8 100644 --- a/webui/src/data/server.js +++ b/webui/src/data/server.js @@ -243,13 +243,17 @@ class FilePoster { return xhr; } - delete(successFn) { + delete(successFn, version) { if (this.xhr && this.xhr.abort) { this.xhr.abort(); } const completeFn = () => {this.xhr = null}; + var extension = "" + if (version){ + extension = "?versionId="+version + } ajaxDelete({ - url: this.url, + url: this.url + extension, successFn: (data) => { completeFn(); successFn(data); }, completeFn: completeFn, }); @@ -829,10 +833,10 @@ class ServerCache { return this.posters.files.get(draft.get('id')).get(fileObject.name).put(fileObject, progFn); } - deleteFile(draft, fileKey) { + deleteFile(draft, fileKey, version) { return this.posters.files.get(draft.get('id')).get(fileKey).delete(() => { this.getDraftFiles(draft.get('id'), true); // force fetch files - }); + }, version); } updateDraft(id, metadata, successFn) {