def user_memberships_by_username_put(username) -> Response: """ Update the team and group memberships of a user. :param username: Username that uniquely identifies a user. :return: A response object for the PUT API request. """ jwt_claims: dict = get_claims(request) jwt_username = jwt_claims.get('sub') if username == jwt_username: current_app.logger.info( f'User {jwt_username} is updating their memberships.') else: current_app.logger.info( f'User {jwt_username} is not authorized to update the memberships of user {username}.' ) response = jsonify({ 'self': f'/v2/users/memberships/{username}', 'updated': False, 'error': f'User {jwt_username} is not authorized to update the memberships of user {username}.' }) response.status_code = 400 return response membership_data: dict = request.get_json() teams_joined = membership_data.get('teams_joined') teams_left = membership_data.get('teams_left') groups_joined = membership_data.get('groups_joined') groups_left = membership_data.get('groups_left') committed: bool = False try: committed = TeamMemberDao.update_user_memberships( username, teams_joined, teams_left, groups_joined, groups_left) except SQLAlchemyError as e: current_app.logger.error(str(e)) if committed: response = jsonify({ 'self': f'/v2/users/memberships/{username}', 'updated': True, }) response.status_code = 201 return response else: response = jsonify({ 'self': f'/v2/users/memberships/{username}', 'updated': False, 'error': "failed to update the user's memberships" }) response.status_code = 500 return response
def user_teams_by_username_get(username) -> Response: """ Get the team memberships for a user. :param username: Username that uniquely identifies a user. :return: A response object for the GET API request. """ teams: ResultProxy = TeamMemberDao.get_user_teams(username=username) team_list = [] for team in teams: team_list.append({ 'team_name': team['team_name'], 'title': team['title'], 'status': team['status'], 'user': team['user'] }) response = jsonify({ 'self': f'/v2/users/teams/{username}', 'teams': team_list }) response.status_code = 200 return response
def user_memberships_by_username_get(username) -> Response: """ Get the team and group memberships for a user. :param username: Username that uniquely identifies a user. :return: A response object for the GET API request. """ teams: ResultProxy = TeamMemberDao.get_user_teams(username=username) membership_list = [] for team in teams: groups: ResultProxy = GroupMemberDao.get_user_groups_in_team( username=username, team_name=team['team_name']) membership_list.append({ 'team_name': team['team_name'], 'title': team['title'], 'status': team['status'], 'user': team['user'], 'groups': [{ 'group_name': group['group_name'], 'group_title': group['group_title'], 'group_id': group['group_id'], 'status': group['status'], 'user': group['user'] } for group in groups] }) response = jsonify({ 'self': f'/v2/users/memberships/{username}', 'memberships': membership_list }) response.status_code = 200 return response
def team_members_by_team_name_get(team_name) -> Response: """ Get the members of a team based on the team name. :param team_name: Unique name of a team. :return: A response object for the GET API request. """ team_members_result: ResultProxy = TeamMemberDao.get_team_members(team_name=team_name) if team_members_result is None or team_members_result.rowcount == 0: response = jsonify({ 'self': f'/v2/teams/members/{team_name}', 'team': f'/v2/teams/{team_name}', 'team_members': None, 'error': 'the team does not exist or it has no members' }) response.status_code = 400 return response else: team_members_list = [{ 'username': member.username, 'first': member.first, 'last': member.last, 'member_since': member.member_since, 'user': member.user, 'status': member.status, 'deleted': member.deleted } for member in team_members_result] response = jsonify({ 'self': f'/v2/teams/members/{team_name}', 'group': f'/v2/teams/{team_name}', 'team_members': team_members_list }) response.status_code = 200 return response
def user_post() -> Response: """ Create a new user. :return: A response object for the POST API request. """ user_data: dict = request.get_json() def create_validation_error_response(message: str): """ Reusable 400 HTTP error response for the users POST request. :param message: Message sent in the response JSON's 'error' field. :return: An HTTP response object. """ error_response = jsonify({ 'self': f'/v2/users', 'added': False, 'user': None, 'error': message }) error_response.status_code = 400 return error_response if user_data is None: return create_validation_error_response( "The request body isn't populated.") user_to_add = User(user_data) if None in [ user_to_add.username, user_to_add.first, user_to_add.last, user_to_add.password, user_to_add.activation_code, user_to_add.email ]: return create_validation_error_response( "'username', 'first', 'last', 'email', 'password', and 'activation_code' are required fields" ) if len(user_to_add.password) < 6: return create_validation_error_response( "Password must contain at least 6 characters.") username_pattern = re.compile('^[a-zA-Z0-9]+$') if not username_pattern.match(user_to_add.username): return create_validation_error_response( "Username can only contain Roman characters and numbers.") email_pattern = re.compile( '^(([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+\\.([a-zA-Z])+([a-zA-Z])+)?$') if not email_pattern.match(user_to_add.email): return create_validation_error_response( "The email address is invalid.") # Passwords must be hashed before stored in the database password = user_to_add.password hashed_password = flask_bcrypt.generate_password_hash(password).decode( 'utf-8') user_to_add.password = hashed_password activation_code_count = CodeDao.get_code_count( activation_code=user_to_add.activation_code) if activation_code_count == 1: now = datetime.now() user_to_add.member_since = now.date() user_to_add.created_date = now user_to_add.last_signin = now user_to_add.created_app = 'saints-xctf-api' user_to_add.created_user = None user_to_add.modified_date = None user_to_add.modified_app = None user_to_add.modified_user = None user_to_add.deleted_date = None user_to_add.deleted_app = None user_to_add.deleted_user = None user_to_add.deleted = False # First, add the user since its activation code is valid. UserDao.add_user(user_to_add) # Second, set the initial team and group for the user. code: Code = ActivationCodeDao.get_activation_code( user_to_add.activation_code) initial_group_id = int(code.group_id) initial_group: Group = GroupDao.get_group_by_id(initial_group_id) initial_team: Team = TeamDao.get_team_by_group_id(initial_group_id) TeamMemberDao.set_initial_membership( username=user_to_add.username, team_name=initial_team.name, group_id=initial_group_id, group_name=initial_group.group_name) # Third, remove the activation code so it cant be used again. CodeDao.remove_code(code) added_user = UserDao.get_user_by_username(user_to_add.username) if added_user is None: response = jsonify({ 'self': '/v2/users', 'added': False, 'user': None, 'error': 'An unexpected error occurred creating the user.' }) response.status_code = 500 return response else: response = jsonify({ 'self': '/v2/users', 'added': True, 'user': UserData(added_user).__dict__, 'new_user': f'/v2/users/{added_user.username}' }) response.status_code = 201 return response else: current_app.logger.error( 'Failed to create new User: The Activation Code does not exist.') response = jsonify({ 'self': '/v2/users', 'added': False, 'user': None, 'error': 'The activation code is invalid or expired.' }) response.status_code = 400 return response
def group_members_by_group_id_and_username_delete(group_id: str, username: str) -> Response: """ Soft delete a group membership. The membership is identified by a group's identifier and a user's username. :param group_id: Unique id which identifies a group within a team. :param username: Unique name for a user. :return: A response object for the DELETE API request. """ jwt_claims: dict = get_claims(request) jwt_username = jwt_claims.get('sub') group_member: GroupMember = GroupMemberDao.get_group_member( group_id=int(group_id), username=jwt_username) if group_member is not None and group_member.user == 'admin' and group_member.status == 'accepted': current_app.logger.info( f'Admin user {jwt_username} is deleting the group membership for user {username} in group with id ' f'{group_id}.') else: current_app.logger.info( f'User {jwt_username} is not authorized to delete the group membership for user {username} in group with ' f'id {group_id}.') response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'deleted': False, 'error': f'User {jwt_username} is not authorized to delete the group membership for user {username} in ' f'group with id {group_id}.' }) response.status_code = 400 return response membership_deleted = GroupMemberDao.soft_delete_group_member( int(group_id), username) if membership_deleted: team: Team = TeamDao.get_team_by_group_id(int(group_id)) user_groups: ResultProxy = GroupMemberDao.get_user_groups_in_team( username, team.name) # If the user has no more group memberships in this team, remove them from the team. if user_groups.rowcount == 0: TeamMemberDao.update_user_memberships(username=username, teams_joined=[], teams_left=[team.name], groups_joined=[], groups_left=[]) response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'deleted': True, }) response.status_code = 204 return response else: response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'deleted': False, 'error': 'Failed to delete the group membership.' }) response.status_code = 500 return response
def group_members_by_group_id_and_username_put(group_id: str, username: str) -> Response: """ Update a group membership. The membership is identified by a group's identifier and a user's username. :param group_id: Unique id which identifies a group within a team. :param username: Unique name for a user. :return: A response object for the PUT API request. """ jwt_claims: dict = get_claims(request) jwt_username = jwt_claims.get('sub') group_member: GroupMember = GroupMemberDao.get_group_member( group_id=int(group_id), username=jwt_username) if group_member is not None and group_member.user == 'admin' and group_member.status == 'accepted': current_app.logger.info( f'Admin user {jwt_username} is updating the group membership for user {username} in group with id ' f'{group_id}.') else: current_app.logger.info( f'User {jwt_username} is not authorized to update the group membership for user {username} in group with ' f'id {group_id}.') response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'updated': False, 'group_member': None, 'error': f'User {jwt_username} is not authorized to update the group membership for user {username} in ' f'group with id {group_id}.' }) response.status_code = 400 return response group_member_data: dict = request.get_json() status = group_member_data.get('status') user = group_member_data.get('user') is_updated = GroupMemberDao.update_group_member(int(group_id), username, status, user) if is_updated: team: Team = TeamDao.get_team_by_group_id(int(group_id)) team_membership: ResultProxy = TeamMemberDao.get_user_team_membership( username=username, team_name=team.name) if team_membership.rowcount > 0: for membership in team_membership: if membership.user != 'accepted': TeamMemberDao.accept_user_team_membership( username=username, team_name=team.name, updating_username=jwt_username) updated_group_member = GroupMemberDao.get_group_member( int(group_id), username) updated_group_member_dict: dict = GroupMemberData( updated_group_member).__dict__ response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'updated': True, 'group_member': updated_group_member_dict }) response.status_code = 200 return response else: response = jsonify({ 'self': f'/v2/groups/members/{group_id}/{username}', 'updated': False, 'group_member': None, 'error': 'The group membership failed to update.' }) response.status_code = 500 return response