def login_user(osm_user_details, redirect_to, user_element='user') -> str: """ Generates authentication details for user, creating in DB if user is unknown to us :param osm_user_details: XML response from OSM :param redirect_to: Route to redirect user to, from callback url :param user_element: Exists for unit testing :raises AuthServiceError :returns Authorized URL with authentication details in query string """ osm_user = osm_user_details.find(user_element) if osm_user is None: raise AuthServiceError('User element not found in OSM response') osm_id = int(osm_user.attrib['id']) username = osm_user.attrib['display_name'] try: UserService.get_user_by_id(osm_id) except NotFound: # User not found, so must be new user changesets = osm_user.find('changesets') changeset_count = int(changesets.attrib['count']) new_user = UserService.register_user(osm_id, username, changeset_count) MessageService.send_welcome_message(new_user) session_token = AuthenticationService.generate_session_token_for_user( osm_id) authorized_url = AuthenticationService.generate_authorized_url( username, session_token, redirect_to) return authorized_url
def test_get_user_by_id_returns_not_found_exception(self): """ Get a canned user from the DB """ if self.skip_tests: return # Arrange test_user_id = 9999999999999999999 # Act with self.assertRaises(NotFound): UserService.get_user_by_id(test_user_id)
def set_counters_after_undo(project_id: int, user_id: int, current_state: TaskStatus, undo_state: TaskStatus): """ Resets counters after a user undoes their task""" project = ProjectService.get_project_by_id(project_id) user = UserService.get_user_by_id(user_id) # This is best endeavours to reset the stats and may have missed some edge cases, hopefully majority of # cases will be Mapped to Ready if current_state == TaskStatus.MAPPED and undo_state == TaskStatus.READY: project.tasks_mapped -= 1 user.tasks_mapped -= 1 if current_state == TaskStatus.MAPPED and undo_state == TaskStatus.INVALIDATED: user.tasks_mapped -= 1 project.tasks_mapped -= 1 elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.READY: project.tasks_bad_imagery -= 1 elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.MAPPED: project.tasks_mapped += 1 project.tasks_bad_imagery -= 1 elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.INVALIDATED: project.tasks_bad_imagery -= 1 elif current_state == TaskStatus.INVALIDATED and undo_state == TaskStatus.MAPPED: user.tasks_invalidated -= 1 project.tasks_mapped += 1 elif current_state == TaskStatus.INVALIDATED and undo_state == TaskStatus.VALIDATED: user.tasks_invalidated -= 1 project.tasks_validated += 1 elif current_state == TaskStatus.VALIDATED and undo_state == TaskStatus.MAPPED: user.tasks_validated -= 1 project.tasks_validated -= 1 elif current_state == TaskStatus.VALIDATED and undo_state == TaskStatus.BADIMAGERY: user.tasks_validated -= 1 project.tasks_validated -= 1
def update_stats_after_task_state_change(project_id: int, user_id: int, new_state: TaskStatus, task_id: int): """ Update stats when a task has had a state change """ if new_state in [ TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION, TaskStatus.LOCKED_FOR_MAPPING ]: return # No stats to record for these states project = ProjectService.get_project_by_id(project_id) user = UserService.get_user_by_id(user_id) if new_state == TaskStatus.MAPPED: StatsService._set_counters_after_mapping(project, user) elif new_state == TaskStatus.INVALIDATED: StatsService._set_counters_after_invalidated( task_id, project, user) elif new_state == TaskStatus.VALIDATED: StatsService._set_counters_after_validated(project, user) elif new_state == TaskStatus.BADIMAGERY: StatsService._set_counters_after_bad_imagery(project) UserService.upsert_mapped_projects(user_id, project_id) project.last_updated = timestamp() # Transaction will be saved when task is saved return project, user
def update_stats_after_task_state_change( project_id: int, user_id: int, last_state: TaskStatus, new_state: TaskStatus, action="change", ): """ Update stats when a task has had a state change """ if new_state in [ TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION, TaskStatus.LOCKED_FOR_MAPPING, ]: return # No stats to record for these states project = ProjectService.get_project_by_id(project_id) user = UserService.get_user_by_id(user_id) project, user = StatsService._update_tasks_stats( project, user, last_state, new_state, action) UserService.upsert_mapped_projects(user_id, project_id) project.last_updated = timestamp() # Transaction will be saved when task is saved return project, user
def is_valid_token(token, token_expiry): """ Validates if the supplied token is valid, and hasn't expired. :param token: Token to check :param token_expiry: When the token expires :return: True if token is valid """ serializer = URLSafeTimedSerializer(current_app.secret_key) try: tokenised_user_id = serializer.loads(token, max_age=token_expiry) dmis.authenticated_user_id = tokenised_user_id except SignatureExpired: current_app.logger.debug('Token has expired') return False except BadSignature: current_app.logger.debug('Bad Token Signature') return False # Ensure user is Admin priv to access admin only endpoints if dmis.is_admin_only_resource: user = UserService.get_user_by_id(tokenised_user_id) if UserRole(user.role) != UserRole.ADMIN: current_app.logger.debug(f'User {tokenised_user_id} is not an Admin {request.base_url}') return False return True
def send_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """ Sends supplied message to all contributors on specified project. Message all contributors can take over a minute to run, so this method is expected to be called on its own thread """ app = create_app( ) # Because message-all run on background thread it needs it's own app context with app.app_context(): contributors = Message.get_all_contributors(project_id) project_link = MessageService.get_project_link(project_id) message_dto.message = f'{project_link}<br/><br/>' + message_dto.message # Append project link to end of message msg_count = 0 for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.save() user = UserService.get_user_by_id(contributor[0]) SMTPService.send_email_alert(user.email_address, user.username) msg_count += 1 if msg_count == 10: time.sleep( 0.5 ) # Sleep for 0.5 seconds to avoid hitting AWS rate limits every 10 messages msg_count = 0
def send_message_after_validation(status: int, validated_by: int, mapped_by: int, task_id: int, project_id: int): """ Sends mapper a notification after their task has been marked valid or invalid """ if validated_by == mapped_by: return # No need to send a message to yourself user = UserService.get_user_by_id(mapped_by) if user.validation_message == False: return # No need to send validation message text_template = get_template('invalidation_message_en.txt' if status == TaskStatus.INVALIDATED \ else 'validation_message_en.txt') status_text = 'marked invalid' if status == TaskStatus.INVALIDATED else 'validated' task_link = MessageService.get_task_link(project_id, task_id) text_template = text_template.replace('[USERNAME]', user.username) text_template = text_template.replace('[TASK_LINK]', task_link) validation_message = Message() validation_message.from_user_id = validated_by validation_message.to_user_id = mapped_by validation_message.subject = f'Your mapping in Project {project_id} on {task_link} has just been {status_text}' validation_message.message = text_template validation_message.add_message() SMTPService.send_email_alert(user.email_address, user.username)
def send_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """ Sends supplied message to all contributors on specified project """ contributors = Message.get_all_contributors(project_id) for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.save() user = UserService.get_user_by_id(contributor[0]) SMTPService.send_email_alert(user.email_address, user.username)
def test_get_user_by_id(self): """ Get a canned user from the DB """ if self.skip_tests: return # Arrange # Act found_user = UserService.get_user_by_id(TEST_USER_ID) self.assertIsNotNone(found_user, f'Did not find user by ID {TEST_USER_ID}')
def send_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """ Sends supplied message to all contributors on specified project """ contributors = Message.get_all_contributors(project_id) project_link = MessageService.get_project_link(project_id) message_dto.message = f'{project_link}<br/><br/>' + message_dto.message # Append project link to end of message for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.save() user = UserService.get_user_by_id(contributor[0]) SMTPService.send_email_alert(user.email_address, user.username)
def login_user(user_id: int) -> SessionDTO: """ Method gets relevant user details for a successfully authenticated customer and generates a session token that can be used in place of username and password for the remainder of the user session :param user_id: The account username """ user = UserService.get_user_by_id(user_id) session = SessionDTO() session.username = user.username session.role = UserRole(user.role).name session.token = AuthenticationService.generate_timed_token(user_id) return session
def send_message_after_validation(validated_by: int, mapped_by: int, task_id: int, project_id: int): """ Sends mapper a thank you, after their task has been marked as valid """ if validated_by == mapped_by: return # No need to send a thankyou to yourself text_template = get_template('validation_message_en.txt') task_link = MessageService.get_task_link(project_id, task_id) user = UserService.get_user_by_id(mapped_by) text_template = text_template.replace('[USERNAME]', user.username) text_template = text_template.replace('[TASK_LINK]', task_link) validation_message = Message() validation_message.from_user_id = validated_by validation_message.to_user_id = mapped_by validation_message.subject = f'Your mapping on {task_link} has just been validated' validation_message.message = text_template validation_message.add_message() SMTPService.send_email_alert(user.email_address, user.username)
def send_message_to_all_contributors(project_id: int, message_dto: MessageDTO): """ Sends supplied message to all contributors on specified project """ contributors = Message.get_all_contributors(project_id) project_link = MessageService.get_project_link(project_id) message_dto.message = f'{project_link}<br/><br/>' + message_dto.message # Append project link to end of message msg_count = 0 for contributor in contributors: message = Message.from_dto(contributor[0], message_dto) message.save() user = UserService.get_user_by_id(contributor[0]) SMTPService.send_email_alert(user.email_address, user.username) msg_count += 1 if msg_count == 5: time.sleep( 0.5 ) # Sleep for 0.5 seconds to avoid hitting AWS rate limits every 5 messages msg_count = 0
def transfer_project_to(project_id: int, transfering_user_id: int, username: str): """ Transfers project from old owner (transfering_user_id) to new owner (username) """ project = Project.get(project_id) transfering_user = UserService.get_user_by_id(transfering_user_id) new_owner = UserService.get_user_by_username(username) is_pm = new_owner.role in (UserRole.PROJECT_MANAGER.value, UserRole.ADMIN.value) if not is_pm: raise Exception("User must be a project manager") if transfering_user.role == UserRole.PROJECT_MANAGER.value: if project.author_id == transfering_user_id: project.author_id = new_owner.id project.save() else: raise Exception("Invalid owner_id") elif transfering_user.role == UserRole.ADMIN.value: project.author_id = new_owner.id project.save() else: raise Exception("Normal users cannot transfer projects")
def resend_email_validation(user_id: int): """ Resends the email validation email to the logged in user """ user = UserService.get_user_by_id(user_id) SMTPService.send_verification_email(user.email_address, user.username)