From 925ff9a9fa05ffd34959179c63958ddbab9caff5 Mon Sep 17 00:00:00 2001 From: Lorenzo Balugani Date: Thu, 29 Apr 2021 21:59:05 +0200 Subject: [PATCH] Breaking changes! All the urls have been changed, and several have been regrouped. Please check the docstrings. --- code/backend/nest_backend/__main__.py | 13 +++-- code/backend/nest_backend/gestione.py | 2 +- .../routes/repository/__init__.py | 7 ++- .../routes/repository/repositories.py | 38 +++++++++++++++ .../routes/repository/repository.py | 40 ++++++++++++++++ .../repository/repository_add_condition.py | 34 -------------- .../repository/repository_conditions.py | 39 +++++++++++++++ .../routes/repository/repository_create.py | 25 ---------- .../routes/repository/repository_edit.py | 28 ----------- .../routes/repository/repository_list.py | 32 ------------- .../nest_backend/routes/users/__init__.py | 4 +- .../nest_backend/routes/users/login.py | 8 +--- .../backend/nest_backend/routes/users/user.py | 47 +++++++++++++++++++ .../nest_backend/routes/users/user_create.py | 26 ---------- .../nest_backend/routes/users/user_delete.py | 28 ----------- .../nest_backend/routes/users/users.py | 29 ++++++++++++ 16 files changed, 207 insertions(+), 193 deletions(-) create mode 100644 code/backend/nest_backend/routes/repository/repositories.py create mode 100644 code/backend/nest_backend/routes/repository/repository.py delete mode 100644 code/backend/nest_backend/routes/repository/repository_add_condition.py create mode 100644 code/backend/nest_backend/routes/repository/repository_conditions.py delete mode 100644 code/backend/nest_backend/routes/repository/repository_create.py delete mode 100644 code/backend/nest_backend/routes/repository/repository_edit.py delete mode 100644 code/backend/nest_backend/routes/repository/repository_list.py create mode 100644 code/backend/nest_backend/routes/users/user.py delete mode 100644 code/backend/nest_backend/routes/users/user_create.py delete mode 100644 code/backend/nest_backend/routes/users/user_delete.py create mode 100644 code/backend/nest_backend/routes/users/users.py diff --git a/code/backend/nest_backend/__main__.py b/code/backend/nest_backend/__main__.py index e8e181b..04f4c4e 100644 --- a/code/backend/nest_backend/__main__.py +++ b/code/backend/nest_backend/__main__.py @@ -20,13 +20,12 @@ reverse_proxy_app = werkzeug.middleware.proxy_fix.ProxyFix(app=app, x_for=1, x_p # Routes setup app.add_url_rule("/doa", view_func=page_doa, methods=["GET", "POST"]) -app.add_url_rule("/api/login", view_func=page_login, methods=["POST"]) -app.add_url_rule("/api/user/create", view_func=page_user_create, methods=["POST"]) -app.add_url_rule("/api/user/remove", view_func=page_user_delete, methods=["POST"]) -app.add_url_rule("/api/repository/list", view_func=page_repository_list, methods=["GET"]) -app.add_url_rule("/api/repository/create", view_func=page_repository_create, methods=["POST"]) -app.add_url_rule("/api/repository/edit", view_func=page_repository_edit, methods=["PUT"]) -app.add_url_rule("/api/repository/add_condition", view_func=page_repository_add_condition, methods=["POST"]) +app.add_url_rule("/api/v1/login", view_func=page_login, methods=["POST"]) +app.add_url_rule("/api/v1/users", view_func=page_users, methods=["GET", "POST"]) +app.add_url_rule("/api/v1/users/", view_func=page_user, methods=["GET", "PATCH", "DELETE"]) +app.add_url_rule("/api/v1/repositories/", view_func=page_repositories, methods=["GET", "POST"]) +app.add_url_rule("/api/v1/repositories/", view_func=page_repository, methods=["GET", "PATCH", "DELETE"]) +app.add_url_rule("/api/v1/repositories//conditions", view_func=page_repository_conditions, methods=["GET", "POST"]) app.register_error_handler(Exception, error_handler) diff --git a/code/backend/nest_backend/gestione.py b/code/backend/nest_backend/gestione.py index f998d42..6be7d5b 100644 --- a/code/backend/nest_backend/gestione.py +++ b/code/backend/nest_backend/gestione.py @@ -74,7 +74,7 @@ def repository_auth(f): repository = Repository.query.filter_by(id=repository_id) if not repository: return json_error("Cant't find the repository."), 404 - if repository.owner_id != user.email: + if repository.owner_id != user.email and user.email not in [a.email for a in repository.authorizations]: return json_error("Stop right there, criminal scum! Nobody accesses protected data under MY watch!"), 403 return f(*args, **kwargs) return func diff --git a/code/backend/nest_backend/routes/repository/__init__.py b/code/backend/nest_backend/routes/repository/__init__.py index 66d676a..ca4a42d 100644 --- a/code/backend/nest_backend/routes/repository/__init__.py +++ b/code/backend/nest_backend/routes/repository/__init__.py @@ -1,4 +1,3 @@ -from .repository_add_condition import page_repository_add_condition -from .repository_edit import page_repository_edit -from .repository_create import page_repository_create -from .repository_list import page_repository_list \ No newline at end of file +from .repository_conditions import page_repository_conditions +from .repository import page_repository +from .repositories import page_repositories \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/repositories.py b/code/backend/nest_backend/routes/repository/repositories.py new file mode 100644 index 0000000..d58edf4 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/repositories.py @@ -0,0 +1,38 @@ +from flask import render_template, abort, jsonify, request +from ...database import * +from flask_jwt_extended import jwt_required +from ...gestione import * +import datetime +from flask_cors import cross_origin + + +@cross_origin() +@jwt_required() +def page_repositories(): + """ + Repositories: + + GET: [onlyActive], [onlyDead] -> Gets the list of all the user-related repos. + + POST: name -> Creates a new repository and returns it + """ + user = find_user(get_jwt_identity()) + if request.method == "GET": + owner = Repository.query.filter_by(owner_id=user.email) + spectator = Authorization.query.filter_by(email=user.email).join(Repository) + if request.json.get("onlyActive"): + owner = owner.filter_by(isActive=True) + spectator = spectator.filter(Repository.isActive == True) + elif request.json.get("onlyDead"): + owner = owner.filter_by(isActive=False) + spectator = spectator.filter(Repository.isActive == False) + owner = owner.all() + spectator = spectator.all() + return json_success({"owner": [r.to_json() for r in owner], + "spectator": [r.repository.to_json() for r in spectator]}) + elif request.method == "POST": + name = request.json.get("name") + if not name: + return json_error("Missing one or more parameters"), 400 + repository = Repository(name=name, owner_id=user.email) + Base.session.add(repository) + Base.session.commit() + return json_success(repository.to_json()), 200 diff --git a/code/backend/nest_backend/routes/repository/repository.py b/code/backend/nest_backend/routes/repository/repository.py new file mode 100644 index 0000000..3c7bb68 --- /dev/null +++ b/code/backend/nest_backend/routes/repository/repository.py @@ -0,0 +1,40 @@ +from flask import render_template, abort, jsonify, request +from ...database import * +from flask_jwt_extended import jwt_required +from ...gestione import * +from flask_cors import cross_origin +import datetime + + +@cross_origin() +@jwt_required() +@repository_auth +def page_repository(rid): + """ + Repository : + + GET: Gets info about the specified repository. + + PATCH: [name], [close], [open] -> Updates certain aspects of the repository. + + DELETE: deletes the specified repository. + """ + user = find_user(get_jwt_identity()) + repository = Repository.query.filter_by(id=request.json['id']).first() + 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 + if 'name' in request.json: + repository.name = request.json['name'] + if 'close' in request.json and not repository.end and repository.isActive: + repository.end = datetime.datetime.now() + repository.isActive = False + if 'open' in request.json and not repository.isActive and not repository.end: + repository.isActive = True + Base.session.commit() + return json_success(repository.to_json()), 200 + elif request.method == "DELETE": + if repository.owner_id != user.email: + return json_error("You are not the owner of this repository."), 403 + Base.session.delete(repository) + Base.session.commit() + return json_success("Success"), 200 \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/repository_add_condition.py b/code/backend/nest_backend/routes/repository/repository_add_condition.py deleted file mode 100644 index 29b453c..0000000 --- a/code/backend/nest_backend/routes/repository/repository_add_condition.py +++ /dev/null @@ -1,34 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -from flask_cors import cross_origin - - -@cross_origin() -@jwt_required() -@repository_auth -def page_repository_add_condition(): - """ - API call that allows to add conditions to a repository. - :parameter id: Repository ID - :parameter type: The type of the condition. It can either be an 'hashtag', a 'location' or 'time' - :parameter content: The content of the condition (#PdS2021, Roma, 18:00) - :returns: a JSON string that tells whether or not the procedure was a success. - """ - type = request.json.get("type") - if not type or type not in dir(ConditionType): - return json_error("Could not understand the type of the condition."), 400 - content = request.json.get("content") - if not content: - return json_error("Could not find the content"), 400 - condition = Condition.query.filter(Condition.content.ilike(str(content))).filter_by(type=ConditionType.__getattr__(str(type)).value).first() - if not condition: - condition = Condition(content=content, type=ConditionType.__getattr__(str(type)).value) - Base.session.add(condition) - repository = Repository.query.filter_by(request.json.get("id")) - if Uses.query.filter_by(cid=condition.id, rid=repository.id): - return json_error("This condition is already connected to the repository."), 406 - Base.session.add(Uses(cid=condition.id, rid=repository.id)) - Base.session.commit() - return json_success("Operation done with success."), 200 \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/repository_conditions.py b/code/backend/nest_backend/routes/repository/repository_conditions.py new file mode 100644 index 0000000..8672d2e --- /dev/null +++ b/code/backend/nest_backend/routes/repository/repository_conditions.py @@ -0,0 +1,39 @@ +from flask import render_template, abort, jsonify, request +from ...database import * +from flask_jwt_extended import jwt_required +from ...gestione import * +from flask_cors import cross_origin + + +@cross_origin() +@jwt_required() +@repository_auth +def page_repository_conditions(rid): + """ + Repository/Condition: + + GET: Returns the conditions of the specified repo. + + POST: type, content -> Adds a condition and returns it. + """ + repository = Repository.query.filter_by(rid=rid).first() + user = find_user(get_jwt_identity()) + if user.email != repository.owner_id: + return json_error("You are not authorized."), 403 + if request.method == "GET": + return json_success([u.condition.to_json() for u in repository.uses]) + if request.method == "POST": + type = request.json.get("type") + if not type or type not in dir(ConditionType): + return json_error("Could not understand the type of the condition."), 400 + content = request.json.get("content") + if not content: + return json_error("Could not find the content"), 400 + condition = Condition.query.filter(Condition.content.ilike(str(content))).filter_by(type=ConditionType.__getattr__(str(type)).value).first() + if not condition: + condition = Condition(content=content, type=ConditionType.__getattr__(str(type)).value) + Base.session.add(condition) + repository = Repository.query.filter_by(request.json.get("id")) + if Uses.query.filter_by(cid=condition.id, rid=repository.id): + return json_error("This condition is already connected to the repository."), 406 + Base.session.add(Uses(cid=condition.id, rid=repository.id)) + Base.session.commit() + return json_success(condition.to_json()), 200 \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/repository_create.py b/code/backend/nest_backend/routes/repository/repository_create.py deleted file mode 100644 index 4454d57..0000000 --- a/code/backend/nest_backend/routes/repository/repository_create.py +++ /dev/null @@ -1,25 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -import datetime -from flask_cors import cross_origin - - -@cross_origin() -@jwt_required() -def page_repository_create(): - """ - API call that allows an user to create a new repository. - :parameter name: The name of the repository. - :returns: If the user is logged in and has provided the repository name, a JSON string is returned containing - the return status of the operation and the repository in json format. - """ - user = find_user(get_jwt_identity()) - name = request.json.get("name") - if not name: - return json_error("Missing one or more parameters"), 400 - repository = Repository(name=name, owner_id=user.email) - Base.session.add(repository) - Base.session.commit() - return json_success(repository.to_json()), 200 diff --git a/code/backend/nest_backend/routes/repository/repository_edit.py b/code/backend/nest_backend/routes/repository/repository_edit.py deleted file mode 100644 index 4eb3dc1..0000000 --- a/code/backend/nest_backend/routes/repository/repository_edit.py +++ /dev/null @@ -1,28 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -from flask_cors import cross_origin -import datetime - - -@cross_origin() -@jwt_required() -@repository_auth -def page_repository_edit(): - """ - This API call allows to edit a repository. - :parameter name: If present, it changes the repository name. - :parameter close: If present, it closes the repository. - :returns: A JSON formatted string that either contains an error or the updated representation of the repository. - """ - repository = Repository.query.filter_by(id=request.json['id']) - if 'name' in request.json: - repository.name = request.json['name'] - if 'close' in request.json and not repository.end and repository.isActive: - repository.end = datetime.datetime.now() - repository.isActive = False - if 'open' in request.json and not repository.isActive and not repository.end: - repository.isActive = True - Base.session.commit() - return json_success(repository.to_json()) \ No newline at end of file diff --git a/code/backend/nest_backend/routes/repository/repository_list.py b/code/backend/nest_backend/routes/repository/repository_list.py deleted file mode 100644 index d87f0c0..0000000 --- a/code/backend/nest_backend/routes/repository/repository_list.py +++ /dev/null @@ -1,32 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -from flask_cors import cross_origin - - -@cross_origin() -@jwt_required() -def page_repository_list(): - """ - API call that returns the list of repositories. - - :parameter onlyActive: if present, only active repos are provided - :parameter onlyDead: if present, only dead repos are provided - - :returns: a JSON-formatted string that contains under the "content" field the list of repositories that belong to - the user ("owner") and a list of repositories that he can spectate ("spectator"). - """ - user = find_user(get_jwt_identity()) - owner = Repository.query.filter_by(owner_id=user.email) - spectator = Authorization.query.filter_by(email=user.email).join(Repository) - if request.args.get("onlyActive"): - owner = owner.filter_by(isActive=True) - spectator = spectator.filter(Repository.isActive == True) - elif request.args.get("onlyDead"): - owner = owner.filter_by(isActive=False) - spectator = spectator.filter(Repository.isActive == False) - owner = owner.all() - spectator = spectator.all() - return json_success({"owner": [r.to_json() for r in owner], - "spectator": [r.repository.to_json() for r in spectator]}) diff --git a/code/backend/nest_backend/routes/users/__init__.py b/code/backend/nest_backend/routes/users/__init__.py index df8755e..6b5f6da 100644 --- a/code/backend/nest_backend/routes/users/__init__.py +++ b/code/backend/nest_backend/routes/users/__init__.py @@ -1,3 +1,3 @@ -from .user_create import page_user_create +from .users import page_users from .login import page_login -from .user_delete import page_user_delete \ No newline at end of file +from .user import page_user \ No newline at end of file diff --git a/code/backend/nest_backend/routes/users/login.py b/code/backend/nest_backend/routes/users/login.py index e26c227..03d28c2 100644 --- a/code/backend/nest_backend/routes/users/login.py +++ b/code/backend/nest_backend/routes/users/login.py @@ -9,12 +9,8 @@ from datetime import timedelta, datetime @cross_origin() def page_login(): """ - The API call that allows to log-in. It requires: - :parameter email: The user's email - :parameter password: The users's password - :returns: Json-formatted data. If the login is successful, it will contain the access_token and the users data. - - The access_token must be included in the Authorization header, using the format Bearer . + Login: + + POST: email, password -> Sends a response containing the JWT token """ email = request.json.get("email", None) password = request.json.get("password", None) diff --git a/code/backend/nest_backend/routes/users/user.py b/code/backend/nest_backend/routes/users/user.py new file mode 100644 index 0000000..0f91cb4 --- /dev/null +++ b/code/backend/nest_backend/routes/users/user.py @@ -0,0 +1,47 @@ +from flask import render_template, abort, jsonify, request +from ...database import * +from flask_jwt_extended import jwt_required +from ...gestione import * +from flask_cors import cross_origin + + +@cross_origin() +@jwt_required() +def page_user(email): + """ + User : + + GET: gets info about the specified user. + + PATCH: password, username -> Updates data about the user, returns the updated user. + + DELETE: deletes the specified user. + """ + user = find_user(get_jwt_identity()) + if request.method == "GET": + if not email == user.email and not user.isAdmin: + return json_error("Thou art not authorized."), 403 + target = find_user(email).to_json() + if not target: + json_error("Could not locate the user."), 404 + return json_success(target.to_json()) + elif request.method == "DELETE": + if not user.isAdmin: + return json_error("User is not admin."), 403 + deluser = request.json.get('email') + target = find_user(deluser) + if not target: + return json_error("User not found."), 404 + if user == target: + return json_error("The user cant delete himself. Its a sin."), 406 + Base.session.delete(target) + Base.session.commit() + return json_success("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 + target = find_user(email) + if not target: + json_error("Could not locate the user."), 404 + if request.json.get("username"): + target.username = request.json.get("username") + if request.json.get("password"): + target.password = gen_password(request.json.get("password")) + Base.session.commit() diff --git a/code/backend/nest_backend/routes/users/user_create.py b/code/backend/nest_backend/routes/users/user_create.py deleted file mode 100644 index c93531c..0000000 --- a/code/backend/nest_backend/routes/users/user_create.py +++ /dev/null @@ -1,26 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -from flask_cors import cross_origin - - -@cross_origin() -@jwt_required() -def page_user_create(): - """ - The API call that allows to create new users. It requires: - :parameter email: The user's email - :parameter password: The users's password - :parameter username: The users's username - :return: Json-formatted data. If something goes wrong, it returns a - {'result':'failure', 'content':'something blew up'}, else it returns {'result':'success', 'content':newUser.to_json(). - """ - user = find_user(get_jwt_identity()) - if not user.isAdmin: - return json_error("User is not admin. Thou are not authorized."), 403 - new_user = User(email=request.json.get("email"), password=gen_password(request.json.get("password")), - username=request.json.get("username")) - Base.session.add(new_user) - Base.session.commit() - return json_success(new_user.to_json()) diff --git a/code/backend/nest_backend/routes/users/user_delete.py b/code/backend/nest_backend/routes/users/user_delete.py deleted file mode 100644 index 892e3fb..0000000 --- a/code/backend/nest_backend/routes/users/user_delete.py +++ /dev/null @@ -1,28 +0,0 @@ -from flask import render_template, abort, jsonify, request -from ...database import * -from flask_jwt_extended import jwt_required -from ...gestione import * -from flask_cors import cross_origin - - -@cross_origin() -@jwt_required() -def page_user_delete(): - """ - API call that allows an user to be deleted from NEST. - :parameter email: The email of the user that needs to be removed. - :return: JSON-formatted data. If the user has the privilege, the target user exists and differs from the current - user, the target gets deleted and a json containing the field "result":"success" is returned. - """ - user = find_user(get_jwt_identity()) - if not user.isAdmin: - return json_error("User is not admin."), 403 - deluser=request.json.get('email') - target = find_user(deluser) - if not target: - return json_error("User not found."), 404 - if user == target: - return json_error("The user cant delete himself. Its a sin."), 406 - Base.session.delete(target) - Base.session.commit() - return json_success("The user has been deleted.") diff --git a/code/backend/nest_backend/routes/users/users.py b/code/backend/nest_backend/routes/users/users.py new file mode 100644 index 0000000..898bdd0 --- /dev/null +++ b/code/backend/nest_backend/routes/users/users.py @@ -0,0 +1,29 @@ +from flask import render_template, abort, jsonify, request +from ...database import * +from flask_jwt_extended import jwt_required +from ...gestione import * +from flask_cors import cross_origin + + +@cross_origin() +@jwt_required() +def page_users(): + """ + Users: + + GET: gets info about all the users of the platform. Requires the user to be admin. + + POST: email, password, username -> Creates a new user and returns it. Requires the user to be admin. + """ + 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 + users = User.query.all() + return json_success([user.to_json() for user in users]) + if request.method == "POST": + if not user.isAdmin: + return json_error("User is not admin. Thou art not authorized."), 403 + new_user = User(email=request.json.get("email"), password=gen_password(request.json.get("password")), + username=request.json.get("username")) + Base.session.add(new_user) + Base.session.commit() + return json_success(new_user.to_json())