Compare commits

..

3 Commits

Author SHA1 Message Date
3d93e489c4
docs/files.rst: added note about null ownership 2023-05-22 12:16:45 -04:00
14734446ba
/files/<uuid>: fix ownership transfers 2023-05-21 22:18:45 -04:00
bc2c14e52f
/files/<uuid>: fix shares permission issue
users can no longer change the metadata on shares they do not own
2023-05-21 22:01:02 -04:00
4 changed files with 104 additions and 1 deletions

View File

@ -23,6 +23,8 @@ Currently, server settings are represented by the following object:
"default_permissions": ["PERMISSION1", "PERMISSION2"]
}
.. _admin_anon_perms:
Anonymous permissions
^^^^^^^^^^^^^^^^^^^^^

View File

@ -64,6 +64,11 @@ In JSON, a file share has the following properties::
- Read-only
- UUID that uniquely identifies this share.
.. note::
Share ownership can be changed by changing ``owner_name``.
Do note that setting it to ``null`` is equivalent to :ref:`unauthenticated users<admin_anon_perms>` owning the share.
.. _files_metadata_api:
Metadata API

View File

@ -2,7 +2,7 @@ import uuid
import io
from flask import Blueprint, request, jsonify, send_file, make_response
from flask.views import MethodView
from sachet.server.models import Share, Permissions, Upload, Chunk
from sachet.server.models import Share, Permissions, Upload, Chunk, User
from sachet.server.views_common import ModelAPI, ModelListAPI, auth_required
from sachet.server import storage, db
@ -18,6 +18,28 @@ class FilesMetadataAPI(ModelAPI):
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
def patch(self, share_id, auth_user=None):
share = Share.query.filter_by(share_id=share_id).first()
if auth_user != share.owner:
return (
jsonify(
{
"status": "fail",
"message": "Share must be modified by its owner.",
}
),
403,
)
owner_name = request.get_json().get("owner_name")
if owner_name is not None:
if User.query.filter_by(username=owner_name).first() is None:
return (
jsonify(
{
"status": "fail",
"message": f"Invalid value for `owner_name`: {owner_name}",
}
),
400,
)
if share.locked:
return jsonify({"status": "fail", "message": "This share is locked."}), 423
return super().patch(share)
@ -25,6 +47,28 @@ class FilesMetadataAPI(ModelAPI):
@auth_required(required_permissions=(Permissions.MODIFY,), allow_anonymous=True)
def put(self, share_id, auth_user=None):
share = Share.query.filter_by(share_id=share_id).first()
if auth_user != share.owner:
return (
jsonify(
{
"status": "fail",
"message": "Share must be modified by its owner.",
}
),
403,
)
owner_name = request.get_json().get("owner_name")
if owner_name is not None:
if User.query.filter_by(username=owner_name).first() is None:
return (
jsonify(
{
"status": "fail",
"message": f"Invalid value for `owner_name`: {owner_name}",
}
),
400,
)
if share.locked:
return jsonify({"status": "fail", "message": "This share is locked."}), 423
return super().put(share)

View File

@ -105,6 +105,42 @@ class TestSuite:
assert resp.data == new_data
assert "filename=new_bin.bin" in resp.headers["Content-Disposition"].split("; ")
def test_transfer(self, client, users, auth):
# create share
resp = client.post(
"/files", headers=auth("jeff"), json={"file_name": "content.bin"}
)
data = resp.get_json()
url = data.get("url")
# transfer ownership over to dave
resp = client.patch(url, headers=auth("jeff"), json={"owner_name": "dave"})
assert resp.status_code == 200
# ensure the transfer worked
resp = client.patch(
url, headers=auth("jeff"), json={"file_name": "jeff's file"}
)
assert resp.status_code == 403
resp = client.patch(
url, headers=auth("dave"), json={"file_name": "dave's file"}
)
assert resp.status_code == 200
# transfer ownership back to jeff
resp = client.patch(url, headers=auth("dave"), json={"owner_name": "jeff"})
assert resp.status_code == 200
# ensure the transfer worked
resp = client.patch(
url, headers=auth("dave"), json={"file_name": "dave's epic file"}
)
assert resp.status_code == 403
resp = client.patch(
url, headers=auth("jeff"), json={"file_name": "jeff's file"}
)
assert resp.status_code == 200
def test_invalid(self, client, users, auth, rand, upload):
"""Test invalid requests."""
@ -182,6 +218,22 @@ class TestSuite:
method=client.put,
)
assert resp.status_code == 403
resp = client.patch(
url, headers=auth("dave"), json=dict(file_name="epic_new_filename.bin")
)
assert resp.status_code == 403
resp = client.put(
url,
headers=auth("dave"),
json=dict(file_name="epic_new_filename.bin", owner_name="dave"),
)
assert resp.status_code == 403
# test assigning a file to a non-existent user
resp = client.patch(
url, headers=auth("jeff"), json=dict(owner_name="non_existent_user")
)
assert resp.status_code == 400
# test not allowing re-upload
resp = upload(