Skip to content

Commit

Permalink
Merge pull request #231 from OGAWAHirofumi/add-new-rest-api
Browse files Browse the repository at this point in the history
Add delete/lock/unlock REST api
  • Loading branch information
ThomasWaldmann authored Nov 3, 2020
2 parents 1bd8c3c + ee54aaf commit 434701d
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/bepasty/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import Blueprint

from .lodgeit import LodgeitUpload
from .rest import ItemDetailView, ItemDownloadView, ItemUploadView, InfoView
from .rest import ItemDetailView, ItemDownloadView, ItemUploadView, InfoView, \
ItemDeleteView, ItemLockView, ItemUnlockView


blueprint = Blueprint('bepasty_apis', __name__, url_prefix='/apis')
Expand All @@ -11,3 +12,6 @@
blueprint.add_url_rule('/rest/items', view_func=ItemUploadView.as_view('items'))
blueprint.add_url_rule('/rest/items/<itemname:name>', view_func=ItemDetailView.as_view('items_detail'))
blueprint.add_url_rule('/rest/items/<itemname:name>/download', view_func=ItemDownloadView.as_view('items_download'))
blueprint.add_url_rule('/rest/items/<itemname:name>/delete', view_func=ItemDeleteView.as_view('items_delete'))
blueprint.add_url_rule('/rest/items/<itemname:name>/lock', view_func=ItemLockView.as_view('items_lock'))
blueprint.add_url_rule('/rest/items/<itemname:name>/unlock', view_func=ItemUnlockView.as_view('items_unlock'))
38 changes: 38 additions & 0 deletions src/bepasty/apis/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
from ..utils.permissions import CREATE, LIST, may
from ..utils.upload import Upload, background_compute_hash
from ..views.filelist import file_infos
from ..views.delete import DeleteView
from ..views.download import DownloadView
from ..views.setkv import LockView, UnlockView


# This wrappper handles exceptions in the REST api implementation.
Expand Down Expand Up @@ -239,6 +241,42 @@ def get(self, name):
return super(ItemDetailView, self).get(name)


class ItemDeleteView(DeleteView, RestBase):
def error(self, item, error):
raise Conflict(description=error)

def response(self, name):
return make_response('{}', {'Content-Type': 'application/json'})

@rest_errorhandler
def post(self, name):
return super(ItemDeleteView, self).post(name)


class ItemLockView(LockView, RestBase):
def error(self, item, error):
raise Conflict(description=error)

def response(self, name):
return make_response('{}', {'Content-Type': 'application/json'})

@rest_errorhandler
def post(self, name):
return super(ItemLockView, self).post(name)


class ItemUnlockView(UnlockView, RestBase):
def error(self, item, error):
raise Conflict(description=error)

def response(self, name):
return make_response('{}', {'Content-Type': 'application/json'})

@rest_errorhandler
def post(self, name):
return super(ItemUnlockView, self).post(name)


class InfoView(RestBase):
@rest_errorhandler
def get(self):
Expand Down
118 changes: 117 additions & 1 deletion src/bepasty/tests/test_rest_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ def download(self):
with current_app.test_request_context():
return url_for('bepasty_apis.items_download', name=self.item_id)

@property
def delete(self):
with current_app.test_request_context():
return url_for('bepasty_apis.items_delete', name=self.item_id)

@property
def lock(self):
with current_app.test_request_context():
return url_for('bepasty_apis.items_lock', name=self.item_id)

@property
def unlock(self):
with current_app.test_request_context():
return url_for('bepasty_apis.items_unlock', name=self.item_id)


def add_auth(user, password, headers=None):
headers = headers if headers is not None else {}
Expand Down Expand Up @@ -783,6 +798,90 @@ def test_download_range(client_fixture):
total_size=len(data))


def test_delete_basic(client_fixture):
app, client, faketime = client_fixture

faketime.set_time(100)

datas, metas = upload_files(client)

url = RestUrl('abcdefgh')

# delete ENOENT item
response = client.post(url.delete, headers=add_auth('user', 'admin'))
check_err_response(response, 404)

for item_id in metas.keys():
url = RestUrl(item_id)

# no permission
response = client.post(url.delete, headers=add_auth('user', 'invalid'))
check_err_response(response, 403)

# has permission
response = client.post(url.delete, headers=add_auth('user', 'full'))
check_json_response(response, {})

# should already be deleted
response = client.post(url.delete, headers=add_auth('user', 'full'))
check_err_response(response, 404)


def test_lock_basic(client_fixture):
app, client, faketime = client_fixture

faketime.set_time(100)

datas, metas = upload_files(client)

url = RestUrl('abcdefgh')

for u in (url.lock, url.unlock):
# lock/unlock ENOENT item
response = client.post(u, headers=add_auth('user', 'admin'))
check_err_response(response, 404)

for item_id in metas.keys():
url = RestUrl(item_id)

for u in (url.lock, url.unlock):
# lock/unlock, no permission (invalid user)
response = client.post(u, headers=add_auth('user', 'invalid'))
check_err_response(response, 403)

# lock/unlock, no permission (not admin)
response = client.post(u, headers=add_auth('user', 'full'))
check_err_response(response, 403)

# lock/unlock, has permission
response = client.post(u, headers=add_auth('user', 'admin'))
check_json_response(response, {})

# lock item
response = client.post(url.lock, headers=add_auth('user', 'admin'))
check_json_response(response, {})

# download locked item (should fail)
response = client.get(url.download, headers=add_auth('user', 'full'))
check_err_response(response, 403)

# download locked item with admin (should succeed)
response = client.get(url.download, headers=add_auth('user', 'admin'))
check_data_response(response, metas[item_id], datas[item_id])

# delete locked item (should fail)
response = client.post(url.delete, headers=add_auth('user', 'full'))
check_err_response(response, 403)

# delete locked item with admin (should succeed)
response = client.post(url.delete, headers=add_auth('user', 'admin'))
check_json_response(response, {})

# deleted item
response = client.post(url.delete, headers=add_auth('user', 'admin'))
check_err_response(response, 404)


def test_incomplete(client_fixture):
_, client, _ = client_fixture

Expand All @@ -806,11 +905,28 @@ def test_incomplete(client_fixture):

item_id = list(response.json.keys())[0]

# detail should error with incomplete
url = RestUrl(item_id)

# detail should error with incomplete
response = client.get(url.detail, headers=add_auth('user', 'full'))
check_err_response(response, 409)

# download should error with incomplete
response = client.get(url.download, headers=add_auth('user', 'full'))
check_err_response(response, 409)

# lock should error with incomplete
response = client.post(url.lock, headers=add_auth('user', 'admin'))
check_err_response(response, 409)

# unlock should error with incomplete
response = client.post(url.unlock, headers=add_auth('user', 'admin'))
check_err_response(response, 409)

# delete should error with incomplete
response = client.post(url.delete, headers=add_auth('user', 'full'))
check_err_response(response, 409)

# delete by admin with incomplete should succeed
response = client.post(url.delete, headers=add_auth('user', 'admin'))
check_json_response(response, {})
10 changes: 8 additions & 2 deletions src/bepasty/views/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@


class DeleteView(MethodView):
def error(self, item, error):
return render_template('error.html', heading=item.meta[FILENAME], body=error), 409

def response(self, name):
return redirect_next_referrer('bepasty.index')

def post(self, name):
if not may(DELETE):
raise Forbidden()
try:
with current_app.storage.open(name) as item:
if not item.meta[COMPLETE] and not may(ADMIN):
error = 'Upload incomplete. Try again later.'
return render_template('error.html', heading=item.meta[FILENAME], body=error), 409
self.error(item, error)

if item.meta[LOCKED] and not may(ADMIN):
raise Forbidden()
Expand All @@ -29,4 +35,4 @@ def post(self, name):
raise NotFound()
raise

return redirect_next_referrer('bepasty.index')
return self.response(name)
10 changes: 8 additions & 2 deletions src/bepasty/views/setkv.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class SetKeyValueView(MethodView):
KEY = None
NEXT_VALUE = None

def error(self, item, error):
return render_template('error.html', heading=item.meta[FILENAME], body=error), 409

def response(self, name):
return redirect_next_referrer('bepasty.display', name=name)

def post(self, name):
if self.REQUIRED_PERMISSION is not None and not may(self.REQUIRED_PERMISSION):
raise Forbidden()
Expand All @@ -31,9 +37,9 @@ def post(self, name):
else:
error = None
if error:
return render_template('error.html', heading=item.meta[FILENAME], body=error), 409
return self.error(item, error)
item.meta[self.KEY] = self.NEXT_VALUE
return redirect_next_referrer('bepasty.display', name=name)
return self.response(name)

except (OSError, IOError) as e:
if e.errno == errno.ENOENT:
Expand Down

0 comments on commit 434701d

Please sign in to comment.