diff --git a/main.py b/main.py index a9f19e9..e9eb50d 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,13 @@ from base64 import b64encode -from flask import Flask, request, jsonify +from flask import Flask, g, request, jsonify +from functools import wraps import json import os import tempfile from werkzeug.exceptions import (BadRequest, - NotFound) + NotFound, + Unauthorized) +from werkzeug.security import safe_str_cmp app = Flask(__name__) @@ -43,17 +46,10 @@ def read_metadata_file(filepath): return json.loads(content) -def write_metadata_file(filepath, metadata): - metadata_file = os.path.join(filepath, metadata_filename) - - with open(metadata_file, 'wb') as f: - f.write(json.dumps(metadata)) - - -def read_properties_file(filepath): - with open(os.path.join(filepath, properties_filename), 'r') as f: - content = f.read() - return json.loads(content) +def read_properties_file(file_id): + file_path = os.path.join(DATA_PATH, file_id, properties_filename) + with open(file_path, 'r') as f: + return json.load(f) def write_properties_file(filepath, properties): @@ -63,8 +59,10 @@ def write_properties_file(filepath, properties): f.write(json.dumps(properties)) -def parse_request_data(request): - return request.get_json() or request.form +@app.before_request +def parse_request_data(): + if request.method in ['POST', 'PUT']: + g.payload = request.get_json() or request.form def not_implemented(): @@ -73,20 +71,10 @@ def not_implemented(): @app.route('/api/file', methods=['POST']) def post_file(): - payload = parse_request_data(request) - - if not payload['metadata']: - app.logger.error("Parsing of request failed") - raise BadRequest() + payload = g.payload fileid, filepath = create_new_file(DATA_PATH) - write_metadata_file(filepath, - { - 'metadata': payload['metadata'], - 'mac': payload['mac'] - }) - uploadpassword = generate_password() properties = { 'uploadpassword': uploadpassword, @@ -104,25 +92,63 @@ def post_file(): }) -def assert_file_exists(id): - if not os.path.isdir(os.path.join(DATA_PATH, id)): - raise NotFound("The requested file could not be found") +def require_file_exists(f): + @wraps(f) + def check_file_exists(*args, **kwargs): + dirname = os.path.join(DATA_PATH, kwargs['id']) + if not os.path.isdir(dirname): + raise NotFound("The requested file could not be found") + app.logger.info("Found file %s", kwargs['id']) + return f(*args, **kwargs) + return check_file_exists -@app.route('/api/file/', methods=['PUT']) -def put_file(id): - return not_implemented() +def require_uploadpassword(f): + @require_file_exists + @wraps(f) + def check_uploadpassword(*args, **kwargs): + properties = read_properties_file(kwargs['id']) + payload = g.payload + comparison = safe_str_cmp( + payload['uploadpassword'], + properties['uploadpassword']) + + if not comparison: + raise Unauthorized() + return f(*args, **kwargs) + return check_uploadpassword + + +@app.route('/api/file//metadata', methods=['PUT']) +@require_uploadpassword +def put_file_metadata(id): + payload = g.payload + + if 'metadata' not in payload: + app.logger.error("Parsing of request failed") + raise BadRequest() + + metadata = payload.copy() + del metadata['uploadpassword'] + + metadata_file = os.path.join(DATA_PATH, id, metadata_filename) + + app.logger.debug("Writing metadata to %s", metadata_file) + with open(metadata_file, 'wb') as f: + json.dump(metadata, f) + + return jsonify({}) @app.route('/api/file//exists', methods=['GET']) +@require_file_exists def get_file_exists(id): - assert_file_exists(id) return "" @app.route('/api/file//info', methods=['GET']) +@require_file_exists def get_file_info(id): - assert_file_exists(id) '''Return public information about the file''' return not_implemented() diff --git a/test_main.py b/test_main.py index e100d81..fc0a481 100644 --- a/test_main.py +++ b/test_main.py @@ -29,6 +29,15 @@ def setUp(self): def tearDown(self): shutil.rmtree(self.tmp_dir) + def setup_mockfile(self, file_id, uploadpassword='1234'): + filepath = os.path.join(self.tmp_dir, file_id) + os.makedirs(filepath) + with open(os.path.join(filepath, 'properties.json'), 'w') as f: + json.dump({ + 'uploadpassword': uploadpassword + }, f) + return filepath + def api_upload_success(self, wrap_func=lambda x: x, headers={}): payload = { 'metadata': b64encode(os.urandom(42)), @@ -56,13 +65,6 @@ def test_post_file_json(self): def test_post_file_multipart(self): self.api_upload_success() - def test_post_file_no_meta(self): - payload = { - 'mac': '1234' - } - resp = self.app.post('/api/file', data=payload) - self.assertEqual(resp.status_code, 400) - def test_get_serverinfo(self): config_location = os.path.join(files, 'config.json') @@ -72,3 +74,46 @@ def test_get_serverinfo(self): self.status_code(resp, 200) assert json.loads(resp.get_data()) == expected + + def test_put_metadata_success(self): + with patch('main.DATA_PATH', self.tmp_dir): + filepath = self.setup_mockfile('foobar') + payload = { + 'uploadpassword': '1234', + 'metadata': 'encryptedtexthere' + } + resp = self.app.put('/api/file/foobar/metadata', data=payload) + self.status_code(resp, 200) + + with open(os.path.join(filepath, 'metadata.json')) as f: + j = json.load(f) + assert 'uploadpassword' not in j + + def test_put_metadata_not_exists(self): + resp = self.app.put('/api/file/fewfsadgsg/metadata', data={}) + self.status_code(resp, 404) + + def test_put_file_no_meta(self): + with patch('main.DATA_PATH', self.tmp_dir): + filepath = self.setup_mockfile('1234') + payload = { + 'uploadpassword': '1234', + } + resp = self.app.put('/api/file/1234/metadata', data=payload) + self.assertEqual(resp.status_code, 400) + metadata_file = os.path.join(filepath, 'metadata.json') + assert not os.path.exists(metadata_file) + + def test_put_file_unauthorized(self): + with patch('main.DATA_PATH', self.tmp_dir): + filepath = self.setup_mockfile('weggkgelg') + payload = { + 'uploadpassword': '2wefegf', + 'metadata': 'encryptedtexthere' + } + + resp = self.app.put('/api/file/weggkgelg/metadata', data=payload) + self.status_code(resp, 401) + + metadata_file = os.path.join(filepath, 'metadata.json') + assert not os.path.exists(metadata_file)