예제 #1
0
def post(body):
    """Method to handle POST verb for /Groups endpoint"""
    user = g.db_session.query(User)\
                       .filter(User.user_id == g.user_id)\
                       .one_or_none()
    # Check to see if the user has privileges to create groups
    if not user.create_groups:
        return api_error(401, 'INSUFFICIENT_PRIVILEGES', g.user.username)

    # Check if this request would be a duplicate, and if so return an error
    check_group = g.db_session.query(Group).filter(Group.name == body['name']).one_or_none()
    if check_group is not None:
        return api_error(400, 'DUPLICATE_GROUP_NAME', body['name'])
    if 'gid' in body:
        check_group = g.db_session.query(Group).filter(Group.gid == body['gid']).one_or_none()
        if check_group is not None:
            return api_error(400, 'DUPLICATE_GID', body['gid'])
    obj = new_dm_object(Group, body)
    # Ensure that user who created the group is the owner
    obj.memberships.append(Membership(user=user, is_owner=True))
    persist_dm_object(obj, g.db_session)
    current_app.logger.info('Group Post Response: {}'.format(str(post_response(obj, 'group_id'))))
#    return post_response(obj, 'group_id')
    # Need to return the first member appended since that did not come from client
    return {'group_id': obj.get_uuid(), 'membership': obj.memberships[0].dump()}, 201
예제 #2
0
def put(reset_finish):
    """Method to handle PUT verb for /pw_reset endpoint"""
    user_id = get_jwt_identity()
    current_app.logger.debug('pw_reset PUT - Have identity = ' + str(user_id))
    reset_user = g.db_session.query(User)\
                  .filter(User.user_id == uuid.UUID(user_id).bytes).one_or_none()
    # Do not need to check if the user exists here, because the refresh token required should
    # ensure that this is a valid identity
    #
    # Check to confirm the reset code provided matches the stored one, and that it has not
    # expired
    if reset_user.reset_code != reset_finish['reset_code'] or\
       reset_user.reset_expires < datetime.datetime.now():
        current_app.logger.debug('reset_user.reset_code = ' +
                                 reset_user.reset_code)
        current_app.logger.debug('reset_finish[reset_code] = ' +
                                 reset_finish['reset_code'])
        current_app.logger.debug('reset_user.reset_expires = ' +
                                 str(reset_user.reset_expires))
        current_app.logger.debug('datetime.datetime.now() = ' +
                                 str(datetime.datetime.now()))
        return api_error(400, 'RESET_CODE_INVALID_OR_EXIRED')
    # Confirm the provided email matches the user that we're updating
    if reset_user.email != reset_finish['email']:
        return api_error(400, 'RESET_EMAIL_MISMATCH')
    # Now reset the user's password
    reset_user.hash_password(reset_finish['password'])
    reset_user.reset_code = None
    reset_user.reset_expires = None
    g.db_session.add(reset_user)
    g.db_session.commit()
    return 'Password reset!', 200
예제 #3
0
def put(user_id, user):
    """Method to handle PUT verb for /users/{user_id} endpoint"""
    binary_uuid = uuid.UUID(user_id).bytes
    update_user = g.db_session.query(User).filter(
        User.user_id == binary_uuid).one_or_none()
    if not update_user:
        return api_error(404, 'USER_ID_NOT_FOUND', user_id)
    # Allow Admin users to edit others without requiring a current password,
    # and let Facebook users edit their information without requiring a password,
    # but everyone else has to include their current password when making an edit
    if not 'Admin' in g.user.roles and\
       not g.user.source == 'Facebook' and\
       not g.user.verify_password(user['password']):
        current_app.logger.debug(
            '/users PUT: rejected missing current password')
        return api_error(401, 'MISSING_PASSWORD_EDIT')
    if update_user.username != g.user.username and not 'Admin' in g.user.roles:
        current_app.logger.debug('/users PUT: rejected update to %s by %s with roles %s' %\
                                 (update_user.username, g.user.username, g.user.roles))
        return api_error(401, 'UNAUTHORIZED_USER_EDIT')
    for key, value in user.items():
        if key != 'password' and key != 'newPassword':
            setattr(update_user, key, value)
        elif key == 'newPassword':
            update_user.hash_password(value)
    g.db_session.add(update_user)
    g.db_session.commit()
    return 'User updated', 200
예제 #4
0
def post(user):
    """Method to handle POST verb for /user enpoint"""

    # Check if the username is already in use, and if so return an error
    check_user = g.db_session.query(User).filter(
        User.username == user['username']).one_or_none()
    if check_user is not None:
        return api_error(400, 'DUPLICATE_USER_NAME', user['username'])
    if 'roles' in user and 'Admin' in user['roles'] and\
       ('NODE_ENV' not in os.environ or os.environ['NODE_ENV'] != 'test'):
        return api_error(400, 'CANNOT_ASSIGN_ADMIN')  # pragma: no cover
    # Confirm ReCaptcha is valid
    if 'NODE_ENV' in os.environ and os.environ['NODE_ENV'] == 'test':
        with requests_mock.Mocker(real_http=True) as mock:
            mock.post(RECAPTCHA_URL, json={'success': True})
            resp = requests.post(RECAPTCHA_URL,
                                 data={
                                     'secret': RECAPTCHA_KEY,
                                     'response': user['reCaptchaResponse'],
                                     'remoteip': request.remote_addr
                                 })
    else:
        resp = requests.post(RECAPTCHA_URL,
                             data={
                                 'secret': RECAPTCHA_KEY,
                                 'response': user['reCaptchaResponse'],
                                 'remoteip': request.remote_addr
                             })  # pragma: no cover
    current_app.logger.debug('Google Post Response Status: ' +
                             str(resp.status_code))
    current_app.logger.debug('Google Post Response JSON: ' + str(resp.json()))
    if resp.status_code >= 400 or not resp.json()['success']:
        return api_error(401, 'API_RECAPTCHA_FAILS')
    current_app.logger.debug('user = '******'username'],
                    email=user['email'],
                    first_name=user['first_name'],
                    last_name=user['last_name'],
                    phone=user['phone'],
                    roles=user['roles'],
                    source='Local')
    if 'preferences' in user:
        new_user.preferences = user['preferences']
    new_user.hash_password(user['password'])
    try:
        g.db_session.add(new_user)
        g.db_session.commit()
    except IntegrityError:
        return api_error(400, 'DUPLICATE_USER_KEY')
    return {'user_id': uuid.UUID(bytes=new_user.user_id)}, 201
예제 #5
0
def post(body):
    """Method to handle POST verb for /pw_reset enpoint"""
    # Find the person by e-mail
    reset_user = g.db_session.query(User)\
                  .filter(User.email == body['email']).one_or_none()
    # If not found, respond with a 404
    if not reset_user:
        return api_error(404, 'EMAIL_NOT_FOUND', body['email'])
    # If found, check if the user already has a reset code that has not expired, and if so
    # return an error 400
    if reset_user.reset_code != None and reset_user.reset_expires > datetime.datetime.now(
    ):
        return api_error(400, 'RESET_CODE_CURRENT')
    # Assign a reset code and expiration timestamp
    reset_user.reset_code = str(random.randint(100000, 999999))
    reset_user.reset_expires = datetime.datetime.now() + datetime.timedelta(
        minutes=15)
    current_app.logger.debug('pw_reset POST - the reset code for ' + \
                              reset_user.username + ' is ' + reset_user.reset_code)
    g.db_session.add(reset_user)
    g.db_session.commit()
    # Need to put the email sending here
    msg = MIMEMultipart('alternative')
    msg['Subject'] = os.environ['APP_PW_RESET_SUBJECT']
    msg['From'] = os.environ['APP_PW_RESET_FROM']
    msg['To'] = body['email']
    msg.attach(
        MIMEText("Here is your reset code: " + reset_user.reset_code, 'plain'))
    msg.attach(
        MIMEText(
            "<html><head></head><body>Here is your reset code: <strong>" +
            reset_user.reset_code + "</strong></body></html>", 'html'))
    server = smtplib.SMTP(os.environ['APP_PW_RESET_MAILHOST'])
    server.starttls()
    current_app.logger.debug('Mail: {} - {}'.format(
        os.environ['APP_PW_RESET_FROM_USER'],
        os.environ['APP_PW_RESET_FROM_PW']))
    server.login(os.environ['APP_PW_RESET_FROM_USER'],
                 os.environ['APP_PW_RESET_FROM_PW'])
    server.sendmail(msg['From'], msg['To'], msg.as_string())

    # Add a refresh token (not an access_token) so that we confirm that the identity we generated
    # the reset code for is the same as the one using the reset code. Possibly redundant, but may
    # prevent database lookup DOS attacks with the actual reset endpoint (?)
    resp = make_response('Reset code sent!', 200)
    refresh_token = create_refresh_token(identity=reset_user.get_uuid())
    set_refresh_cookies(resp, refresh_token)
    return resp
예제 #6
0
def post(body):
    """Method to handle POST verb for /shutdown API endpoint"""
    if 'key' in body and body['key'] == os.environ['APP_SHUTDOWN_KEY']:
        shutdown_server()
        return 'Server shutting down...\n'
    LOGGER.error("ERROR: Post made to /shutdown endpoint without required key value")
    return api_error(400, 'INVALID_SHUTDOWN_KEY', body['key'])
예제 #7
0
def get(user_id):
    """Handles GET verb for /users/{user_id} endpoint"""
    binary_uuid = uuid.UUID(user_id).bytes
    find_user = g.db_session.query(User).filter(
        User.user_id == binary_uuid).one_or_none()
    if not find_user:
        return api_error(404, 'USER_ID_NOT_FOUND', user_id)
    return find_user.dump(), 200
예제 #8
0
def post(body):
    """handles POST verb for /login endpoint"""

    # Need to confirm that either username & password are provided, or
    # access_token for a Facebook login.
    if ('username' not in body or 'password' not in body)\
       and 'access_token' not in body:
        return api_error(400, 'MISSING_USERNAME_API_KEY')

    # Get user based on username / password or access_token
    if 'username' in body:

        # Look up the user and verify that the password is correct
        user = g.db_session.query(User)\
                           .filter(User.username == body['username'])\
                           .one_or_none()
        if not user or not user.verify_password(body['password']):
            current_app.logger.info('--> Failed user.verify_password')
            return api_error(401, 'INVALID_USERNAME_PASSWORD')

    # Now at this point, we should always have a valid user object,
    # whether it came from a Facebook authentication or a normal
    # username / password validation

    # Create access and refresh tokens for the user. See the documentation for
    # flask-jwt-extended for details on these two different kinds of tokens
    access_token = create_access_token(identity=user.get_uuid())
    refresh_token = create_refresh_token(identity=user.get_uuid())

    # Build the response data by dumping the user data
    resp = jsonify({'Users': [user.dump()], 'auth_user_id': user.get_uuid()})

    # Set the tokens we created as cookies in the response
    set_access_cookies(resp, access_token, int(datetime.timedelta(minutes=30).total_seconds()))
    set_refresh_cookies(resp, refresh_token, int(datetime.timedelta(days=30).total_seconds()))

    # TODO: Figure out what the server needs to do, if anything, to enable
    # the CSRF cookie to be accessible to via fetch() headers in browser apps.
    # Some documentation implies that the ability to allow this must be granted
    # from the server via headers, but this may be specific to CORS situations,
    # which does not currently apply to this app. The below 3rd parameter to
    # return adds a custom header which is one component of CORS security to
    # allow access to the cookie
    return resp, 200, {'Access-Control-Expose-Headers': 'Set-Cookie, Content-Type'}
예제 #9
0
def delete(user_id):
    """Method to handle DELETE verb for /users/{user_id} endpoint"""
    current_app.logger.debug('Delete user called with user_id = ' + user_id)
    binary_uuid = uuid.UUID(user_id).bytes
    delete_user = g.db_session.query(User).filter(
        User.user_id == binary_uuid).one_or_none()
    if not delete_user:
        return api_error(404, 'USER_ID_NOT_FOUND', user_id)
    g.db_session.delete(delete_user)
    g.db_session.commit()
    return 'User deleted', 204
예제 #10
0
def post(body):
    """Method to handle POST verb for /Users endpoint"""
    # Check if the username is already in use, and if so return an error
    check_user = g.db_session.query(User).filter(
        User.username == body['username']).one_or_none()
    if check_user is not None:
        return api_error(400, 'DUPLICATE_USER_NAME', body['username'])
    obj = new_dm_object(User, body)
    obj.source = 'Local'
    obj.hash_password(body['password'])
    persist_dm_object(obj, g.db_session)
    return post_response(obj, 'user_id')
예제 #11
0
def delete(user_id):
    """Method to handle DELETE verb for /User/user_id endpoint"""
    obj = existing_dm_object(User, g.db_session, User.user_id, user_id)
    if not obj:
        return 'NOT_FOUND', 404
    user = g.db_session.query(User)\
                       .filter(User.user_id == g.user_id)\
                       .one_or_none()
    if obj.username == user.username or not user.create_users:
        current_app.logger.debug('/users PUT: rejected delete of %s by %s' %\
                                 (obj.username, user.username))
        return api_error(401, 'UNAUTHORIZED_USER_DELETION')
    delete_dm_object(obj, g.db_session)
    return 'User deleted', 204
예제 #12
0
def put(user_id, body):
    """Method to handle PUT verb for /User/user_id endpoint"""
    obj = existing_dm_object(User, g.db_session, User.user_id, user_id)
    if not obj:
        return 'NOT_FOUND', 404
    user = g.db_session.query(User)\
                       .filter(User.user_id == g.user_id)\
                       .one_or_none()
    if obj.username != user.username and not user.create_users:
        current_app.logger.debug('/users PUT: rejected update to %s by %s' %\
                                 (obj.username, user.username))
        return api_error(401, 'UNAUTHORIZED_USER_EDIT')
    obj.apply_update(body)
    if 'newPassword' in body:
        obj.hash_password(body['newPassword'])
    persist_dm_object(obj, g.db_session)
    return 'User updated', 200
예제 #13
0
def delete(group_id):
    """Method to handle DELETE verb for /Group/group_id endpoint"""
    obj = existing_dm_object(Group, g.db_session, Group.group_id, group_id)
    if not obj:
        return 'NOT_FOUND', 404
    user = g.db_session.query(User)\
                       .filter(User.user_id == g.user_id)\
                       .one_or_none()
    # Confirm the logged in user is an admin or owner
    authorized = False
    for member in obj.memberships:
        if member.user.user_id == user.user_id:
            if member.is_owner:
                authorized = True
            break
    if not authorized:
        return api_error(401,'INSUFFICIENT_PRIVILEGES', user.username)
    delete_dm_object(obj, g.db_session)
    return 'Group deleted', 204
예제 #14
0
def post(key):
    """Method to handle POST verb for /shutdown API endpoint"""
    if key['key'] == 'Eric':
        shutdown_server()
        return 'Server shutting down...\n'
    return api_error(400, 'INVALID_SHUTDOWN_KEY', key)
예제 #15
0
def post(login_data):
    """handles POST verb for /login endpoint"""

    # Need to confirm that either username & password are provided, or
    # access_token for a Facebook login.
    if ('username' not in login_data or 'password' not in login_data)\
       and 'access_token' not in login_data:
        return api_error(400, 'MISSING_USERNAME_API_KEY')

    # Get user based on username / password or access_token
    if 'username' in login_data:

        # Look up the user and verify that the password is correct
        user = g.db_session.query(User)\
                           .filter(User.username == login_data['username'])\
                           .one_or_none()
        if not user or not user.verify_password(login_data['password']):
            return api_error(401, 'INVALID_USERNAME_PASSWORD')

    # This section handles facebook login, and I expect it to be tested manually,
    # so it is explicitly excluded from code coverage metrics
    elif 'access_token' in login_data:  # pragma: no cover
        graph = GraphAPI(login_data['access_token'])
        if not graph:
            current_app.logger.debug(
                'Unable to instantiate graph GraphAPI object')
            return api_error(500, 'ERROR_FACEBOOK_MODULE')
        profile = graph.get_object(
            'me?fields=id,name,email,first_name,last_name')
        if not profile:
            current_app.logger.debug('Unable to get profile')
            return api_error(401, 'ERROR_FACEBOOK_PROFILE')
        current_app.logger.debug('Cool - got %s', profile)
        if not 'email' in profile:
            return api_error(401, 'ERROR_FACEBOOK_PRIVILEGES')
        user = g.db_session.query(User)\
                           .filter(User.email == profile['email']).one_or_none()
        # Create the user if it does not exist
        if not user:
            user = User(email=profile['email'],
                        username=profile['first_name'] + '.' +
                        profile['last_name'],
                        first_name=profile['first_name'],
                        last_name=profile['last_name'],
                        user_id=uuid.uuid4().bytes,
                        source='Facebook',
                        roles='User')
            g.db_session.add(user)
            g.db_session.commit()

    # Now at this point, we should always have a valid user object,
    # whether it came from a Facebook authentication or a normal
    # username / password validation

    # Create access and refresh tokens for the user. See the documentation for
    # flask-jwt-extended for details on these two different kinds of tokens
    access_token = create_access_token(identity=user.get_uuid())
    refresh_token = create_refresh_token(identity=user.get_uuid())

    # Build the response data by dumping the user data
    resp = jsonify(user.dump())

    # Set the tokens we created as cookies in the response
    set_access_cookies(resp, access_token)
    set_refresh_cookies(resp, refresh_token)

    # TODO: Figure out what the server needs to do, if anything, to enable
    # the CSRF cookie to be accessible to via fetch() headers in browser apps.
    # Some documentation implies that the ability to allow this must be granted
    # from the server via headers, but this may be specific to CORS situations,
    # which does not currently apply to this app. The below 3rd parameter to
    # return adds a custom header which is one component of CORS security to
    # allow access to the cookie
    return resp, 200, {
        'Access-Control-Expose-Headers': 'Set-Cookie, Content-Type'
    }