diff --git a/nest_backend/api_schemas.py b/nest_backend/api_schemas.py index 8b39f0e..dd70f7a 100644 --- a/nest_backend/api_schemas.py +++ b/nest_backend/api_schemas.py @@ -21,6 +21,7 @@ class InputLoginSchema(Schema): class ErrorSchema(Schema): result = fields.String(description="Contains a string that informs if the procedure was successful.") msg = fields.String(description="Contains a description of the error.") + code = fields.String(description="Error code") class SuccesSchema(Schema): diff --git a/nest_backend/errors.py b/nest_backend/errors.py new file mode 100644 index 0000000..6fd48f7 --- /dev/null +++ b/nest_backend/errors.py @@ -0,0 +1,27 @@ +# User errors +USER_NOT_FOUND = "errorUserNotFound" # Could not find user +USER_WRONG_CREDENTIALS = "errorUserWrongCredentials" # User has given incorrect pair of credentials +USER_NOT_AUTHORIZED = "errorUserNotAuthorized" # User is not authorized to proceed +USER_NOT_ADMIN = "errorUserNotAdmin" # User is not an admin +USER_PREVENT_SEPPUKU = "errorUserPreventSeppuku" # User cannot delete himself +USER_DELETION_ERROR = "errorDeletionError" # Something is preventing the deletion of the user +# Generic +GENERIC_NOT_FOUND = "errorNotFound" # Generic 404 +GENERIC_MISSING_FIELDS = "errorMissingFields" # Generic 400 +GENERIC_ALREADY_EXISTS = "errorAlreadyExists" # Generic primary key error +GENERIC_ENUM_INVALID = "errorEnumInvalid" # The given integer is not a valid one +GENERIC_UFO = "errorUnknownError" # The classic 'the hell is this' error +GENERIC_NO_JSON = "errorNoJson" # No JSON was given +# Repository +REPOSITORY_NOT_FOUND = "errorRepositoryNotFound" # Repository not found +REPOSITORY_NOT_OWNER = "errorRepositoryNotOwner" # The user is not the repository owner +REPOSITORY_DEPENDENCY_FAILURE = "errorRepositoryDepencencyFailure" # Something is preventing the repo to go away +# Conditions +CONDITION_NOT_FOUND = "errorConditionNotFound" # Condition not found. +# Alerts +ALERT_NOT_FOUND = "errorAlertNotFound" # Alert not found +ALERT_NO_NAME = "errorAlertNoName" # Missing name entry +ALERT_NO_LIMIT = "errorAlertNoLimit" # Missing limit entry +ALERT_NO_WINDOW = "errorAlertNoWindow" # Missing window entry +ALERT_NO_EVALUATION = "errorAlertNoEvaluation" # Missing evalmode entry +ALERT_DELETION_FAILURE = "errorAlertDeletionFailure" # Error while deleting alerts diff --git a/nest_backend/gestione.py b/nest_backend/gestione.py index 90c8f00..29462b5 100644 --- a/nest_backend/gestione.py +++ b/nest_backend/gestione.py @@ -8,6 +8,7 @@ import functools from flask_jwt_extended import get_jwt_identity from flask import jsonify from re import sub +from .errors import GENERIC_UFO __all__ = ["authenticate", "identity", "gen_password", "find_user", "admin_or_403", "repository_auth", "json_request_authorizer", "json_error", @@ -84,13 +85,14 @@ def repository_auth(f): return func -def json_error(msg): +def json_error(msg, code=GENERIC_UFO): """ Returns an error in json format + :param code: the code of the error according to the spec. :param msg: the error message. :return: a json formatted string. """ - return jsonify({"result": "failure", 'msg': msg}) + return jsonify({"result": "failure", 'msg': msg, 'code':code}) def json_success(data): diff --git a/nest_backend/routes/repository/alerts/alert.py b/nest_backend/routes/repository/alerts/alert.py index 1bd67f5..50a63fd 100644 --- a/nest_backend/routes/repository/alerts/alert.py +++ b/nest_backend/routes/repository/alerts/alert.py @@ -4,6 +4,7 @@ from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin import datetime +from nest_backend.errors import * @cross_origin() @@ -113,16 +114,14 @@ def page_alert(aid): """ user = find_user(get_jwt_identity()) alert = Alert.query.filter_by(id=aid).first() - if alert.repository_id not in user.owner_of: - return json_error("The user is not authorized."), 403 if not alert: - return json_error("Could not find alert."), 404 + return json_error("Could not find alert.", ALERT_NOT_FOUND), 404 if alert.repository not in [a.repository for a in user.authorizations] + user.owner_of: - return json_error("You are not authorized to proceed."), 403 + return json_error("You are not authorized to proceed.", USER_NOT_AUTHORIZED), 403 if request.method == "GET": return json_success(alert.to_json()), 200 if alert.repository not in user.owner_of: - return json_error("You are not authorized to proceed."), 403 + return json_error("You are not authorized to proceed.", REPOSITORY_NOT_OWNER), 403 if request.method == "PATCH": if 'name' in request.json: alert.name = request.json['name'] @@ -137,11 +136,11 @@ def page_alert(aid): ext.session.delete(alert) ext.session.commit() except Exception: - return json_error("Something went wrong while deleting alert."), 500 + return json_error("Something went wrong while deleting alert.", ALERT_DELETION_FAILURE), 500 return json_success("Deletion completed."), 204 elif request.method == "PUT": if not json_request_authorizer(request.json, alert): - return json_error("Missing one or more parameters in repository json."), 400 + return json_error("Missing one or more parameters in repository json.", GENERIC_MISSING_FIELDS), 400 alert.limit = request.json['limit'] alert.name = request.json['name'] alert.window_size = request.json['window_size'] @@ -149,14 +148,14 @@ def page_alert(aid): try: alert.evaluation_mode = ConditionMode(mode) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 except Exception as e: - return json_error("Unknown error:" + str(e)), 400 + return json_error("Unknown error:" + str(e), GENERIC_UFO), 400 if request.json['conditions'] is not None: # Possibile vulnearabilità! Un utente potrebbe aggiungere conditions non del suo repo! for c in request.json['conditions']: if c['id'] not in alert.repository.conditions: - return json_error("Stop! You violated the law!"), 403 + return json_error("Stop! You violated the law!", USER_NOT_AUTHORIZED), 403 # Wow very pythonic so much wow # Obtain list of no longer needed connections to_be_deleted = [c.cid for c in alert.conditions if diff --git a/nest_backend/routes/repository/alerts/repository_alerts.py b/nest_backend/routes/repository/alerts/repository_alerts.py index ec066fe..925cd0b 100644 --- a/nest_backend/routes/repository/alerts/repository_alerts.py +++ b/nest_backend/routes/repository/alerts/repository_alerts.py @@ -3,6 +3,7 @@ from nest_backend.database import * from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin +from nest_backend.errors import * @cross_origin() @@ -69,30 +70,30 @@ def page_repository_alerts(rid): repository = Repository.query.filter_by(id=rid).first() if not repository: - return json_error("Could not find repository"), 404 + return json_error("Could not find repository", REPOSITORY_NOT_FOUND), 404 user = find_user(get_jwt_identity()) if user.email != repository.owner_id: - return json_error("You are not authorized."), 403 + return json_error("You are not authorized.", REPOSITORY_NOT_OWNER), 403 if request.method == "GET": return json_success([alert.to_json() for alert in repository.alerts]) if request.method == "POST": if 'name' not in request.json: - return json_error("Missing name."), 400 + return json_error("Missing name.", ALERT_NO_NAME), 400 if 'limit' not in request.json: - return json_error('Missing limit'), 400 + return json_error('Missing limit', ALERT_NO_LIMIT), 400 if 'window_size' not in request.json: - return json_error('Missing window size'), 400 + return json_error('Missing window size', ALERT_NO_WINDOW), 400 if (mode := request.json.get("evaluation_mode")) is not None: try: mode = ConditionMode(mode) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 except Exception as e: - return json_error("Unknown error:" + str(e)), 400 + return json_error("Unknown error:" + str(e), GENERIC_UFO), 400 else: - return json_error("Evaluation mode was not provided."), 400 + return json_error("Evaluation mode was not provided.", ALERT_NO_EVALUATION), 400 alert = Alert(name=request.json['name'], limit=request.json['limit'], window_size=request.json['window_size'], repository_id=rid, evaluation_mode=mode) @@ -102,7 +103,7 @@ def page_repository_alerts(rid): for condition in request.json['conditions']: c = Condition.query.filter_by(id=condition['id']).first() if not c: - return json_error("Could not locate condition."), 404 + return json_error("Could not locate condition.", CONDITION_NOT_FOUND), 404 conn = MadeOf(aid=alert.id, cid=c.id) ext.session.add(conn) ext.session.commit() diff --git a/nest_backend/routes/repository/conditions/condition.py b/nest_backend/routes/repository/conditions/condition.py index 132b708..c8fe3a3 100644 --- a/nest_backend/routes/repository/conditions/condition.py +++ b/nest_backend/routes/repository/conditions/condition.py @@ -3,6 +3,7 @@ from nest_backend.database import * from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin +from nest_backend.errors import * @cross_origin() @@ -106,25 +107,25 @@ def page_condition(cid): condition = Condition.query.filter_by(id=cid).first() user = find_user(get_jwt_identity()) if not condition: - return json_error("Could not find the condition."), 404 + return json_error("Could not find the condition.", CONDITION_NOT_FOUND), 404 if condition.repository not in [a.repository for a in user.authorizations] + user.owner_of and not user.isAdmin: - return json_error("You lack the authorization to proceed, pal."), 403 + return json_error("You lack the authorization to proceed, pal.", USER_NOT_AUTHORIZED), 403 if request.method == "GET": return json_success(condition.to_json()), 200 if condition.repository not in user.owner_of and not user.isAdmin: - return json_error("You lack the authorization to proceed, pal."), 403 + return json_error("You lack the authorization to proceed, pal.", USER_NOT_AUTHORIZED), 403 if request.method == "PATCH": if request.json is None: - return json_error("Missing json content."), 400 + return json_error("Missing json content.", GENERIC_NO_JSON), 400 if (type_ := request.json.get("type")) is not None: try: type_ = ConditionType(type_) condition.type = type_ except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 except Exception as e: - return json_error("Unknown error:" + str(e)), 400 + return json_error("Unknown error:" + str(e), GENERIC_UFO), 400 if content := request.json.get("content"): condition.content = content diff --git a/nest_backend/routes/repository/conditions/repository_conditions.py b/nest_backend/routes/repository/conditions/repository_conditions.py index d6cb8c2..963c23b 100644 --- a/nest_backend/routes/repository/conditions/repository_conditions.py +++ b/nest_backend/routes/repository/conditions/repository_conditions.py @@ -5,6 +5,7 @@ from nest_backend.gestione import repository_auth, json_error, json_success, Con from nest_backend.database import ext from flask_cors import cross_origin from nest_backend.gestione import hashtag_validator +from nest_backend.errors import * @cross_origin() @@ -74,34 +75,34 @@ def page_repository_conditions(rid): repository = Repository.query.filter_by(id=rid).first() if not repository: - return json_error("Could not find repository"), 404 + return json_error("Could not find repository", REPOSITORY_NOT_FOUND), 404 user = find_user(get_jwt_identity()) if user.email != repository.owner_id: - return json_error("You are not authorized."), 403 + return json_error("You are not authorized.", REPOSITORY_NOT_OWNER), 403 if request.method == "GET": try: return json_success([u.to_json() for u in repository.conditions]) except Exception as e: - return json_error("Unknown error:" + str(e)), 400 + return json_error("Unknown error:" + str(e), GENERIC_UFO), 400 if request.method == "POST": if request.json is None: - return json_error("Missing json content."), 400 + return json_error("Missing json content.", GENERIC_NO_JSON), 400 if (type_ := request.json.get("type")) is None: - return json_error("Missing `type` parameter."), 400 + return json_error("Missing `type` parameter.", GENERIC_MISSING_FIELDS), 400 try: type_ = ConditionType(type_) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 except Exception as e: return json_error("Unknown error: " + str(e)), 400 if not (content := request.json.get("content")): - return json_error("Missing `content` parameter."), 400 + return json_error("Missing `content` parameter.", GENERIC_MISSING_FIELDS), 400 if type_ == ConditionType.hashtag: content = hashtag_validator(content) condition = Condition(content=content, type=type_, repository_id=rid) diff --git a/nest_backend/routes/repository/repositories.py b/nest_backend/routes/repository/repositories.py index 796484a..57bfcc9 100644 --- a/nest_backend/routes/repository/repositories.py +++ b/nest_backend/routes/repository/repositories.py @@ -4,6 +4,7 @@ from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * import datetime from flask_cors import cross_origin +from nest_backend.errors import * @cross_origin() @@ -75,23 +76,27 @@ def page_repositories(): # Users will be tolerated if they change parameters they're not supposed to touch. We'll ignore them for now. if not request.json.get("name") or not request.json.get("conditions") or not str( request.json.get("evaluation_mode")): - return json_error("Missing arguments."), 400 + return json_error("Missing arguments.", GENERIC_MISSING_FIELDS), 400 name = request.json.get("name") try: evaluation_mode = ConditionMode(request.json['evaluation_mode']) - except: # KeyError - return json_error("Unknown `type` specified."), 400 + except KeyError: + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 repository = Repository(name=name, owner_id=user.email, is_active=False, evaluation_mode=evaluation_mode) ext.session.add(repository) ext.session.commit() - ids = [c['id'] for c in request.json['conditions'] if c['id']] + conditions = [c for c in repository.conditions if c.id not in [a['id'] for a in request.json['conditions'] if + a['id'] in [b.id for b in repository.conditions]]] + for c in conditions: + ext.session.delete(c) + ext.session.commit() # Create brand new conditions for c in request.json['conditions']: if not c['id']: try: type_ = ConditionType(c['type']) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 ext.session.add(Condition(type=type_, content=c['content'], repository_id=repository.id)) ext.session.commit() repository.is_active = True diff --git a/nest_backend/routes/repository/repository.py b/nest_backend/routes/repository/repository.py index 7746b41..56687f1 100644 --- a/nest_backend/routes/repository/repository.py +++ b/nest_backend/routes/repository/repository.py @@ -4,7 +4,7 @@ from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin import datetime - +from nest_backend.errors import * @cross_origin() @@ -156,12 +156,12 @@ def page_repository(rid): user = find_user(get_jwt_identity()) repository = Repository.query.filter_by(id=rid).first() if not repository: - return json_error("Could not find repository."), 404 + return json_error("Could not find repository.", REPOSITORY_NOT_FOUND), 404 if request.method == "GET": return json_success(repository.to_json()), 200 elif request.method == "PATCH": if repository.owner_id != user.email: - return json_error("You are not the owner of this repository."), 403 + return json_error("You are not the owner of this repository.", REPOSITORY_NOT_OWNER), 403 if 'name' in request.json: repository.name = request.json['name'] if 'close' in request.json and not repository.end and repository.is_active: @@ -173,28 +173,28 @@ def page_repository(rid): try: evaluation_mode = ConditionMode(request.json['evaluation_mode']) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 repository.evaluation_mode = evaluation_mode ext.session.commit() return json_success(repository.to_json()), 204 elif request.method == "DELETE": if repository.owner_id != user.email and not user.isAdmin: - return json_error("You are not the owner of this repository."), 403 + return json_error("You are not the owner of this repository.", REPOSITORY_NOT_OWNER), 403 try: ext.session.delete(repository) ext.session.commit() except Exception as e: ext.session.rollback() - return json_error("Cant delete repository because of dependencies."), 500 + return json_error("Cant delete repository because of dependencies.", REPOSITORY_DEPENDENCY_FAILURE), 500 return json_success("Success"), 204 elif request.method == "PUT": if not json_request_authorizer(request.json, repository): - return json_error("Missing one or more parameters in repository json."), 400 + return json_error("Missing one or more parameters in repository json.", GENERIC_MISSING_FIELDS), 400 # Users will be tolerated if they change parameters they're not supposed to touch. We'll ignore them for now. try: evaluation_mode = ConditionMode(request.json['evaluation_mode']) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 repository.evaluation_mode = evaluation_mode repository.name = request.json['name'] repository.is_active = request.json['is_active'] @@ -210,7 +210,7 @@ def page_repository(rid): try: type_ = ConditionType(c['type']) except KeyError: - return json_error("Unknown `type` specified."), 400 + return json_error("Unknown `type` specified.", GENERIC_ENUM_INVALID), 400 content = c['content'] if type_ == ConditionType.hashtag: content = hashtag_validator(content) diff --git a/nest_backend/routes/repository/tweets/repository_tweets.py b/nest_backend/routes/repository/tweets/repository_tweets.py index aa60f86..beb4e71 100644 --- a/nest_backend/routes/repository/tweets/repository_tweets.py +++ b/nest_backend/routes/repository/tweets/repository_tweets.py @@ -5,6 +5,7 @@ from nest_backend.gestione import repository_auth, json_error, json_success, Con from nest_backend.database import ext from flask_cors import cross_origin from nest_backend.gestione import hashtag_validator +from nest_backend.errors import * @cross_origin() @@ -44,11 +45,11 @@ def page_repository_tweets(rid): repository = Repository.query.filter_by(id=rid).first() if not repository: - return json_error("Could not find repository"), 404 + return json_error("Could not find repository", REPOSITORY_NOT_FOUND), 404 user = find_user(get_jwt_identity()) - if user.email != repository.owner_id: - return json_error("You are not authorized."), 403 + if user.email != repository.owner_id and user.email not in [a.email for a in Repository.authorizations]: + return json_error("You are not authorized.", USER_NOT_AUTHORIZED), 403 if request.method == "GET": return json_success([t.tweet.to_json() for t in repository.tweets]) diff --git a/nest_backend/routes/users/login.py b/nest_backend/routes/users/login.py index 78e4ee4..0178c44 100644 --- a/nest_backend/routes/users/login.py +++ b/nest_backend/routes/users/login.py @@ -4,6 +4,7 @@ from nest_backend.gestione import * from flask_jwt_extended import create_access_token from flask_cors import cross_origin from datetime import timedelta, datetime +from nest_backend.errors import * @cross_origin() @@ -42,4 +43,4 @@ def page_login(): access_token = create_access_token(identity=email, expires_delta=delta) user = find_user(email) return json_success({"access_token": access_token, 'user': user.to_json(), "expiration": expiration}), 201 - return json_error("Bad username or password."), 401 + return json_error("Bad username or password.", USER_WRONG_CREDENTIALS), 401 diff --git a/nest_backend/routes/users/user.py b/nest_backend/routes/users/user.py index 92900fa..9251bb7 100644 --- a/nest_backend/routes/users/user.py +++ b/nest_backend/routes/users/user.py @@ -3,6 +3,7 @@ from nest_backend.database import * from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin +from nest_backend.errors import * @cross_origin() @@ -117,26 +118,26 @@ def page_user(email): user = find_user(get_jwt_identity()) target = find_user(email) if not target: - return json_error("Could not locate the user."), 404 + return json_error("Could not locate the user.", USER_NOT_FOUND), 404 if request.method == "GET": if not email == user.email and not user.isAdmin: - return json_error("Thou art not authorized."), 403 + return json_error("Thou art not authorized.", USER_NOT_AUTHORIZED), 403 return json_success(target.to_json()) elif request.method == "DELETE": if not user.isAdmin: - return json_error("User is not admin."), 403 + return json_error("User is not admin.", USER_NOT_ADMIN), 403 if user == target: - return json_error("The user cant delete himself. Its a sin."), 406 + return json_error("The user cant delete himself. Its a sin.", USER_PREVENT_SEPPUKU), 406 ext.session.delete(target) try: ext.session.commit() except Exception: ext.session.rollback() - return json_error("Could not delete the user."), 500 + return json_error("Could not delete the user.", USER_DELETION_ERROR), 500 return json_success(""), 204 # "The user has been deleted." elif request.method == "PATCH": if not email == user.email and not user.isAdmin: - return json_error("Thou art not authorized."), 403 + return json_error("Thou art not authorized.", USER_NOT_AUTHORIZED), 403 target = find_user(email) if request.json.get("username"): target.username = request.json.get("username") diff --git a/nest_backend/routes/users/users.py b/nest_backend/routes/users/users.py index 3847453..45a1670 100644 --- a/nest_backend/routes/users/users.py +++ b/nest_backend/routes/users/users.py @@ -3,6 +3,7 @@ from nest_backend.database import * from flask_jwt_extended import jwt_required, get_jwt_identity from nest_backend.gestione import * from flask_cors import cross_origin +from nest_backend.errors import * @cross_origin() @@ -65,16 +66,16 @@ def page_users(): user = find_user(get_jwt_identity()) if request.method == "GET": if not user.isAdmin: - return json_error("User is not admin. Thou art not authorized"), 403 + return json_error("User is not admin. Thou art not authorized", USER_NOT_ADMIN), 403 users = User.query.all() return json_success([user.to_json() for user in users]), 200 if request.method == "POST": if not user.isAdmin: - return json_error("User is not admin. Thou art not authorized."), 403 + return json_error("User is not admin. Thou art not authorized.", USER_NOT_ADMIN), 403 if not request.json.get("email") or not request.json.get("password") or not request.json.get("username"): - return json_error("Missing required fields."), 400 + return json_error("Missing required fields.", GENERIC_MISSING_FIELDS), 400 if User.query.filter_by(email=request.json.get("email")).first(): - return json_error("User already exists."), 406 + return json_error("User already exists.", GENERIC_ALREADY_EXISTS), 406 new_user = User(email=request.json.get("email"), password=gen_password(request.json.get("password")), username=request.json.get("username")) ext.session.add(new_user)