Exemplo n.º 1
0
def user_lookup_by_username_get(username: str) -> Response:
    """
    Check if a user exists based on its username or email.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the GET API request.
    """
    existing_user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if existing_user is None:
        email = username
        existing_user: User = UserDao.get_user_by_email(email=email)

    # If the user still can't be found, return with an error code
    if existing_user is None:
        response = jsonify({
            'self':
            f'/v2/users/lookup/{username}',
            'exists':
            False,
            'error':
            'There is no user with this username or email.'
        })
        response.status_code = 400
        return response
    else:
        response = jsonify({
            'self': f'/v2/users/lookup/{username}',
            'exists': True
        })
        response.status_code = 200
        return response
Exemplo n.º 2
0
def user_statistics_by_username_get(username) -> Response:
    """
    Get exercise statistics for a user.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the GET API request.
    """
    user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if user is None:
        email = username
        user: User = UserDao.get_user_by_email(email=email)

    # If the user still can't be found, return with an error code
    if user is None:
        response = jsonify({
            'self': f'/v2/users/statistics/{username}',
            'stats': None,
            'error': 'there is no user with this username'
        })
        response.status_code = 400
        return response

    response = jsonify({
        'self': f'/v2/users/statistics/{username}',
        'stats': compile_user_statistics(user, username)
    })
    response.status_code = 200
    return response
Exemplo n.º 3
0
def user_by_username_get(username) -> Response:
    """
    Retrieve a user based on its username.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the GET API request.
    """
    user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if user is None:
        email = username
        user: User = UserDao.get_user_by_email(email=email)

    # If the user still can't be found, return with an error code
    if user is None:
        response = jsonify({
            'self': f'/v2/users/{username}',
            'user': None,
            'error': 'there is no user with this username'
        })
        response.status_code = 400
        return response
    else:
        user_dict: dict = UserData(user).__dict__

        if user_dict.get('member_since') is not None:
            user_dict['member_since'] = str(user_dict['member_since'])
        if user_dict.get('last_signin') is not None:
            user_dict['last_signin'] = str(user_dict['last_signin'])

        if user_dict.get('profilepic') is not None:
            try:
                user_dict['profilepic'] = user_dict['profilepic'].decode(
                    'utf-8')
            except AttributeError:
                pass

        response = jsonify({
            'self': f'/v2/users/{username}',
            'user': user_dict
        })
        response.status_code = 200
        return response
Exemplo n.º 4
0
def user_snapshot_by_username_get(username) -> Response:
    """
    Get a snapshot with information about a user with a given username.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the GET API request.
    """
    user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if user is None:
        email = username
        user: User = UserDao.get_user_by_email(email=email)

    # If the user still can't be found, return with an error code
    if user is None:
        response = jsonify({
            'self': f'/v2/users/snapshot/{username}',
            'user': None,
            'error': 'there is no user with this username'
        })
        response.status_code = 400
        return response
    else:
        user_dict: dict = UserData(user).__dict__

        if user_dict.get('member_since') is not None:
            user_dict['member_since'] = str(user_dict['member_since'])
        if user_dict.get('last_signin') is not None:
            user_dict['last_signin'] = str(user_dict['last_signin'])

        if user_dict.get('profilepic') is not None:
            try:
                user_dict['profilepic'] = user_dict['profilepic'].decode(
                    'utf-8')
            except AttributeError:
                pass

        username = user_dict['username']
        groups: ResultProxy = GroupMemberDao.get_user_groups(username=username)
        group_list = []

        for group in groups:
            group_dict = {
                'id': group['id'],
                'group_name': group['group_name'],
                'group_title': group['group_title'],
                'status': group['status'],
                'user': group['user']
            }
            newest_log: Column = GroupDao.get_newest_log_date(
                group['group_name'])
            group_dict['newest_log'] = newest_log['newest']

            newest_message = GroupDao.get_newest_message_date(
                group['group_name'])
            group_dict['newest_message'] = newest_message['newest']

            group_list.append(group_dict)

        user_dict['groups'] = group_list

        forgot_password_codes: ResultProxy = ForgotPasswordDao.get_forgot_password_codes(
            username=username)

        forgot_password_list = []
        for forgot_password_code in forgot_password_codes:
            forgot_password_list.append({
                'forgot_code':
                forgot_password_code['forgot_code'],
                'username':
                forgot_password_code['username'],
                'expires':
                forgot_password_code['expires'],
                'deleted':
                forgot_password_code['deleted'],
            })

        user_dict['forgotpassword'] = forgot_password_list

        flairs: List[Flair] = FlairDao.get_flair_by_username(username=username)
        flair_dicts = []

        for flair in flairs:
            flair_dicts.append(FlairData(flair).__dict__)

        user_dict['flair'] = flair_dicts

        notifications: ResultProxy = NotificationDao.get_notification_by_username(
            username=username)

        notification_dicts = []
        for notification in notifications:
            notification_dicts.append({
                'notification_id':
                notification['notification_id'],
                'username':
                notification['username'],
                'time':
                notification['time'],
                'link':
                notification['link'],
                'viewed':
                notification['viewed'],
                'description':
                notification['description']
            })

        user_dict['notifications'] = notification_dicts

        stats = compile_user_statistics(user, username)
        user_dict['statistics'] = stats

        response = jsonify({
            'self': f'/v2/users/snapshot/{username}',
            'user': user_dict
        })
        response.status_code = 200
        return response
Exemplo n.º 5
0
def user_by_username_soft_delete(username) -> Response:
    """
    Soft delete an existing user with a given username.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the DELETE 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 soft deleting their user.')
    else:
        current_app.logger.info(
            f'User {jwt_username} is not authorized to soft delete user {username}.'
        )
        response = jsonify({
            'self':
            f'/v2/users/soft/{username}',
            'deleted':
            False,
            'error':
            f'User {jwt_username} is not authorized to soft delete user {username}.'
        })
        response.status_code = 400
        return response

    existing_user: User = UserDao.get_user_by_username(username=username)

    if existing_user is None:
        response = jsonify({
            'self':
            f'/v2/users/soft/{username}',
            'deleted':
            False,
            'error':
            'there is no existing user with this username'
        })
        response.status_code = 400
        return response

    # Update the user model to reflect the soft delete
    existing_user.deleted = True
    existing_user.deleted_date = datetime.now()
    existing_user.deleted_app = 'saints-xctf-api'
    existing_user.modified_date = datetime.now()
    existing_user.modified_app = 'saints-xctf-api'

    is_deleted: bool = UserDao.soft_delete_user(existing_user)

    if is_deleted:
        response = jsonify({
            'self': f'/v2/users/soft/{username}',
            'deleted': True,
        })
        response.status_code = 204
        return response
    else:
        response = jsonify({
            'self': f'/v2/users/soft/{username}',
            'deleted': False,
            'error': 'failed to soft delete the user'
        })
        response.status_code = 500
        return response
Exemplo n.º 6
0
def user_by_username_put(username) -> Response:
    """
    Update an existing user with a given username.
    :param username: Username that uniquely identifies a user.
    :return: A response object for the PUT API request.
    """
    old_user: User = UserDao.get_user_by_username(username=username)

    if old_user is None:
        response = jsonify({
            'self':
            f'/v2/users/{username}',
            'updated':
            False,
            'user':
            None,
            'error':
            'there is no existing user with this username'
        })
        response.status_code = 400
        return response

    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 user details.')
    else:
        current_app.logger.info(
            f'User {jwt_username} is not authorized to update user {username}.'
        )
        response = jsonify({
            'self':
            f'/v2/users/{username}',
            'updated':
            False,
            'user':
            None,
            'error':
            f'User {jwt_username} is not authorized to update user {username}.'
        })
        response.status_code = 400
        return response

    user_data: dict = request.get_json()
    new_user = User(user_data)

    if new_user != old_user:
        is_updated = UserDao.update_user(username, new_user)

        if is_updated:
            updated_user = UserDao.get_user_by_username(username)

            updated_user_dict: dict = UserData(updated_user).__dict__

            if updated_user_dict.get('profilepic') is not None:
                try:
                    updated_user_dict['profilepic'] = updated_user_dict[
                        'profilepic'].decode('utf-8')
                except AttributeError:
                    pass

            response = jsonify({
                'self': f'/v2/users/{username}',
                'updated': True,
                'user': updated_user_dict
            })
            response.status_code = 200
            return response
        else:
            response = jsonify({
                'self': f'/v2/users/{username}',
                'updated': False,
                'user': None,
                'error': 'the user failed to update'
            })
            response.status_code = 500
            return response
    else:
        response = jsonify({
            'self':
            f'/v2/users/{username}',
            'updated':
            False,
            'user':
            None,
            'error':
            'the user submitted is equal to the existing user with the same username'
        })
        response.status_code = 400
        return response
Exemplo n.º 7
0
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
Exemplo n.º 8
0
def forgot_password_get(username) -> Response:
    """
    Retrieve an existing forgot password code for a specific user.
    :param username: Uniquely identifies a user.
    :return: JSON with the resulting Forgot Password object and relevant metadata.
    """
    user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if user is None:
        email = username
        user: User = UserDao.get_user_by_email(email=email)

    if user is None:
        response = jsonify({
            'self':
            f'/v2/forgot_password/{username}',
            'forgot_password_codes': [],
            'error':
            'There is no user associated with this username/email.'
        })
        response.status_code = 400
        return response

    jwt_claims: dict = get_claims(request)
    jwt_username = jwt_claims.get('sub')

    if user.username == jwt_username:
        current_app.logger.info(
            f'User {jwt_username} is accessing their forgot password codes.')
    else:
        current_app.logger.info(
            f'User {jwt_username} is not authorized to access forgot password codes for user {user.username}.'
        )
        response = jsonify({
            'self':
            f'/v2/forgot_password/{username}',
            'created':
            False,
            'error':
            f'User {jwt_username} is not authorized to access forgot password codes for user {user.username}.'
        })
        response.status_code = 400
        return response

    forgot_password_codes: ResultProxy = ForgotPasswordDao.get_forgot_password_codes(
        username=user.username)

    if forgot_password_codes is None:
        response = jsonify({
            'self': f'/v2/forgot_password/{username}',
            'forgot_password_codes': [],
            'error': 'This user has no forgot password codes.'
        })
        response.status_code = 400
        return response
    else:
        forgot_password_list = []
        for code in forgot_password_codes:
            fpw = ForgotPasswordData(None)
            fpw.forgot_code = code[0]
            fpw.username = code[1]
            fpw.expires = code[2]
            fpw.deleted = code[3]
            forgot_password_list.append(fpw.__dict__)

        response = jsonify({
            'self': f'/v2/forgot_password/{username}',
            'forgot_password_codes': forgot_password_list,
        })
        response.status_code = 200
        return response
Exemplo n.º 9
0
def forgot_password_post(username) -> Response:
    """
    Create a new forgot password code for a specific user.
    :param username: Uniquely identifies a user.
    :return: JSON with the resulting Forgot Password object and relevant metadata.
    """
    user: User = UserDao.get_user_by_username(username=username)

    # If the user cant be found, try searching the email column in the database
    if user is None:
        email = username
        user: User = UserDao.get_user_by_email(email=email)

    if user is None:
        response = jsonify({
            'self':
            f'/v2/forgot_password/{username}',
            'created':
            False,
            'error':
            'There is no user associated with this username/email.'
        })
        response.status_code = 400
        return response

    code = generate_code(length=8)
    expires = datetime.now() + timedelta(hours=2)

    new_forgot_password = ForgotPassword({
        'forgot_code': code,
        'username': user.username,
        'expires': expires
    })

    new_forgot_password.created_date = datetime.now()
    new_forgot_password.created_app = 'saints-xctf-api'
    new_forgot_password.created_user = None
    new_forgot_password.modified_date = None
    new_forgot_password.modified_app = None
    new_forgot_password.modified_user = None
    new_forgot_password.deleted_date = None
    new_forgot_password.deleted_app = None
    new_forgot_password.deleted_user = None
    new_forgot_password.deleted = False

    forgot_password_inserted = ForgotPasswordDao.add_forgot_password_code(
        new_forgot_password)

    if forgot_password_inserted:
        new_forgot_password = ForgotPasswordDao.get_forgot_password_code(code)

        async def send_forgot_password_email():
            async with aiohttp.ClientSession() as session:
                async with session.post(
                        url=
                        f"{current_app.config['FUNCTION_URL']}/email/forgot-password",
                        json={
                            'to': user.email,
                            'code': new_forgot_password.forgot_code,
                            'username': user.username,
                            'firstName': user.first,
                            'lastName': user.last
                        }) as response:
                    response_body = await response.json()
                    if not response_body.get('result'):
                        current_app.logger.error(
                            'Failed to send the activation code to the user')
                        abort(424)

        asyncio.run(send_forgot_password_email())

        response = jsonify({
            'self': f'/v2/forgot_password/{username}',
            'created': True
        })
        response.status_code = 201
        return response
    else:
        response = jsonify({
            'self':
            f'/v2/forgot_password/{username}',
            'created':
            False,
            'error':
            'An unexpected error occurred while creating the new forgot password code.'
        })
        response.status_code = 500
        return response