diff --git a/backend/__init__.py b/backend/__init__.py index 1cbbb40d2b..1279b45312 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -41,18 +41,18 @@ def create_app(env=None): ) # Load configuration options from environment - app.config.from_object(f"backend.config.EnvironmentConfig") + app.config.from_object("backend.config.EnvironmentConfig") # Enable logging to files initialise_logger(app) - app.logger.info(f"Starting up a new Tasking Manager application") + app.logger.info("Starting up a new Tasking Manager application") # Connect to database - app.logger.debug(f"Connecting to the database") + app.logger.debug("Connecting to the database") db.init_app(app) migrate.init_app(app, db) - app.logger.debug(f"Initialising frontend routes") + app.logger.debug("Initialising frontend routes") # Main route to frontend @app.route("/") diff --git a/backend/api/annotations/resources.py b/backend/api/annotations/resources.py index 8262905dcd..c4f6be8b13 100644 --- a/backend/api/annotations/resources.py +++ b/backend/api/annotations/resources.py @@ -127,10 +127,10 @@ def post(self, project_id: int, annotation_type: str): application_token ) except NotFound: - current_app.logger.error(f"Invalid token") + current_app.logger.error("Invalid token") return {"Error": "Invalid token"}, 500 else: - current_app.logger.error(f"No token supplied") + current_app.logger.error("No token supplied") return {"Error": "No token supplied"}, 500 try: diff --git a/backend/api/campaigns/resources.py b/backend/api/campaigns/resources.py index 075657233a..3a22c17caa 100644 --- a/backend/api/campaigns/resources.py +++ b/backend/api/campaigns/resources.py @@ -5,7 +5,7 @@ from backend.services.campaign_service import CampaignService from backend.services.organisation_service import OrganisationService from backend.models.postgis.utils import NotFound -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class CampaignsRestAPI(Resource): @@ -44,9 +44,10 @@ def get(self, campaign_id): description: Internal Server Error """ try: - if tm.authenticated_user_id: + authenticated_user_id = token_auth.current_user() + if authenticated_user_id: campaign = CampaignService.get_campaign_as_dto( - campaign_id, tm.authenticated_user_id + campaign_id, authenticated_user_id ) else: campaign = CampaignService.get_campaign_as_dto(campaign_id, 0) @@ -120,7 +121,7 @@ def patch(self, campaign_id): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") @@ -185,7 +186,7 @@ def delete(self, campaign_id): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") @@ -284,7 +285,7 @@ def post(self): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") diff --git a/backend/api/comments/resources.py b/backend/api/comments/resources.py index 6e4bf7c89a..9ad3e5ba81 100644 --- a/backend/api/comments/resources.py +++ b/backend/api/comments/resources.py @@ -52,13 +52,13 @@ def post(self, project_id): 500: description: Internal Server Error """ - - if UserService.is_user_blocked(tm.authenticated_user_id): + authenticated_user_id = token_auth.current_user() + if UserService.is_user_blocked(authenticated_user_id): return "User is on read only mode", 403 try: chat_dto = ChatMessageDTO(request.get_json()) - chat_dto.user_id = tm.authenticated_user_id + chat_dto.user_id = authenticated_user_id chat_dto.project_id = project_id chat_dto.validate() except DataError as e: @@ -67,7 +67,7 @@ def post(self, project_id): try: project_messages = ChatService.post_message( - chat_dto, project_id, tm.authenticated_user_id + chat_dto, project_id, authenticated_user_id ) return project_messages.to_primitive(), 201 except ValueError as e: @@ -188,7 +188,7 @@ def post(self, project_id, task_id): """ try: task_comment = TaskCommentDTO(request.get_json()) - task_comment.user_id = tm.authenticated_user_id + task_comment.user_id = token_auth.current_user() task_comment.task_id = task_id task_comment.project_id = project_id task_comment.validate() @@ -263,7 +263,7 @@ def get(self, project_id, task_id): """ try: task_comment = TaskCommentDTO(request.get_json()) - task_comment.user_id = tm.authenticated_user_id + task_comment.user_id = token_auth.current_user() task_comment.task_id = task_id task_comment.project_id = project_id task_comment.validate() diff --git a/backend/api/interests/resources.py b/backend/api/interests/resources.py index 955b0e44bf..4cd7c6efb4 100644 --- a/backend/api/interests/resources.py +++ b/backend/api/interests/resources.py @@ -5,7 +5,7 @@ from backend.models.postgis.utils import NotFound from backend.services.interests_service import InterestService from backend.services.organisation_service import OrganisationService -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth from sqlalchemy.exc import IntegrityError @@ -50,7 +50,7 @@ def post(self): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") @@ -145,7 +145,7 @@ def get(self, interest_id): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") @@ -206,7 +206,7 @@ def patch(self, interest_id): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") @@ -265,7 +265,7 @@ def delete(self, interest_id): """ try: orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + token_auth.current_user() ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a Org Manager") diff --git a/backend/api/licenses/actions.py b/backend/api/licenses/actions.py index bc9de69fd7..d8c0f18772 100644 --- a/backend/api/licenses/actions.py +++ b/backend/api/licenses/actions.py @@ -1,6 +1,6 @@ from flask_restful import Resource, current_app -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth from backend.services.users.user_service import UserService, NotFound @@ -38,7 +38,7 @@ def post(self, license_id): description: Internal Server Error """ try: - UserService.accept_license_terms(tm.authenticated_user_id, license_id) + UserService.accept_license_terms(token_auth.current_user(), license_id) return {"Success": "Terms Accepted"}, 200 except NotFound: return {"Error": "User or mapping not found"}, 404 diff --git a/backend/api/notifications/actions.py b/backend/api/notifications/actions.py index c89dfa52c3..17f18d9001 100644 --- a/backend/api/notifications/actions.py +++ b/backend/api/notifications/actions.py @@ -42,7 +42,7 @@ def delete(self): message_ids = request.get_json()["messageIds"] if message_ids: MessageService.delete_multiple_messages( - message_ids, tm.authenticated_user_id + message_ids, token_auth.current_user() ) return {"Success": "Messages deleted"}, 200 diff --git a/backend/api/notifications/resources.py b/backend/api/notifications/resources.py index 884b3e512b..ac29a18669 100644 --- a/backend/api/notifications/resources.py +++ b/backend/api/notifications/resources.py @@ -43,7 +43,7 @@ def get(self, message_id): """ try: user_message = MessageService.get_message_as_dto( - message_id, tm.authenticated_user_id + message_id, token_auth.current_user() ) return user_message.to_primitive(), 200 except MessageServiceError: @@ -89,7 +89,7 @@ def delete(self, message_id): description: Internal Server Error """ try: - MessageService.delete_message(message_id, tm.authenticated_user_id) + MessageService.delete_message(message_id, token_auth.current_user()) return {"Success": "Message deleted"}, 200 except MessageServiceError: return {"Error": "Unable to delete message"}, 403 @@ -171,7 +171,7 @@ def get(self): project = request.args.get("project", None, int) task_id = request.args.get("taskId", None, int) user_messages = MessageService.get_all_messages( - tm.authenticated_user_id, + token_auth.current_user(), preferred_locale, page, page_size, @@ -215,7 +215,7 @@ def get(self): """ try: unread_count = MessageService.has_user_new_messages( - tm.authenticated_user_id + token_auth.current_user() ) return unread_count, 200 except Exception as e: diff --git a/backend/api/organisations/campaigns.py b/backend/api/organisations/campaigns.py index 34333e7ab0..c14db621d9 100644 --- a/backend/api/organisations/campaigns.py +++ b/backend/api/organisations/campaigns.py @@ -4,7 +4,7 @@ from backend.services.organisation_service import OrganisationService from backend.models.postgis.utils import NotFound from backend.models.postgis.campaign import Campaign -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class OrganisationsCampaignsAPI(Resource): @@ -50,7 +50,7 @@ def post(self, organisation_id, campaign_id): """ try: if OrganisationService.can_user_manage_organisation( - organisation_id, tm.authenticated_user_id + organisation_id, token_auth.current_user() ): if Campaign.campaign_organisation_exists(campaign_id, organisation_id): message = "Campaign {} is already assigned to organisation {}.".format( @@ -155,7 +155,7 @@ def delete(self, organisation_id, campaign_id): """ try: if OrganisationService.can_user_manage_organisation( - organisation_id, tm.authenticated_user_id + organisation_id, token_auth.current_user() ): CampaignService.delete_organisation_campaign( organisation_id, campaign_id diff --git a/backend/api/organisations/resources.py b/backend/api/organisations/resources.py index b2a532eef0..1d6276968f 100644 --- a/backend/api/organisations/resources.py +++ b/backend/api/organisations/resources.py @@ -13,7 +13,7 @@ ) from backend.services.users.user_service import UserService -from backend.services.users.authentication_service import token_auth, tm, verify_token +from backend.services.users.authentication_service import token_auth class OrganisationsRestAPI(Resource): @@ -70,7 +70,7 @@ def post(self): 500: description: Internal Server Error """ - request_user = User.get_by_id(tm.authenticated_user_id) + request_user = User.get_by_id(token_auth.current_user()) if request_user.role != 1: return {"Error": "Only admin users can create organisations."}, 403 @@ -128,7 +128,7 @@ def delete(self, organisation_id): description: Internal Server Error """ if not OrganisationService.can_user_manage_organisation( - organisation_id, tm.authenticated_user_id + organisation_id, token_auth.current_user() ): return {"Error": "User is not an admin for the org"}, 403 try: @@ -174,10 +174,11 @@ def get(self, organisation_id): description: Internal Server Error """ try: - if tm.authenticated_user_id is None: + authenticated_user_id = token_auth.current_user() + if authenticated_user_id is None: user_id = 0 else: - user_id = tm.authenticated_user_id + user_id = authenticated_user_id organisation_dto = OrganisationService.get_organisation_by_id_as_dto( organisation_id, user_id ) @@ -247,7 +248,7 @@ def patch(self, organisation_id): description: Internal Server Error """ if not OrganisationService.can_user_manage_organisation( - organisation_id, tm.authenticated_user_id + organisation_id, token_auth.current_user() ): return {"Error": "User is not an admin for the org"}, 403 try: @@ -272,6 +273,7 @@ def patch(self, organisation_id): class OrganisationsAllAPI(Resource): + @token_auth.login_required(optional=True) def get(self): """ List all organisations @@ -307,6 +309,7 @@ def get(self): """ # Restrict some of the parameters to some permissions + authenticated_user_id = token_auth.current_user() try: manager_user_id = int(request.args.get("manager_user_id")) except Exception: @@ -314,15 +317,10 @@ def get(self): if manager_user_id is not None: try: - # Verify login - verify_token( - request.environ.get("HTTP_AUTHORIZATION").split(None, 1)[1] - ) - # Check whether user is admin (can do any query) or user is checking for own projects if ( - not UserService.is_user_an_admin(tm.authenticated_user_id) - and tm.authenticated_user_id != manager_user_id + not UserService.is_user_an_admin(authenticated_user_id) + and authenticated_user_id != manager_user_id ): raise ValueError @@ -332,7 +330,7 @@ def get(self): # Obtain organisations try: results_dto = OrganisationService.get_organisations_as_dto( - manager_user_id, tm.authenticated_user_id + manager_user_id, authenticated_user_id ) return results_dto.to_primitive(), 200 except NotFound: diff --git a/backend/api/projects/actions.py b/backend/api/projects/actions.py index 3964d932d6..e2da0f9163 100644 --- a/backend/api/projects/actions.py +++ b/backend/api/projects/actions.py @@ -7,7 +7,7 @@ from backend.services.project_service import ProjectService, NotFound from backend.services.project_admin_service import ProjectAdminService from backend.services.messaging.message_service import MessageService -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth from backend.services.interests_service import InterestService @@ -54,8 +54,9 @@ def post(self, project_id): """ try: username = request.get_json()["username"] + authenticated_user_id = token_auth.current_user() ProjectAdminService.transfer_project_to( - project_id, tm.authenticated_user_id, username + project_id, authenticated_user_id, username ) return {"Success": "Project Transfered"}, 200 except ValueError as e: @@ -114,8 +115,9 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() message_dto = MessageDTO(request.get_json()) - message_dto.from_user_id = tm.authenticated_user_id + message_dto.from_user_id = authenticated_user_id message_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") @@ -123,7 +125,7 @@ def post(self, project_id): try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) threading.Thread( target=MessageService.send_message_to_all_contributors, @@ -175,15 +177,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"FeaturedProjects POST: {str(e)}" return {"Error": error_msg}, 403 try: - ProjectService.set_project_as_featured(project_id, tm.authenticated_user_id) + ProjectService.set_project_as_featured(project_id, authenticated_user_id) return {"Success": True}, 200 except NotFound: return {"Error": "Project Not Found"}, 404 @@ -233,7 +236,7 @@ def post(self, project_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"FeaturedProjects POST: {str(e)}" @@ -300,7 +303,7 @@ def post(self, project_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsActionsSetInterestsAPI POST: {str(e)}" diff --git a/backend/api/projects/campaigns.py b/backend/api/projects/campaigns.py index 98c8d12de0..1dd69bcde0 100644 --- a/backend/api/projects/campaigns.py +++ b/backend/api/projects/campaigns.py @@ -5,7 +5,7 @@ from backend.services.campaign_service import CampaignService from backend.services.project_admin_service import ProjectAdminService from backend.models.postgis.utils import NotFound -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class ProjectsCampaignsAPI(Resource): @@ -51,7 +51,7 @@ def post(self, project_id, campaign_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsCampaignsAPI POST: {str(e)}" @@ -154,7 +154,7 @@ def delete(self, project_id, campaign_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsCampaignsAPI DELETE: {str(e)}" diff --git a/backend/api/projects/favorites.py b/backend/api/projects/favorites.py index 6a71df0021..6c0334de9f 100644 --- a/backend/api/projects/favorites.py +++ b/backend/api/projects/favorites.py @@ -4,7 +4,7 @@ from backend.models.postgis.utils import NotFound from backend.models.dtos.project_dto import ProjectFavoriteDTO from backend.services.project_service import ProjectService -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class ProjectsFavoritesAPI(Resource): @@ -40,7 +40,7 @@ def get(self, project_id: int): description: Internal Server Error """ try: - user_id = tm.authenticated_user_id + user_id = token_auth.current_user() favorited = ProjectService.is_favorited(project_id, user_id) if favorited is True: return {"favorited": True}, 200 @@ -85,15 +85,16 @@ def post(self, project_id: int): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() favorite_dto = ProjectFavoriteDTO() favorite_dto.project_id = project_id - favorite_dto.user_id = tm.authenticated_user_id + favorite_dto.user_id = authenticated_user_id favorite_dto.validate() except DataError as e: current_app.logger.error(f"Error validating request: {str(e)}") return str(e), 400 try: - ProjectService.favorite(project_id, tm.authenticated_user_id) + ProjectService.favorite(project_id, authenticated_user_id) except NotFound: return {"Error": "Project Not Found"}, 404 except ValueError as e: @@ -137,7 +138,7 @@ def delete(self, project_id: int): description: Internal Server Error """ try: - ProjectService.unfavorite(project_id, tm.authenticated_user_id) + ProjectService.unfavorite(project_id, token_auth.current_user()) except NotFound: return {"Error": "Project Not Found"}, 404 except ValueError as e: diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py index e06d9842e9..a97c3a1e00 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -22,7 +22,7 @@ ) from backend.services.users.user_service import UserService from backend.services.organisation_service import OrganisationService -from backend.services.users.authentication_service import token_auth, tm, verify_token +from backend.services.users.authentication_service import token_auth from backend.services.project_admin_service import ( ProjectAdminService, ProjectAdminServiceError, @@ -42,6 +42,12 @@ def get(self, project_id): produces: - application/json parameters: + - in: header + name: Authorization + description: Base64 encoded session token + required: false + type: string + default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting @@ -75,6 +81,7 @@ def get(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() as_file = ( strtobool(request.args.get("as_file")) if request.args.get("as_file") @@ -88,7 +95,7 @@ def get(self, project_id): project_dto = ProjectService.get_project_dto_for_mapper( project_id, - tm.authenticated_user_id, + authenticated_user_id, request.environ.get("HTTP_ACCEPT_LANGUAGE"), abbreviated, ) @@ -189,7 +196,7 @@ def post(self): """ try: draft_project_dto = DraftProjectDTO(request.get_json()) - draft_project_dto.user_id = tm.authenticated_user_id + draft_project_dto.user_id = token_auth.current_user() draft_project_dto.validate() except DataError as e: current_app.logger.error(f"error validating request: {str(e)}") @@ -245,7 +252,7 @@ def head(self, project_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsRestAPI HEAD: {str(e)}" @@ -387,9 +394,10 @@ def patch(self, project_id): 500: description: Internal Server Error """ + authenticated_user_id = token_auth.current_user() try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"ProjectsRestAPI PATCH: {str(e)}" @@ -404,7 +412,7 @@ def patch(self, project_id): return {"Error": "Unable to update project"}, 400 try: - ProjectAdminService.update_project(project_dto, tm.authenticated_user_id) + ProjectAdminService.update_project(project_dto, authenticated_user_id) return {"Status": "Updated"}, 200 except InvalidGeoJson as e: return {"Invalid GeoJson": str(e)}, 400 @@ -450,15 +458,16 @@ def delete(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"ProjectsRestAPI DELETE: {str(e)}" return {"Error": error_msg}, 403 try: - ProjectAdminService.delete_project(project_id, tm.authenticated_user_id) + ProjectAdminService.delete_project(project_id, authenticated_user_id) return {"Success": "Project deleted"}, 200 except ProjectAdminServiceError: return {"Error": "Project has some mapping"}, 403 @@ -471,6 +480,7 @@ def delete(self, project_id): class ProjectSearchBase(Resource): + @token_auth.login_required(optional=True) def setup_search_dto(self): search_dto = ProjectSearchDTO() search_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") @@ -489,19 +499,18 @@ def setup_search_dto(self): # See https://github.com/hotosm/tasking-manager/pull/922 for more info try: - verify_token(request.environ.get("HTTP_AUTHORIZATION").split(None, 1)[1]) - + authenticated_user_id = token_auth.current_user() if request.args.get("createdByMe") == "true": - search_dto.created_by = tm.authenticated_user_id + search_dto.created_by = authenticated_user_id if request.args.get("mappedByMe") == "true": - search_dto.mapped_by = tm.authenticated_user_id + search_dto.mapped_by = authenticated_user_id if request.args.get("favoritedByMe") == "true": - search_dto.favorited_by = tm.authenticated_user_id + search_dto.favorited_by = authenticated_user_id if request.args.get("managedByMe") == "true": - search_dto.managed_by = tm.authenticated_user_id + search_dto.managed_by = authenticated_user_id except Exception: pass @@ -691,8 +700,9 @@ def get(self): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + authenticated_user_id ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a project manager") @@ -711,7 +721,7 @@ def get(self): else False ) if createdByMe: - search_dto.project_author = tm.authenticated_user_id + search_dto.project_author = authenticated_user_id search_dto.validate() except Exception as e: current_app.logger.error(f"Error validating request: {str(e)}") @@ -765,8 +775,9 @@ def get(self): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() orgs_dto = OrganisationService.get_organisations_managed_by_user_as_dto( - tm.authenticated_user_id + authenticated_user_id ) if len(orgs_dto.organisations) < 1: raise ValueError("User not a project manager") @@ -777,7 +788,7 @@ def get(self): try: search_dto = self.setup_search_dto() admin_projects = ProjectAdminService.get_projects_for_admin( - tm.authenticated_user_id, + authenticated_user_id, request.environ.get("HTTP_ACCEPT_LANGUAGE"), search_dto, ) @@ -988,7 +999,7 @@ def get(self, project_id): """ try: ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + token_auth.current_user(), project_id ) except ValueError as e: error_msg = f"ProjectsQueriesNoTasksAPI GET: {str(e)}" diff --git a/backend/api/projects/teams.py b/backend/api/projects/teams.py index 353fde58d0..ea728013f1 100644 --- a/backend/api/projects/teams.py +++ b/backend/api/projects/teams.py @@ -2,7 +2,7 @@ from schematics.exceptions import DataError from backend.services.team_service import TeamService, TeamServiceError, NotFound -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class ProjectsTeamsAPI(Resource): @@ -92,7 +92,7 @@ def post(self, team_id, project_id): 500: description: Internal Server Error """ - if not TeamService.user_is_manager(team_id, tm.authenticated_user_id): + if not TeamService.user_is_manager(team_id, token_auth.current_user()): return {"Error": "User is not an admin or a manager for the team"}, 401 try: @@ -163,7 +163,7 @@ def patch(self, team_id, project_id): 500: description: Internal Server Error """ - if not TeamService.user_is_manager(team_id, tm.authenticated_user_id): + if not TeamService.user_is_manager(team_id, token_auth.current_user()): return {"Error": "User is not an admin or a manager for the team"}, 401 try: role = request.get_json(force=True)["role"] @@ -217,7 +217,7 @@ def delete(self, team_id, project_id): 500: description: Internal Server Error """ - if not TeamService.user_is_manager(team_id, tm.authenticated_user_id): + if not TeamService.user_is_manager(team_id, token_auth.current_user()): return {"Error": "User is not an admin or a manager for the team"}, 401 try: TeamService.delete_team_project(team_id, project_id) diff --git a/backend/api/system/applications.py b/backend/api/system/applications.py index e15a18da22..f2ad6e6a8b 100644 --- a/backend/api/system/applications.py +++ b/backend/api/system/applications.py @@ -1,7 +1,7 @@ from flask_restful import Resource, current_app from backend.services.application_service import ApplicationService, NotFound -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class SystemApplicationsRestAPI(Resource): @@ -31,7 +31,7 @@ def get(self): """ try: tokens = ApplicationService.get_all_tokens_for_logged_in_user( - tm.authenticated_user_id + token_auth.current_user() ) if len(tokens) == 0: return 400 @@ -66,7 +66,7 @@ def post(self): description: A problem occurred """ try: - token = ApplicationService.create_token(tm.authenticated_user_id) + token = ApplicationService.create_token(token_auth.current_user()) return token.to_primitive(), 200 except Exception as e: error_msg = f"Application POST API - unhandled error: {str(e)}" @@ -141,7 +141,7 @@ def delete(self, application_key): """ try: token = ApplicationService.get_token(application_key) - if token.user == tm.authenticated_user_id: + if token.user == token_auth.current_user(): token.delete() return 200 else: diff --git a/backend/api/system/image_upload.py b/backend/api/system/image_upload.py index 69438be448..44efec5a17 100644 --- a/backend/api/system/image_upload.py +++ b/backend/api/system/image_upload.py @@ -4,7 +4,7 @@ from flask_restful import Resource, request, current_app from backend.services.organisation_service import OrganisationService -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth class SystemImageUploadRestAPI(Resource): @@ -59,7 +59,7 @@ def post(self): is_org_manager = ( len( OrganisationService.get_organisations_managed_by_user( - tm.authenticated_user_id + token_auth.current_user() ) ) > 0 diff --git a/backend/api/tasks/actions.py b/backend/api/tasks/actions.py index 3823336c0a..73b688d069 100644 --- a/backend/api/tasks/actions.py +++ b/backend/api/tasks/actions.py @@ -78,7 +78,7 @@ def post(self, project_id, task_id): """ try: lock_task_dto = LockTaskDTO() - lock_task_dto.user_id = tm.authenticated_user_id + lock_task_dto.user_id = token_auth.current_user() lock_task_dto.project_id = project_id lock_task_dto.task_id = task_id lock_task_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") @@ -164,7 +164,7 @@ def post(self, project_id, task_id): """ try: stop_task = StopMappingTaskDTO(request.get_json()) - stop_task.user_id = tm.authenticated_user_id + stop_task.user_id = token_auth.current_user() stop_task.task_id = task_id stop_task.project_id = project_id stop_task.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") @@ -247,8 +247,9 @@ def post(self, project_id, task_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() mapped_task = MappedTaskDTO(request.get_json()) - mapped_task.user_id = tm.authenticated_user_id + mapped_task.user_id = authenticated_user_id mapped_task.task_id = task_id mapped_task.project_id = project_id mapped_task.validate() @@ -269,7 +270,7 @@ def post(self, project_id, task_id): return {"Error": "Task unlock failed"}, 500 finally: # Refresh mapper level after mapping - UserService.check_and_update_mapper_level(tm.authenticated_user_id) + UserService.check_and_update_mapper_level(authenticated_user_id) class TasksActionsMappingUndoAPI(Resource): @@ -320,7 +321,7 @@ def post(self, project_id, task_id): try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") task = MappingService.undo_mapping( - project_id, task_id, tm.authenticated_user_id, preferred_locale + project_id, task_id, token_auth.current_user(), preferred_locale ) return task.to_primitive(), 200 except NotFound: @@ -393,7 +394,7 @@ def post(self, project_id): try: validator_dto = LockForValidationDTO(request.get_json()) validator_dto.project_id = project_id - validator_dto.user_id = tm.authenticated_user_id + validator_dto.user_id = token_auth.current_user() validator_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validator_dto.validate() except DataError as e: @@ -474,7 +475,7 @@ def post(self, project_id): try: validated_dto = StopValidationDTO(request.get_json()) validated_dto.project_id = project_id - validated_dto.user_id = tm.authenticated_user_id + validated_dto.user_id = token_auth.current_user() validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validated_dto.validate() except DataError as e: @@ -551,7 +552,7 @@ def post(self, project_id): try: validated_dto = UnlockAfterValidationDTO(request.get_json()) validated_dto.project_id = project_id - validated_dto.user_id = tm.authenticated_user_id + validated_dto.user_id = token_auth.current_user() validated_dto.preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") validated_dto.validate() except DataError as e: @@ -605,15 +606,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsMapAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: - MappingService.map_all_tasks(project_id, tm.authenticated_user_id) + MappingService.map_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks mapped"}, 200 except Exception as e: error_msg = f"TasksActionsMapAllAPI POST - unhandled error: {str(e)}" @@ -655,15 +657,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsValidateAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: - ValidatorService.validate_all_tasks(project_id, tm.authenticated_user_id) + ValidatorService.validate_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks validated"}, 200 except Exception as e: error_msg = f"TasksActionsValidateAllAPI POST - unhandled error: {str(e)}" @@ -705,15 +708,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsInvalidateAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: - ValidatorService.invalidate_all_tasks(project_id, tm.authenticated_user_id) + ValidatorService.invalidate_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks invalidated"}, 200 except Exception as e: error_msg = f"TasksActionsInvalidateAllAPI POST - unhandled error: {str(e)}" @@ -755,15 +759,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsResetBadImageryAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: - MappingService.reset_all_badimagery(project_id, tm.authenticated_user_id) + MappingService.reset_all_badimagery(project_id, authenticated_user_id) return {"Success": "All bad imagery tasks marked ready for mapping"}, 200 except Exception as e: error_msg = ( @@ -807,15 +812,16 @@ def post(self, project_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() ProjectAdminService.is_user_action_permitted_on_project( - tm.authenticated_user_id, project_id + authenticated_user_id, project_id ) except ValueError as e: error_msg = f"TasksActionsResetAllAPI POST: {str(e)}" return {"Error": error_msg}, 403 try: - ProjectAdminService.reset_all_tasks(project_id, tm.authenticated_user_id) + ProjectAdminService.reset_all_tasks(project_id, authenticated_user_id) return {"Success": "All tasks reset"}, 200 except Exception as e: error_msg = f"TasksActionsResetAllAPI POST - unhandled error: {str(e)}" @@ -874,7 +880,7 @@ def post(self, project_id, task_id): """ try: split_task_dto = SplitTaskDTO() - split_task_dto.user_id = tm.authenticated_user_id + split_task_dto.user_id = token_auth.current_user() split_task_dto.project_id = project_id split_task_dto.task_id = task_id split_task_dto.preferred_locale = request.environ.get( diff --git a/backend/api/tasks/resources.py b/backend/api/tasks/resources.py index 093f7d1b5c..38238631bd 100644 --- a/backend/api/tasks/resources.py +++ b/backend/api/tasks/resources.py @@ -8,7 +8,7 @@ from backend.services.mapping_service import MappingService, NotFound from backend.models.dtos.grid_dto import GridDTO -from backend.services.users.authentication_service import token_auth, tm, verify_token +from backend.services.users.authentication_service import token_auth, tm from backend.services.validator_service import ValidatorService from backend.services.project_service import ProjectService, ProjectServiceError @@ -26,12 +26,6 @@ def get(self, project_id, task_id): produces: - application/json parameters: - - in: header - name: Authorization - description: Base64 encoded session token - required: false - type: string - default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting @@ -60,17 +54,8 @@ def get(self, project_id, task_id): """ try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") - token = request.environ.get("HTTP_AUTHORIZATION") - - # Login isn't required here, but if we have a token we can find out if the user can undo the task - if token: - verify_token(token[6:]) - user_id = tm.authenticated_user_id - - task = MappingService.get_task_as_dto( - task_id, project_id, preferred_locale, user_id - ) + task = MappingService.get_task_as_dto(task_id, project_id, preferred_locale) return task.to_primitive(), 200 except NotFound: return {"Error": "Task Not Found"}, 404 diff --git a/backend/api/teams/actions.py b/backend/api/teams/actions.py index df6c989db0..a0a83ee91c 100644 --- a/backend/api/teams/actions.py +++ b/backend/api/teams/actions.py @@ -61,8 +61,9 @@ def post(self, team_id): return str(e), 400 try: - TeamService.join_team(team_id, tm.authenticated_user_id, username, role) - if TeamService.user_is_manager(team_id, tm.authenticated_user_id): + authenticated_user_id = token_auth.current_user() + TeamService.join_team(team_id, authenticated_user_id, username, role) + if TeamService.user_is_manager(team_id, authenticated_user_id): return {"Success": "User added to the team"}, 200 else: return {"Success": "Request to join the team sent successfully."}, 200 @@ -138,10 +139,11 @@ def patch(self, team_id): return str(e), 400 try: + authenticated_user_id = token_auth.current_user() if request_type == "join-response": - if TeamService.user_is_manager(team_id, tm.authenticated_user_id): + if TeamService.user_is_manager(team_id, authenticated_user_id): TeamService.accept_reject_join_request( - team_id, tm.authenticated_user_id, username, role, action + team_id, authenticated_user_id, username, role, action ) return {"Success": "True"}, 200 else: @@ -153,7 +155,7 @@ def patch(self, team_id): ) elif request_type == "invite-response": TeamService.accept_reject_invitation_request( - team_id, tm.authenticated_user_id, username, role, action + team_id, authenticated_user_id, username, role, action ) return {"Success": "True"}, 200 except Exception as e: @@ -208,10 +210,11 @@ def post(self, team_id): description: Internal Server Error """ try: + authenticated_user_id = token_auth.current_user() username = request.get_json(force=True)["username"] - request_user = User.get_by_id(tm.authenticated_user_id) + request_user = User.get_by_id(authenticated_user_id) if ( - TeamService.user_is_manager(team_id, tm.authenticated_user_id) + TeamService.user_is_manager(team_id, authenticated_user_id) or request_user.username == username ): TeamService.leave_team(team_id, username) diff --git a/backend/api/teams/resources.py b/backend/api/teams/resources.py index 7e8ad3719f..73ae0c8177 100644 --- a/backend/api/teams/resources.py +++ b/backend/api/teams/resources.py @@ -3,7 +3,7 @@ from backend.models.dtos.team_dto import TeamDTO, NewTeamDTO, UpdateTeamDTO from backend.services.team_service import TeamService, TeamServiceError, NotFound -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth from backend.services.organisation_service import OrganisationService from backend.services.users.user_service import UserService @@ -72,17 +72,18 @@ def post(self, team_id): team_dto.team_id = team_id team_dto.validate() + authenticated_user_id = token_auth.current_user() team_details_dto = TeamService.get_team_as_dto( - team_id, tm.authenticated_user_id + team_id, authenticated_user_id ) org = TeamService.assert_validate_organisation(team_dto.organisation_id) TeamService.assert_validate_members(team_details_dto) if not TeamService.user_is_manager( - team_id, tm.authenticated_user_id + team_id, authenticated_user_id ) and not OrganisationService.can_user_manage_organisation( - org.id, tm.authenticated_user_id + org.id, authenticated_user_id ): return {"Error": "User is not a admin or a manager for the team"}, 401 except DataError as e: @@ -165,10 +166,11 @@ def patch(self, team_id): team_dto.team_id = team_id team_dto.validate() + authenticated_user_id = token_auth.current_user() if not TeamService.user_is_manager( - team_id, tm.authenticated_user_id + team_id, authenticated_user_id ) and not OrganisationService.can_user_manage_organisation( - team.organisation_id, tm.authenticated_user_id + team.organisation_id, authenticated_user_id ): return {"Error": "User is not a admin or a manager for the team"}, 401 except DataError as e: @@ -213,10 +215,11 @@ def get(self, team_id): description: Internal Server Error """ try: - if tm.authenticated_user_id is None: + authenticated_user_id = token_auth.current_user() + if authenticated_user_id is None: user_id = 0 else: - user_id = tm.authenticated_user_id + user_id = authenticated_user_id team_dto = TeamService.get_team_as_dto(team_id, user_id) return team_dto.to_primitive(), 200 except NotFound: @@ -262,7 +265,7 @@ def delete(self, team_id): 500: description: Internal Server Error """ - if not TeamService.user_is_manager(team_id, tm.authenticated_user_id): + if not TeamService.user_is_manager(team_id, token_auth.current_user()): return {"Error": "User is not a manager for the team"}, 401 try: TeamService.delete_team(team_id) @@ -333,7 +336,7 @@ def get(self): description: Internal Server Error """ try: - user_id = tm.authenticated_user_id + user_id = token_auth.current_user() except Exception as e: error_msg = f"Teams GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) @@ -418,7 +421,7 @@ def post(self): 500: description: Internal Server Error """ - user_id = tm.authenticated_user_id + user_id = token_auth.current_user() try: team_dto = NewTeamDTO(request.get_json()) diff --git a/backend/api/users/actions.py b/backend/api/users/actions.py index 7c3b6b7202..5b4303c178 100644 --- a/backend/api/users/actions.py +++ b/backend/api/users/actions.py @@ -77,8 +77,8 @@ def patch(self): ) user_dto.validate() - - if tm.authenticated_user_id != user_dto.id: + authenticated_user_id = token_auth.current_user() + if authenticated_user_id != user_dto.id: return {"Error": "Unable to authenticate"}, 401 except ValueError as e: return {"Error": str(e)}, 400 @@ -88,7 +88,7 @@ def patch(self): try: verification_sent = UserService.update_user_details( - tm.authenticated_user_id, user_dto + authenticated_user_id, user_dto ) return verification_sent, 200 except NotFound: @@ -197,7 +197,7 @@ def patch(self, username, role): description: Internal Server Error """ try: - UserService.add_role_to_user(tm.authenticated_user_id, username, role) + UserService.add_role_to_user(token_auth.current_user(), username, role) return {"Success": "Role Added"}, 200 except UserServiceError: return {"Error": "Not allowed"}, 403 @@ -246,7 +246,7 @@ def patch(self, is_expert): """ try: UserService.set_user_is_expert( - tm.authenticated_user_id, is_expert == "true" + token_auth.current_user(), is_expert == "true" ) return {"Success": "Expert mode updated"}, 200 except UserServiceError: @@ -284,7 +284,7 @@ def patch(self): description: Internal Server Error """ try: - MessageService.resend_email_validation(tm.authenticated_user_id) + MessageService.resend_email_validation(token_auth.current_user()) return {"Success": "Verification email resent"}, 200 except Exception as e: error_msg = f"User GET - unhandled error: {str(e)}" @@ -341,7 +341,7 @@ def post(self): user_dto = UserRegisterEmailDTO(dict(email=user_dto.email, details=str(e))) return user_dto.to_primitive(), 400 except Exception as e: - details_msg = f"User POST - unhandled error: Unknown error" + details_msg = "User POST - unhandled error: Unknown error" current_app.logger.critical(str(e)) user_dto = UserRegisterEmailDTO( dict(email=user_dto.email, details=details_msg) @@ -389,7 +389,7 @@ def post(self): try: data = request.get_json() user_interests = InterestService.create_or_update_user_interests( - tm.authenticated_user_id, data["interests"] + token_auth.current_user(), data["interests"] ) return user_interests.to_primitive(), 200 except ValueError as e: diff --git a/backend/api/users/resources.py b/backend/api/users/resources.py index 2b0a38f120..7aacdde9e6 100644 --- a/backend/api/users/resources.py +++ b/backend/api/users/resources.py @@ -2,7 +2,7 @@ from schematics.exceptions import DataError from backend.models.dtos.user_dto import UserSearchQuery -from backend.services.users.authentication_service import token_auth, tm +from backend.services.users.authentication_service import token_auth from backend.services.users.user_service import UserService, NotFound from backend.services.project_service import ProjectService @@ -149,7 +149,7 @@ def get(self, username): """ try: user_dto = UserService.get_user_dto_by_username( - username, tm.authenticated_user_id + username, token_auth.current_user() ) return user_dto.to_primitive(), 200 except NotFound: @@ -242,7 +242,7 @@ def get(self): """ try: locked_tasks = ProjectService.get_task_for_logged_in_user( - tm.authenticated_user_id + token_auth.current_user() ) return locked_tasks.to_primitive(), 200 except Exception as e: @@ -287,7 +287,7 @@ def get(self): try: preferred_locale = request.environ.get("HTTP_ACCEPT_LANGUAGE") locked_tasks = ProjectService.get_task_details_for_logged_in_user( - tm.authenticated_user_id, preferred_locale + token_auth.current_user(), preferred_locale ) return locked_tasks.to_primitive(), 200 except NotFound: @@ -324,7 +324,7 @@ def get(self): description: Internal Server Error """ try: - favs_dto = UserService.get_projects_favorited(tm.authenticated_user_id) + favs_dto = UserService.get_projects_favorited(token_auth.current_user()) return favs_dto.to_primitive(), 200 except NotFound: return {"Error": "User not found"}, 404 diff --git a/backend/models/dtos/mapping_dto.py b/backend/models/dtos/mapping_dto.py index b581a33515..70a6893b8b 100644 --- a/backend/models/dtos/mapping_dto.py +++ b/backend/models/dtos/mapping_dto.py @@ -1,6 +1,6 @@ from schematics import Model from schematics.exceptions import ValidationError -from schematics.types import StringType, IntType, BooleanType, UTCDateTimeType +from schematics.types import StringType, IntType, UTCDateTimeType from schematics.types.compound import ListType, ModelType from backend.models.postgis.statuses import TaskStatus from backend.models.dtos.mapping_issues_dto import TaskMappingIssueDTO @@ -86,7 +86,6 @@ class TaskDTO(Model): per_task_instructions = StringType( serialized_name="perTaskInstructions", serialize_when_none=False ) - is_undoable = BooleanType(serialized_name="isUndoable", default=False) auto_unlock_seconds = IntType(serialized_name="autoUnlockSeconds") last_updated = UTCDateTimeType( serialized_name="lastUpdated", serialize_when_none=False diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 667b7d55d0..bd3441b2e9 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -438,7 +438,7 @@ def update(self, project_dto: ProjectDTO): team = Team.get(team_dto.team_id) if team is None: - raise NotFound(f"Team not found") + raise NotFound("Team not found") role = TeamRoles[team_dto.role].value ProjectTeams(project=self, team=team, role=role) @@ -1023,6 +1023,7 @@ def as_dto_for_mapping( is_allowed_user = False if authenticated_user_id: user = User.get_by_id(authenticated_user_id) + if ( UserRole(user.role) == UserRole.ADMIN or authenticated_user_id == self.author_id diff --git a/backend/models/postgis/task.py b/backend/models/postgis/task.py index bc553c368b..06f6531dd8 100644 --- a/backend/models/postgis/task.py +++ b/backend/models/postgis/task.py @@ -189,7 +189,10 @@ class TaskHistory(db.Model): action_text = db.Column(db.String) action_date = db.Column(db.DateTime, nullable=False, default=timestamp) user_id = db.Column( - db.BigInteger, db.ForeignKey("users.id", name="fk_users"), nullable=False + db.BigInteger, + db.ForeignKey("users.id", name="fk_users"), + index=True, + nullable=False, ) invalidation_history = db.relationship( TaskInvalidationHistory, lazy="dynamic", cascade="all" @@ -203,6 +206,7 @@ class TaskHistory(db.Model): [task_id, project_id], ["tasks.id", "tasks.project_id"], name="fk_tasks" ), db.Index("idx_task_history_composite", "task_id", "project_id"), + db.Index("idx_task_history_project_id_user_id", "user_id", "project_id"), {}, ) @@ -494,13 +498,13 @@ class Task(db.Model): geometry = db.Column(Geometry("MULTIPOLYGON", srid=4326)) task_status = db.Column(db.Integer, default=TaskStatus.READY.value) locked_by = db.Column( - db.BigInteger, db.ForeignKey("users.id", name="fk_users_locked") + db.BigInteger, db.ForeignKey("users.id", name="fk_users_locked"), index=True ) mapped_by = db.Column( - db.BigInteger, db.ForeignKey("users.id", name="fk_users_mapper") + db.BigInteger, db.ForeignKey("users.id", name="fk_users_mapper"), index=True ) validated_by = db.Column( - db.BigInteger, db.ForeignKey("users.id", name="fk_users_validator") + db.BigInteger, db.ForeignKey("users.id", name="fk_users_validator"), index=True ) # Mapped objects diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py index 77b299dd1a..9fa73a6573 100644 --- a/backend/services/mapping_service.py +++ b/backend/services/mapping_service.py @@ -44,15 +44,11 @@ def get_task(task_id: int, project_id: int) -> Task: @staticmethod def get_task_as_dto( - task_id: int, - project_id: int, - preferred_local: str = "en", - logged_in_user_id: int = None, + task_id: int, project_id: int, preferred_local: str = "en", ) -> TaskDTO: """ Get task as DTO for transmission over API """ task = MappingService.get_task(task_id, project_id) task_dto = task.as_dto_with_instructions(preferred_local) - task_dto.is_undoable = MappingService._is_task_undoable(logged_in_user_id, task) return task_dto @staticmethod diff --git a/backend/services/messaging/message_service.py b/backend/services/messaging/message_service.py index 15d7ecf59a..6fab829242 100644 --- a/backend/services/messaging/message_service.py +++ b/backend/services/messaging/message_service.py @@ -61,6 +61,8 @@ def send_message_after_validation( user = UserService.get_user_by_id(mapped_by) if user.validation_message is False: return # No need to send validation message + if user.projects_notifications is False: + return text_template = get_template( "invalidation_message_en.txt" @@ -160,6 +162,10 @@ def send_message_after_comment( except NotFound: continue # If we can't find the user, keep going no need to fail + # Validate mention_notification. + if user.mentions_notifications is False: + continue + message = Message() message.message_type = MessageType.MENTION_NOTIFICATION.value message.project_id = project_id @@ -197,6 +203,9 @@ def send_message_after_comment( except NotFound: continue # If we can't find the user, keep going no need to fail + if user.comments_notifications is False: + continue + message = Message() message.message_type = MessageType.TASK_COMMENT_NOTIFICATION.value message.project_id = project_id @@ -291,6 +300,10 @@ def send_message_after_chat(chat_from: int, chat: str, project_id: int): current_app.logger.error(f"Username {username} not found") continue # If we can't find the user, keep going no need to fail + # Validate mention_notification. + if user.mentions_notifications is False: + continue + message = Message() message.message_type = MessageType.MENTION_NOTIFICATION.value message.project_id = project_id @@ -319,6 +332,9 @@ def send_message_after_chat(chat_from: int, chat: str, project_id: int): except NotFound: continue # If we can't find the user, keep going no need to fail + if user.comments_notifications is False: + continue + message = Message() message.message_type = MessageType.PROJECT_CHAT_NOTIFICATION.value message.project_id = project_id @@ -353,6 +369,8 @@ def send_favorite_project_activities(user_id: int): ) ) user = UserService.get_user_dto_by_id(user_id) + if user.projects_notifications is False: + return messages = [] for project in recently_updated_projects: activity_message = [] diff --git a/backend/services/project_admin_service.py b/backend/services/project_admin_service.py index 21b5c19f38..e84b63614d 100644 --- a/backend/services/project_admin_service.py +++ b/backend/services/project_admin_service.py @@ -130,7 +130,10 @@ def update_project(project_dto: ProjectDTO, authenticated_user_id: int): project = ProjectAdminService._get_project_by_id(project_id) project.update(project_dto) else: - raise ValueError("Project can only be updated by admins or by the owner") + raise ValueError( + str(project_id) + + " :Project can only be updated by admins or by the owner" + ) return project @@ -298,31 +301,32 @@ def is_user_action_permitted_on_project( authenticated_user_id, author_id ) is_org_manager = False - if hasattr(project, "organisation_id") and project.organisation_id: - org_id = project.organisation_id - org = OrganisationService.get_organisation_by_id_as_dto( - org_id, authenticated_user_id - ) - if org.is_manager: - is_org_manager = True - - is_manager_team = None - if hasattr(project, "project_teams") and project.project_teams: - teams_dto = TeamService.get_project_teams_as_dto(project_id) - if teams_dto.teams: - teams_allowed = [ - team_dto - for team_dto in teams_dto.teams - if team_dto.role in allowed_roles - ] - user_membership = [ - team_dto.team_id - for team_dto in teams_allowed - if TeamService.is_user_member_of_team( - team_dto.team_id, authenticated_user_id - ) - ] - if user_membership: - is_manager_team = True + is_manager_team = False + if not (is_admin or is_author): + if hasattr(project, "organisation_id") and project.organisation_id: + org_id = project.organisation_id + org = OrganisationService.get_organisation_by_id_as_dto( + org_id, authenticated_user_id + ) + if org.is_manager: + is_org_manager = True + else: + if hasattr(project, "project_teams") and project.project_teams: + teams_dto = TeamService.get_project_teams_as_dto(project_id) + if teams_dto.teams: + teams_allowed = [ + team_dto + for team_dto in teams_dto.teams + if team_dto.role in allowed_roles + ] + user_membership = [ + team_dto.team_id + for team_dto in teams_allowed + if TeamService.is_user_member_of_team( + team_dto.team_id, authenticated_user_id + ) + ] + if user_membership: + is_manager_team = True return is_admin or is_author or is_org_manager or is_manager_team diff --git a/backend/services/users/authentication_service.py b/backend/services/users/authentication_service.py index e7a8fff1a7..aaf5c16027 100644 --- a/backend/services/users/authentication_service.py +++ b/backend/services/users/authentication_service.py @@ -20,7 +20,6 @@ def verify_token(token): """ Verify the supplied token and check user role is correct for the requested resource""" tm.authenticated_user_id = None if not token: - current_app.logger.debug(f"Token not supplied {request.base_url}") return False try: @@ -37,7 +36,7 @@ def verify_token(token): tm.authenticated_user_id = ( user_id # Set the user ID on the decorator as a convenience ) - return True # All tests passed token is good for the requested resource + return user_id # All tests passed token is good for the requested resource class AuthServiceError(Exception): diff --git a/backend/services/users/user_service.py b/backend/services/users/user_service.py index 9b1045b984..479bf9341a 100644 --- a/backend/services/users/user_service.py +++ b/backend/services/users/user_service.py @@ -564,7 +564,7 @@ def add_role_to_user(admin_user_id: int, username: str, role: str): admin_role = UserRole(admin.role) if admin_role != UserRole.ADMIN and requested_role == UserRole.ADMIN: - raise UserServiceError(f"You must be an Admin to assign Admin role") + raise UserServiceError("You must be an Admin to assign Admin role") user = UserService.get_user_by_username(username) user.set_user_role(requested_role) diff --git a/backend/services/validator_service.py b/backend/services/validator_service.py index 0b0963b4d1..e37b73d866 100644 --- a/backend/services/validator_service.py +++ b/backend/services/validator_service.py @@ -63,7 +63,7 @@ def lock_tasks_for_validation(validation_dto: LockForValidationDTO) -> TaskDTOs: ) if not user_can_validate: raise ValidatorServiceError( - f"Tasks cannot be validated by the same user who marked task as mapped or badimagery" + "Tasks cannot be validated by the same user who marked task as mapped or badimagery" ) tasks_to_lock.append(task) diff --git a/frontend/src/components/projectDetail/index.js b/frontend/src/components/projectDetail/index.js index 7ef9169bd0..2379622320 100644 --- a/frontend/src/components/projectDetail/index.js +++ b/frontend/src/components/projectDetail/index.js @@ -41,7 +41,7 @@ const ProjectDetailTypeBar = (props) => { -
+

@@ -237,11 +237,13 @@ export const ProjectDetail = (props) => { }} />

- {props.project.organisationName} + {props.project.organisationLogo && ( + {props.project.organisationName} + )} )} {!props.project.organisationName && props.project.author && ( diff --git a/frontend/src/components/taskSelection/permissionErrorModal.js b/frontend/src/components/taskSelection/permissionErrorModal.js index 2ebca18872..688448f5de 100644 --- a/frontend/src/components/taskSelection/permissionErrorModal.js +++ b/frontend/src/components/taskSelection/permissionErrorModal.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { redirectTo } from '@reach/router'; +import { navigate } from '@reach/router'; import { FormattedMessage } from 'react-intl'; import messages from './messages'; @@ -30,8 +30,8 @@ export function UserPermissionErrorContent({ project, userLevel, close }: Object {userPermissionError === 'userIsNotMappingTeamMember' && (
{project.teams - .filter(team => team.role === 'MAPPER') - .map(team => ( + .filter((team) => team.role === 'MAPPER') + .map((team) => ( ))}
@@ -39,14 +39,14 @@ export function UserPermissionErrorContent({ project, userLevel, close }: Object {userPermissionError === 'userIsNotValidationTeamMember' && (
{project.teams - .filter(team => team.role === 'VALIDATOR') - .map(team => ( + .filter((team) => team.role === 'VALIDATOR') + .map((team) => ( ))}
)}
-
diff --git a/frontend/src/views/projectEdit.js b/frontend/src/views/projectEdit.js index 637034e3cb..0a0484ac6e 100644 --- a/frontend/src/views/projectEdit.js +++ b/frontend/src/views/projectEdit.js @@ -184,7 +184,10 @@ export function ProjectEdit({ id }) { const saveChanges = () => { const updateProject = () => { pushToLocalJSONAPI(`projects/${id}/`, JSON.stringify(projectInfo), token, 'PATCH') - .then((res) => setSuccess(true)) + .then((res) => { + setSuccess(true); + setError(null); + }) .catch((e) => setError('SERVER')); }; diff --git a/manage.py b/manage.py index 8d2802b309..f27eefab01 100644 --- a/manage.py +++ b/manage.py @@ -19,7 +19,7 @@ from sqlalchemy import func import atexit -from apscheduler.scheduler import Scheduler +from apscheduler.schedulers.background import BackgroundScheduler # Load configuration from file into environment @@ -54,14 +54,8 @@ # Enable db migrations to be run via the command line manager.add_command("db", MigrateCommand) -# Setup a background cron job -cron = Scheduler(daemon=True) -# Initiate the background thread -cron.start() -application.logger.debug("Initiated background thread to auto unlock tasks") # Job runs once every 2 hours -@cron.interval_schedule(hours=2) @manager.command def auto_unlock_tasks(): with application.app_context(): @@ -81,6 +75,13 @@ def auto_unlock_tasks(): Task.auto_unlock_tasks(project_id) +# Setup a background cron job +cron = BackgroundScheduler(daemon=True) +# Initiate the background thread +cron.add_job(auto_unlock_tasks, "interval", hours=2) +cron.start() +application.logger.debug("Initiated background thread to auto unlock tasks") + # Shutdown your cron thread when the application is stopped atexit.register(lambda: cron.shutdown(wait=False)) diff --git a/migrations/versions/55790eeec3bf_.py b/migrations/versions/55790eeec3bf_.py new file mode 100644 index 0000000000..98b0ab7fc0 --- /dev/null +++ b/migrations/versions/55790eeec3bf_.py @@ -0,0 +1,71 @@ +"""empty message + +Revision ID: 55790eeec3bf +Revises: 2d6a7ad4e1eb +Create Date: 2020-05-12 18:43:37.666725 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "55790eeec3bf" +down_revision = "2d6a7ad4e1eb" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "notifications", "date", existing_type=postgresql.TIMESTAMP(), nullable=True + ) + op.alter_column( + "notifications", "unread_count", existing_type=sa.INTEGER(), nullable=True + ) + op.alter_column( + "notifications", "user_id", existing_type=sa.BIGINT(), nullable=True + ) + op.create_index( + op.f("ix_notifications_user_id"), "notifications", ["user_id"], unique=False + ) + op.drop_index("idx_notifications_user_id", table_name="notifications") + op.create_index( + "idx_task_history_project_id_user_id", + "task_history", + ["user_id", "project_id"], + unique=False, + ) + op.create_index( + op.f("ix_task_history_user_id"), "task_history", ["user_id"], unique=False + ) + op.create_index(op.f("ix_tasks_locked_by"), "tasks", ["locked_by"], unique=False) + op.create_index(op.f("ix_tasks_mapped_by"), "tasks", ["mapped_by"], unique=False) + op.create_index( + op.f("ix_tasks_validated_by"), "tasks", ["validated_by"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_tasks_validated_by"), table_name="tasks") + op.drop_index(op.f("ix_tasks_mapped_by"), table_name="tasks") + op.drop_index(op.f("ix_tasks_locked_by"), table_name="tasks") + op.drop_index(op.f("ix_task_history_user_id"), table_name="task_history") + op.drop_index("idx_task_history_project_id_user_id", table_name="task_history") + op.create_index( + "idx_notifications_user_id", "notifications", ["user_id"], unique=False + ) + op.drop_index(op.f("ix_notifications_user_id"), table_name="notifications") + op.alter_column( + "notifications", "user_id", existing_type=sa.BIGINT(), nullable=False + ) + op.alter_column( + "notifications", "unread_count", existing_type=sa.INTEGER(), nullable=False + ) + op.alter_column( + "notifications", "date", existing_type=postgresql.TIMESTAMP(), nullable=False + ) + # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index c52f024393..076769c71c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ alembic==1.4.2 aniso8601==8.0.0 -appdirs==1.4.3 -apscheduler==2.1.2 +appdirs==1.4.4 +apscheduler==3.6.3 attrs==19.3.0 black==19.10b0 bleach==3.1.5 @@ -11,7 +11,7 @@ chardet==3.0.4 Click==7.1.2 coverage==5.1 entrypoints==0.3 -flake8==3.7.9 +flake8==3.8.1 Flask==1.1.2 Flask-Cors==3.0.8 Flask-HTTPAuth==4.0.0 @@ -38,8 +38,8 @@ newrelic==5.12.0.140 nose==1.3.7 oauthlib==2.0.2 psycopg2==2.8.5 -pycodestyle==2.5.0 -pyflakes==2.1.1 +pycodestyle==2.6.0 +pyflakes==2.2.0 pyparsing==2.4.7 pyproj==2.6.1.post1 python-dateutil==2.8.1 diff --git a/scripts/aws/cloudformation/tasking-manager.template.js b/scripts/aws/cloudformation/tasking-manager.template.js index 293abd9749..2fc0ec91fb 100644 --- a/scripts/aws/cloudformation/tasking-manager.template.js +++ b/scripts/aws/cloudformation/tasking-manager.template.js @@ -669,6 +669,7 @@ const Resources = { }, Headers: ['Accept', 'Referer'] }, + Compress: true, TargetOriginId: cf.join('-', [cf.stackName, 'react-app']), ViewerProtocolPolicy: "redirect-to-https" },