API & Client - max sizes and max number of files must be greater than 0 - #71

This commit is contained in:
Sam
2021-02-20 16:59:31 +01:00
parent c4e579dd25
commit fbc40385a0
18 changed files with 203 additions and 26 deletions

View File

@ -12,7 +12,7 @@ from fittrackee.responses import (
from fittrackee.users.decorators import authenticate_as_admin
from .models import AppConfig
from .utils import update_app_config_from_database
from .utils import update_app_config_from_database, verify_app_config
config_blueprint = Blueprint('config', __name__)
@ -96,11 +96,11 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
:param integer auth_user_id: authenticate user id (from JSON Web Token)
:<json integrer gpx_limit_import: max number of files in zip archive
:<json integer gpx_limit_import: max number of files in zip archive
:<json boolean is_registration_enabled: is registration enabled ?
:<json integrer max_single_file_size: max size of a single file
:<json integrer max_zip_file_size: max size of a zip archive
:<json integrer max_users: max users allowed to register on instance
:<json integer max_single_file_size: max size of a single file
:<json integer max_zip_file_size: max size of a zip archive
:<json integer max_users: max users allowed to register on instance
:reqheader Authorization: OAuth 2.0 Bearer Token
@ -117,6 +117,10 @@ def update_application_config(auth_user_id: int) -> Union[Dict, HttpResponse]:
if not config_data:
return InvalidPayloadErrorResponse()
ret = verify_app_config(config_data)
if ret:
return InvalidPayloadErrorResponse(message=ret)
try:
config = AppConfig.query.one()
if 'gpx_limit_import' in config_data:

View File

@ -1,5 +1,5 @@
import os
from typing import Tuple
from typing import Dict, List, Tuple
from flask import Flask
@ -49,3 +49,30 @@ def update_app_config_from_database(
current_app.config[
'is_registration_enabled'
] = db_config.is_registration_enabled
def verify_app_config(config_data: Dict) -> List:
"""
Verify if application config is valid.
If not, it returns not empty string
"""
ret = []
if (
'gpx_limit_import' in config_data
and config_data['gpx_limit_import'] <= 0
):
ret.append('Max. files in a zip archive must be greater than 0')
if (
'max_single_file_size' in config_data
and config_data['max_single_file_size'] <= 0
):
ret.append('Max. size of uploaded files must be greater than 0')
if (
'max_zip_file_size' in config_data
and config_data['max_zip_file_size'] <= 0
):
ret.append('Max. size of zip archive must be greater than 0')
return ret

View File

@ -1,8 +1,8 @@
{
"files": {
"main.css": "/static/css/main.376b8924.chunk.css",
"main.js": "/static/js/main.7c5c861a.chunk.js",
"main.js.map": "/static/js/main.7c5c861a.chunk.js.map",
"main.js": "/static/js/main.4387b246.chunk.js",
"main.js.map": "/static/js/main.4387b246.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.1240af94.js",
"runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
"static/js/2.301144a0.chunk.js": "/static/js/2.301144a0.chunk.js",
@ -19,6 +19,6 @@
"static/js/runtime-main.1240af94.js",
"static/js/2.301144a0.chunk.js",
"static/css/main.376b8924.chunk.css",
"static/js/main.7c5c861a.chunk.js"
"static/js/main.4387b246.chunk.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.7c5c861a.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.376b8924.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.301144a0.chunk.js"></script><script src="/static/js/main.4387b246.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,10 @@ class HttpResponse(Response):
class GenericErrorResponse(HttpResponse):
def __init__(
self, status_code: int, message: str, status: Optional[str] = None
self,
status_code: int,
message: Union[str, List],
status: Optional[str] = None,
) -> None:
response = {
'status': 'error' if status is None else status,
@ -46,7 +49,9 @@ class GenericErrorResponse(HttpResponse):
class InvalidPayloadErrorResponse(GenericErrorResponse):
def __init__(
self, message: Optional[str] = None, status: Optional[str] = None
self,
message: Optional[Union[str, List]] = None,
status: Optional[str] = None,
) -> None:
message = 'Invalid payload.' if message is None else message
super().__init__(status_code=400, message=message, status=status)

View File

@ -276,3 +276,105 @@ class TestUpdateConfig:
'Max. size of zip archive must be equal or greater than max. size '
'of uploaded files'
) in data['message']
def test_it_raises_error_if_archive_max_size_equals_0(
self, app_with_max_single_file_size: Flask, user_1_admin: User
) -> None:
client = app_with_max_single_file_size.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(
dict(email='admin@example.com', password='12345678')
),
content_type='application/json',
)
response = client.patch(
'/api/config',
content_type='application/json',
data=json.dumps(
dict(
max_zip_file_size=0,
)
),
headers=dict(
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
)
data = json.loads(response.data.decode())
assert response.status_code == 400
assert 'error' in data['status']
assert (
'Max. size of zip archive must be greater than 0'
in data['message']
)
def test_it_raises_error_if_files_max_size_equals_0(
self, app: Flask, user_1_admin: User
) -> None:
client = app.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(
dict(email='admin@example.com', password='12345678')
),
content_type='application/json',
)
response = client.patch(
'/api/config',
content_type='application/json',
data=json.dumps(
dict(
max_single_file_size=0,
)
),
headers=dict(
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
)
data = json.loads(response.data.decode())
assert response.status_code == 400
assert 'error' in data['status']
assert (
'Max. size of uploaded files must be greater than 0'
in data['message']
)
def test_it_raises_error_if_gpx_limit_import_equals_0(
self, app: Flask, user_1_admin: User
) -> None:
client = app.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(
dict(email='admin@example.com', password='12345678')
),
content_type='application/json',
)
response = client.patch(
'/api/config',
content_type='application/json',
data=json.dumps(
dict(
gpx_limit_import=0,
)
),
headers=dict(
Authorization='Bearer '
+ json.loads(resp_login.data.decode())['auth_token']
),
)
data = json.loads(response.data.decode())
assert response.status_code == 400
assert 'error' in data['status']
assert (
'Max. files in a zip archive must be greater than 0'
in data['message']
)

View File

@ -11,11 +11,16 @@ from fittrackee.application.utils import update_app_config_from_database
def get_app_config(
with_config: Optional[bool] = False,
max_workouts: Optional[int] = None,
max_single_file_size: Optional[int] = None,
) -> Optional[AppConfig]:
if with_config:
config = AppConfig()
config.gpx_limit_import = 10 if max_workouts is None else max_workouts
config.max_single_file_size = 1 * 1024 * 1024
config.max_single_file_size = (
1 * 1024 * 1024
if max_single_file_size is None
else max_single_file_size
)
config.max_zip_file_size = 1 * 1024 * 1024 * 10
config.max_users = 100
db.session.add(config)
@ -27,6 +32,7 @@ def get_app_config(
def get_app(
with_config: Optional[bool] = False,
max_workouts: Optional[int] = None,
max_single_file_size: Optional[int] = None,
) -> Generator:
app = create_app()
with app.app_context():
@ -64,6 +70,14 @@ def app_with_max_workouts(monkeypatch: pytest.MonkeyPatch) -> Generator:
yield from get_app(with_config=True, max_workouts=2)
@pytest.fixture
def app_with_max_single_file_size(
monkeypatch: pytest.MonkeyPatch,
) -> Generator:
monkeypatch.setenv('EMAIL_URL', 'smtp://none:none@0.0.0.0:1025')
yield from get_app(with_config=True, max_single_file_size=0)
@pytest.fixture
def app_no_config() -> Generator:
yield from get_app(with_config=False)