/files: implemented /lock, /unlock endpoints
This commit is contained in:
parent
c0d4be8363
commit
bdabb4a85a
@ -18,11 +18,15 @@ class FilesMetadataAPI(ModelAPI):
|
|||||||
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
|
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
|
||||||
def patch(self, share_id, auth_user=None):
|
def patch(self, share_id, auth_user=None):
|
||||||
share = Share.query.filter_by(share_id=share_id).first()
|
share = Share.query.filter_by(share_id=share_id).first()
|
||||||
|
if share.locked:
|
||||||
|
return jsonify({"status": "fail", "message": "This share is locked."}), 423
|
||||||
return super().patch(share)
|
return super().patch(share)
|
||||||
|
|
||||||
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
|
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
|
||||||
def put(self, share_id, auth_user=None):
|
def put(self, share_id, auth_user=None):
|
||||||
share = Share.query.filter_by(share_id=share_id).first()
|
share = Share.query.filter_by(share_id=share_id).first()
|
||||||
|
if share.locked:
|
||||||
|
return jsonify({"status": "fail", "message": "This share is locked."}), 423
|
||||||
return super().put(share)
|
return super().put(share)
|
||||||
|
|
||||||
@auth_required(required_permissions=(Permissions.DELETE,), allow_anonymous=True)
|
@auth_required(required_permissions=(Permissions.DELETE,), allow_anonymous=True)
|
||||||
@ -32,6 +36,8 @@ class FilesMetadataAPI(ModelAPI):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return jsonify(dict(status="fail", message=f"Invalid ID: '{share_id}'."))
|
return jsonify(dict(status="fail", message=f"Invalid ID: '{share_id}'."))
|
||||||
share = Share.query.filter_by(share_id=share_id).first()
|
share = Share.query.filter_by(share_id=share_id).first()
|
||||||
|
if share.locked:
|
||||||
|
return jsonify({"status": "fail", "message": "This share is locked."}), 423
|
||||||
return super().delete(share)
|
return super().delete(share)
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +123,11 @@ class FileContentAPI(ModelAPI):
|
|||||||
jsonify({"status": "fail", "message": "This share does not exist."})
|
jsonify({"status": "fail", "message": "This share does not exist."})
|
||||||
), 404
|
), 404
|
||||||
|
|
||||||
|
if share.locked:
|
||||||
|
return (
|
||||||
|
jsonify({"status": "fail", "message": "This share is locked."})
|
||||||
|
), 423
|
||||||
|
|
||||||
if auth_user != share.owner:
|
if auth_user != share.owner:
|
||||||
return (
|
return (
|
||||||
jsonify(
|
jsonify(
|
||||||
@ -182,3 +193,47 @@ files_blueprint.add_url_rule(
|
|||||||
view_func=FileContentAPI.as_view("files_content_api"),
|
view_func=FileContentAPI.as_view("files_content_api"),
|
||||||
methods=["POST", "PUT", "GET"],
|
methods=["POST", "PUT", "GET"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileLockAPI(ModelAPI):
|
||||||
|
@auth_required(required_permissions=(Permissions.LOCK,), allow_anonymous=True)
|
||||||
|
def post(self, share_id, auth_user=None):
|
||||||
|
share = Share.query.filter_by(share_id=uuid.UUID(share_id)).first()
|
||||||
|
if not share:
|
||||||
|
return (
|
||||||
|
jsonify({"status": "fail", "message": "This share does not exist."})
|
||||||
|
), 404
|
||||||
|
|
||||||
|
share.locked = True
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"status": "success", "message": "Share has been locked."})
|
||||||
|
|
||||||
|
|
||||||
|
files_blueprint.add_url_rule(
|
||||||
|
"/files/<share_id>/lock",
|
||||||
|
view_func=FileLockAPI.as_view("files_lock_api"),
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileUnlockAPI(ModelAPI):
|
||||||
|
@auth_required(required_permissions=(Permissions.LOCK,), allow_anonymous=True)
|
||||||
|
def post(self, share_id, auth_user=None):
|
||||||
|
share = Share.query.filter_by(share_id=uuid.UUID(share_id)).first()
|
||||||
|
if not share:
|
||||||
|
return (
|
||||||
|
jsonify({"status": "fail", "message": "This share does not exist."})
|
||||||
|
), 404
|
||||||
|
|
||||||
|
share.locked = False
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"status": "success", "message": "Share has been unlocked."})
|
||||||
|
|
||||||
|
|
||||||
|
files_blueprint.add_url_rule(
|
||||||
|
"/files/<share_id>/unlock",
|
||||||
|
view_func=FileUnlockAPI.as_view("files_unlock_api"),
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@ -220,6 +220,8 @@ class Share(db.Model):
|
|||||||
initialized : bool
|
initialized : bool
|
||||||
Since only the metadata is uploaded first, this switches to True when
|
Since only the metadata is uploaded first, this switches to True when
|
||||||
the real data is uploaded.
|
the real data is uploaded.
|
||||||
|
locked : bool
|
||||||
|
Locks modification and deletion of this share.
|
||||||
create_date : DateTime
|
create_date : DateTime
|
||||||
Time the share was created (not initialized.)
|
Time the share was created (not initialized.)
|
||||||
file_name : str
|
file_name : str
|
||||||
@ -242,12 +244,13 @@ class Share(db.Model):
|
|||||||
owner = db.relationship("User", backref=db.backref("owner"))
|
owner = db.relationship("User", backref=db.backref("owner"))
|
||||||
|
|
||||||
initialized = db.Column(db.Boolean, nullable=False, default=False)
|
initialized = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
locked = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
create_date = db.Column(db.DateTime, nullable=False)
|
create_date = db.Column(db.DateTime, nullable=False)
|
||||||
|
|
||||||
file_name = db.Column(db.String, nullable=False)
|
file_name = db.Column(db.String, nullable=False)
|
||||||
|
|
||||||
def __init__(self, owner_name=None, file_name=None):
|
def __init__(self, owner_name=None, file_name=None, locked=False):
|
||||||
self.owner = User.query.filter_by(username=owner_name).first()
|
self.owner = User.query.filter_by(username=owner_name).first()
|
||||||
if self.owner:
|
if self.owner:
|
||||||
self.owner_name = self.owner.username
|
self.owner_name = self.owner.username
|
||||||
@ -259,6 +262,8 @@ class Share(db.Model):
|
|||||||
else:
|
else:
|
||||||
self.file_name = str(self.share_id)
|
self.file_name = str(self.share_id)
|
||||||
|
|
||||||
|
self.locked = locked
|
||||||
|
|
||||||
def get_schema(self):
|
def get_schema(self):
|
||||||
class Schema(ma.SQLAlchemySchema):
|
class Schema(ma.SQLAlchemySchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -268,6 +273,7 @@ class Share(db.Model):
|
|||||||
owner_name = ma.auto_field()
|
owner_name = ma.auto_field()
|
||||||
file_name = ma.auto_field()
|
file_name = ma.auto_field()
|
||||||
initialized = ma.auto_field(dump_only=True)
|
initialized = ma.auto_field(dump_only=True)
|
||||||
|
locked = ma.auto_field(dump_only=True)
|
||||||
|
|
||||||
return Schema()
|
return Schema()
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ def users(client):
|
|||||||
Permissions.DELETE,
|
Permissions.DELETE,
|
||||||
Permissions.MODIFY,
|
Permissions.MODIFY,
|
||||||
Permissions.LIST,
|
Permissions.LIST,
|
||||||
|
Permissions.LOCK,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dave=dict(
|
dave=dict(
|
||||||
@ -110,6 +111,16 @@ def users(client):
|
|||||||
Permissions.READ,
|
Permissions.READ,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
no_lock_user=dict(
|
||||||
|
password="password",
|
||||||
|
permissions=Bitmask(
|
||||||
|
Permissions.CREATE,
|
||||||
|
Permissions.MODIFY,
|
||||||
|
Permissions.DELETE,
|
||||||
|
Permissions.ADMIN,
|
||||||
|
Permissions.READ,
|
||||||
|
),
|
||||||
|
),
|
||||||
administrator=dict(password="4321", permissions=Bitmask(Permissions.ADMIN)),
|
administrator=dict(password="4321", permissions=Bitmask(Permissions.ADMIN)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -193,7 +193,6 @@ class TestSuite:
|
|||||||
)
|
)
|
||||||
assert resp.status_code == 403
|
assert resp.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
# test not allowing re-upload
|
# test not allowing re-upload
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
url + "/content",
|
url + "/content",
|
||||||
@ -210,3 +209,84 @@ class TestSuite:
|
|||||||
assert resp.status_code == 403
|
assert resp.status_code == 403
|
||||||
resp = client.get(url + "/content", headers=auth("no_read_user"))
|
resp = client.get(url + "/content", headers=auth("no_read_user"))
|
||||||
assert resp.status_code == 403
|
assert resp.status_code == 403
|
||||||
|
|
||||||
|
def test_locking(self, client, users, auth, rand):
|
||||||
|
# upload share
|
||||||
|
resp = client.post(
|
||||||
|
"/files", headers=auth("jeff"), json={"file_name": "content.bin"}
|
||||||
|
)
|
||||||
|
data = resp.get_json()
|
||||||
|
url = data.get("url")
|
||||||
|
upload_data = rand.randbytes(4000)
|
||||||
|
resp = client.post(
|
||||||
|
url + "/content",
|
||||||
|
headers=auth("jeff"),
|
||||||
|
data={
|
||||||
|
"upload": FileStorage(stream=BytesIO(upload_data), filename="upload")
|
||||||
|
},
|
||||||
|
content_type="multipart/form-data",
|
||||||
|
)
|
||||||
|
assert resp.status_code == 201
|
||||||
|
|
||||||
|
# lock share
|
||||||
|
resp = client.post(
|
||||||
|
url + "/lock",
|
||||||
|
headers=auth("jeff"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
# attempt to modify share
|
||||||
|
resp = client.put(
|
||||||
|
url + "/content",
|
||||||
|
headers=auth("jeff"),
|
||||||
|
data={
|
||||||
|
"upload": FileStorage(stream=BytesIO(upload_data), filename="upload")
|
||||||
|
},
|
||||||
|
content_type="multipart/form-data",
|
||||||
|
)
|
||||||
|
assert resp.status_code == 423
|
||||||
|
|
||||||
|
# attempt to delete share
|
||||||
|
resp = client.delete(
|
||||||
|
url,
|
||||||
|
headers=auth("jeff"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 423
|
||||||
|
|
||||||
|
|
||||||
|
# unlock share
|
||||||
|
resp = client.post(
|
||||||
|
url + "/unlock",
|
||||||
|
headers=auth("jeff"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
# attempt to modify share
|
||||||
|
resp = client.put(
|
||||||
|
url + "/content",
|
||||||
|
headers=auth("jeff"),
|
||||||
|
data={
|
||||||
|
"upload": FileStorage(stream=BytesIO(upload_data), filename="upload")
|
||||||
|
},
|
||||||
|
content_type="multipart/form-data",
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
# attempt to delete share
|
||||||
|
resp = client.delete(
|
||||||
|
url,
|
||||||
|
headers=auth("jeff"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
# attempt to lock/unlock without perms
|
||||||
|
resp = client.post(
|
||||||
|
url + "/lock",
|
||||||
|
headers=auth("no_lock_user"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 403
|
||||||
|
resp = client.post(
|
||||||
|
url + "/unlock",
|
||||||
|
headers=auth("no_lock_user"),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 403
|
||||||
|
Loading…
Reference in New Issue
Block a user