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
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
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
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
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
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'])
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
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'}
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
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')
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
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
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
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)
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' }