def test_create_user(test_client, admin_user): """ Test for successful creation of a new user by an admin and for presense of user in the database """ response_login = test_client.post('/login', data=dict(email=admin_user.email, password="******"), follow_redirects=True) response_login_json = response_login.get_json() header = { 'Authorization': 'Bearer {}'.format(response_login_json["access_token"]) } response_create = test_client.post('/admin/create_new_user', data=dict(email="*****@*****.**", fullname="New User", admin="True", password="******"), headers=header, follow_redirects=True) response_create_json = response_create.get_json() assert b"created successfull" in response_create.data assert response_create.status_code == 201 assert Users.find_by_email("*****@*****.**") != None db.session.delete(Users.find_by_email("*****@*****.**")) db.session.commit()
def patch(self) -> (str, int): """ API that allows a user change their password in the users table Parameters can be passed using a PATCH request that contains a JSON with the following fields: :param email: users email address :param password_old: users current password :param password_new: users new password that they want to replace password_old :type email: string :type password_old: string :type password_new: string :return: A message that indicates whether a user's password has been updated. If they have not, the message indicates why not. :rtype: JSON """ args = self.parser.parse_args() current_user = Users.find_by_email(args['email']) #t if current_user and Users.verify_hash( args['password_old'].encode("utf8"), current_user.password.encode("utf8")): current_user.password = Users.generate_hash( args["password_new"].encode("utf8") ).decode( "utf8" ) # decode necessary so that string (and not binary) is stored in the DB current_user.commit() return {"message": "Password has been updated"}, 200 else: return {"message": "Incorrect credentials. Please Try again"}, 403
def post(self) -> (str, int): """ API resource class which deletes a user from the database Parameters can be passed using a DELETE request that contains a JSON with the following fields: :required: valid access JWT where the admin claim has to be true :param email: users email address :type email: str :return: An empty string and a 204 response on success or an error message and relevant status code when unsuccessful """ args = self.delete_reqparser.parse_args() # User needs admin rights to continue if not get_jwt_claims()['admin']: abort(HTTPStatus.FORBIDDEN.value, error="administration privileges required") # Get user instance user = Users.find_by_email(args["email"]) if not user: return abort(HTTPStatus.BAD_REQUEST.value, error='user not found') try: # Remove the user user.delete() user.commit() except exc.SQLAlchemyError as e: logging.critical(e, HTTPStatus.BAD_REQUEST.value, error="User not removed") abort(HTTPStatus.BAD_REQUEST.value, error="User not removed") return "", 204
def post(self) -> (str, int): """ API resource class which updates a users admin and/or activated field Parameters can be passed using a POST request that contains a JSON with the following fields: :required: valid access JWT where the admin claim has to be true :param email: users email address :param activated: the value of the user's activated field :param admin: whether the the value of the user's activated field :type email: str :type activated: str :type admin: str :return: The user's credentials on success or an error message and corresponding status code when unsuccessful """ args = self.post_reqparser.parse_args() # User needs admin rights to continue if not get_jwt_claims()['admin']: abort(HTTPStatus.FORBIDDEN.value, error="administration privileges required") # Get user instance user = Users.find_by_email(args["email"]) if not user: return abort(HTTPStatus.BAD_REQUEST.value, error='User not found') if "activated" in args: user.activated = inputs.boolean(args["activated"]) if "admin" in args: user.admin = inputs.boolean(args["admin"]) # Save changes to user permissions user.save() user.commit() return user.json(), HTTPStatus.OK.value
def test_edit_user(test_client, admin_user, dummy_user): """ Test that the changes a user makes to their fullname persists to the database """ response_login = test_client.post('/login', data=dict(email=admin_user.email, password="******"), follow_redirects=True) response_login_json = response_login.get_json() header = { 'Authorization': 'Bearer {}'.format(response_login_json["access_token"]) } response_change = test_client.post('/admin/edit_user', data=dict(email=dummy_user.email, fullname="Not a dummy", password="******", admin=False, activated=False), headers=header, follow_redirects=True) assert response_change.status_code == 200 changed_user = Users.find_by_email(dummy_user.email) assert changed_user.fullname == "Not a dummy" assert Users.verify_hash("new password".encode("utf8"), dummy_user.password.encode("utf8")) == True assert changed_user.admin == False assert changed_user.admin == False
def test_create_user() -> NoReturn: """tests the user creation""" global dependencies new_user = dependencies.get_dummy_user() same_user = Users.find_by_email(new_user.email) assert same_user is new_user new_user.delete() new_user.commit()
def post(self) -> (str,int): """ POST requests that creates a new valid access JWT for the requesting user :required: A valid refresh JWT in the Authorization Header in the format - Bearer <JWT> :return: The user's corresponding access JWT :rtype: JSON """ current_user = get_jwt_identity() # extracts the users identity from the refresh token current_user = Users.find_by_email(current_user) access_token = create_access_token(identity = current_user) return {'access_token': access_token}, 200
def test_forgot_password(self): """ Test forgot password sets user's password to a system generated password """ forgotten_password = self.dummy_user.password response = self.testing_client.post('/forgot_password', data=dict( email=self.dummy_user.email)) self.assertIn(b"success", response.data) self.assertEquals(response.status_code, 200) updated_user = Users.find_by_email(self.dummy_user.email) self.assertNotEqual(updated_user.password, forgotten_password)
def post(self) -> (str, int): """ POST request that allows user to change their credentials in the Users table. A valid access JWT is required where the admin claim has to be True Parameters can be passed using a POST request that contains a JSON with the following fields: :required: :param email: users current email address :type email: str :optional: :param fullname: users to be fullname :param password: users to be password :param admin: users to be admin status :param activated: users to be activated status :type fullname: str :type password: str :type admin: bool :type activated: bool :return: A message indicating success or failure and the corresponding response code """ if not get_jwt_claims()['admin']: abort(HTTPStatus.FORBIDDEN.value, error="administration privileges required") args = self.post_reqparser.parse_args() current_user = Users.find_by_email(args["email"]) if current_user: if "fullname" in args: current_user.fullname = args["fullname"] if "activated" in args: current_user.activated = bool(args["activated"]) if "admin" in args: current_user.admin = bool(args["admin"]) if "password" in args: current_user.password = Users.generate_hash( args["password"].encode("utf8")).decode("utf8") try: current_user.commit() except Exception as e: logging.error(e) return {"message": "failed saving details to database"}, 500 return {"message": "success"}, 200 else: return {"message": "error: could not find user"}, 403
def get(self) -> (dict, HTTPStatus): """ Check for triggered alerts. :return: On success, an HTTP response with a JSON body content containing the maximum and minimum alerts that have been exceeded with an HTTP status code of 200 (OK), otherwise an HTTP response with a JSON body content containing an appropriate error message and appropriate an HTTP status code """ args = self.reqparser.parse_args() # Use current user_id if not user_id was parsed if "user_id" not in args: user = Users.find_by_email(get_jwt_identity()) if user: args["user_id"] = user.id else: # Return Error current user id not found return (dict(error="User id not found for Current user, " "User session may have timed out"), HTTPStatus.INTERNAL_SERVER_ERROR) else: user = Users.find_by_id(args["user_id"]) if not user: # Return error Parsed User Id not found return dict(error="User with id {} not found.".format( args["user_id"])), HTTPStatus.NOT_FOUND # Check Attribute exists if not Attributes.get_by_id(args["attribute_id"]): # Return Error Attribute ID not found return dict(error="Attribute id {} not found.".format( args["attribute_id"])), HTTPStatus.NOT_FOUND # Get Attribute Max and Minimums attribute_range = AttributeRange.get_by_attr_id(args["attribute_id"]) if not attribute_range: # Return eroor Attribute range not found return (dict(error="Attribute range not found", **args), HTTPStatus.NOT_FOUND) # Check Alerts max_alerts = AlertWidgetModel.get_max_alerts(attribute_range, user_id=user.id) min_alerts = AlertWidgetModel.get_min_alerts(attribute_range, user_id=user.id) return dict(max=max_alerts, min=min_alerts), 200
def create_admin_user(self) -> Users: """ Create Admin user :return: an admin user """ password_hash = bcrypt.hashpw("wfnbqk".encode("utf-8"), bcrypt.gensalt()) user = Users.find_by_email("*****@*****.**") if not user: user = Users("Admin", "*****@*****.**", password_hash.decode("utf8"), True, True) try: user.save() user.commit() except Exception as e: pass return user
def test_change_user_fullname() -> NoReturn: """ tests the user name is changed using the '/admin/change_user_fullname' endpoint """ user = dependencies.get_dummy_user() json_data = {"email": user.email, "fullname": "asdfghjkl"} response = dependencies.client.post('/admin/change_user_fullname', json=json_data, headers=dependencies.auth_header, follow_redirects=True) user = Users.find_by_email(user.email) assert user.fullname == "asdfghjkl" user.delete() user.commit()
def test_change_user_password() -> NoReturn: """ tests that the user password is changed using the '/admin/change_user_password' endpoint """ user = dependencies.get_dummy_user() json_data = { "email": "*****@*****.**", "password": "******", "verify_password": "******" } response = dependencies.client.post('/admin/change_user_password', json=json_data, headers=dependencies.auth_header, follow_redirects=True) user = Users.find_by_email()
def post(self) -> (str, int): """ Save or Update a Widget Parameters can be passed using a POST request. POST request JSON body parameters: :param data: Widget JSON data :param widgetID: Widget Id :param x: x coordinate of the widget layout :param y: y coordinate of the widget layout :param h: height of the widget layout :param w: width of the widget layout :param static: layout static property :returns : A response message and an appropriate HTTP status code """ args = self.reqparse_post.parse_args() current_user = Users.find_by_email(get_jwt_identity()) curr_widget = None if "widget_id" in args: curr_widget = WidgetModel.get_widget_by_id_and_user_id( args["widget_id"], current_user.id) if curr_widget: curr_widget.data = args["data"] else: abort(HTTPStatus.NOT_FOUND, error="Widget with Id {} not found".format( args["widget_id"])) else: # Create a layout for the new widget layout = Layouts(1, 0, 0, args['height'], args['width'], False) curr_widget = WidgetModel(current_user.id, layout, args["data"]) try: db.session.add(curr_widget) # flush session to get new widgetID to assign to layout db.session.flush() self.update_layout(curr_widget, args) db.session.commit() except exc.SQLAlchemyError as e: logger.error(e.with_traceback(e.__traceback__)) abort(HTTPStatus.BAD_REQUEST.value, error="exc.SQLAlchemyError: create_widget") response = self.get_reponse_template(curr_widget, updated="widget_id" in args) return response, HTTPStatus.OK
def _does_user_exsist(email: str = None) -> bool: """ Checks if a user with the email passed exists :param email: users email address :type email: str :return: true if the user exists otherwise false """ if not email: return abort(HTTPStatus.BAD_REQUEST.value, error='email not supplied') try: user_exsists = Users.find_by_email(email) except exc.SQLAlchemyError as error: return abort(HTTPStatus.BAD_REQUEST.value, jsonify({'error': error})) return user_exsists is not None
def post(self) -> (str, int): """ API resource class which returns a user from the database Parameters can be passed using a POST request that contains the following fields in the url: :required: valid access JWT where the admin claim may be true or false :param email: users email address :type email: str :return: The user's credentials on success or an error message and relevant status code when unsuccessful """ args = self.get_reqparser.parse_args() # Fetch user from database using the users email user = Users.find_by_email(args["email"]) if not user: # No user with that email address return abort(HTTPStatus.BAD_REQUEST.value, error='User not found') return user.json(), HTTPStatus.OK.value
def post(self) -> (dict, HTTPStatus): """ Create an alert. :return: On success return the new alert Id and an HTTP status code 201 (Created), Otherwise return an error with the appropriate HTTP status code """ args = self.reqparser.parse_args() if "user_id" not in args: # Not user_id in args get current user_id user = Users.find_by_email(get_jwt_identity()) if user: args["user_id"] = user.id else: # Return Error current user id not found return (dict(error="User id not found for Current user, " "User session may have timed out"), HTTPStatus.INTERNAL_SERVER_ERROR) elif not Users.find_by_id(args["user_id"]): # User not found return an error return dict(error="User id {} not found".format( args["user_id"])), HTTPStatus.NOT_FOUND if "max_threshold" not in args and "min_threshold" not in args: # No Threshold value in args at least one is required return an error return dict( error="A Threshold value is required"), HTTPStatus.BAD_REQUEST # Create AlertModel alert_model = AlertWidgetModel(args["user_id"], args["widget_id"], args["attribute_id"], args["max_threshold"], args["min_threshold"], args["activated"]) if not alert_model: # Unable to create AlertModel return an error return dict(error="Unable to create Alert", args=args), \ HTTPStatus.INTERNAL_SERVER_ERROR # Persist Alert to database alert_model.save() alert_model.commit() return dict(id=alert_model.id), HTTPStatus.CREATED
def create_admin_user() -> Users: """ Create an Admin user :return: an Admin user """ password_hash = bcrypt.hashpw("@p@22M0rd#@!".encode("utf-8"), bcrypt.gensalt()) user = Users.find_by_email("test_admin_user@no_an_email.cr") if not user: user = Users("test_admin_user", "test_admin_user@no_an_email.cr", password_hash.decode("utf8"), True, True) try: user.save() user.commit() except Exception as e: pass return user
def test_delete_user(test_client, admin_user, dummy_user): """ Test for successful deletion of a specified user by an admin and for absence of user in the database """ response_login = test_client.post('/login', data=dict(email=admin_user.email, password="******"), follow_redirects=True) response_login_json = response_login.get_json() header = { 'Authorization': 'Bearer {}'.format(response_login_json["access_token"]) } response_delete = test_client.delete('/admin/delete_user', data=dict(email=dummy_user.email), headers=header, follow_redirects=True) assert response_delete.status_code == 204 assert Users.find_by_email(dummy_user.email) == None
def post(self) -> (str, int): """ API resource class which changes username and saves changes to the database Parameters can be passed using a POST request that contains a JSON with the following fields: :required: valid access JWT where the admin claim may be either true or false :param email: users email address :param fullname: users fullname :type email: str :type fullname: str :return: Empty string and 200 status code """ args = self.post_reqparser.parse_args() user = Users.find_by_email(args["email"]) if not user: abort(HTTPStatus.NOT_FOUND.value, error='User not found.') user.fullname = args["fullname"] user.save() user.commit() return "", 200
def test_create_user_endpoint() -> NoReturn: """ tests the '/admin/create_new_user' endpoint """ global dependencies print(dependencies.user.id) json_data = { "email": "*****@*****.**", "fullname": "fullname", "admin": True, "password": "******" } response = dependencies.client.post('/admin/create_new_user', json=json_data, headers=dependencies.auth_header, follow_redirects=True) assert response.status_code == 201 user = Users.find_by_email('*****@*****.**') if user: user.delete() user.commit
def post(self) -> (dict, HTTPStatus): """ Delete an alert. :return: On success, A HTTP response with a a JSON body content containing a message, a list of deleted alerts and an HTTP status code 200 (OK) otherwise an appropriate error message and an appropriate HTTP status code """ args = self.reqparser.parse_args() # Check if enough arguments where passed to perform a deletion if all(arg not in args for arg in ["user_id", "attribute_id", "id"]): # Return an error responseDid not receive enough # arguments to find an alert accurately return (dict(error="Insufficient Arguments", possible_args="user_id, attribute_id, alert_id"), HTTPStatus.BAD_REQUEST) # Use current user_id if not user_id was parsed if "user_id" not in args: user = Users.find_by_email(get_jwt_identity()) if user: args["user_id"] = user.id else: # Return Error current user id not found return (dict(error="User id not found for Current user, " "User session may have timed out"), HTTPStatus.INTERNAL_SERVER_ERROR) # Get Alerts using parsed args as filter arguments alerts = AlertWidgetModel.get_by_kwargs(**args) response_data = [] for alert in alerts: response_data.append(alert.json) alert.delete() alert.commit() AlertWidgetModel.commit() return dict(message="Alert(s) Deleted", alerts=response_data), 200
def post(self) -> (dict, int): """ API resource class which changes a users password and saves changes to the database Parameters can be passed using a POST request that contains a JSON with the following fields: :required: valid access JWT where the admin claim may be either true or false :param email: users email address :param password: the new password which the user wants to store in the database :param verify_password: a repitition of the the 'password' param :type email: str :type password: str :type verify_password: str :return: A message indicating a successful or unsuccessful change """ args = self.post_reqparser.parse_args() user = None # User must be an admin if not get_jwt_claims()['admin']: abort(HTTPStatus.FORBIDDEN.value, error="administration privileges required") # get user instance using email address if args["email"]: user = Users.find_by_email(args["email"]) if not user: abort(HTTPStatus.BAD_REQUEST.value, error="User not found") if args["password"] == args["verify_password"]: user.password = Users.generate_hash( args["password"].encode("utf-8")).decode("utf-8") try: user.save() user.commit() except exc.SQLAlchemyError as e: logging.critical(e) abort(HTTPStatus.BAD_REQUEST.value, error="User password not changed") return {"user": "******".format(args["email"])}, 201
def post(self) -> (str, int): """ POST requests, activate user's account and change their password Parameters can be passed using a POST request that contains a JSON of the following fields: :param fullname: users fullname :param email: users email address :param password: users password that was sent when they were added on the admin page :type fullname: string :type email: string :type password: string :return: A message that indicates whether a user has been registered. If they have not, the message indicates why not """ args = self.parser.parse_args() current_user = Users.find_by_email(args['email']) #t if not current_user: return { 'message': 'User {} is not authorised to access Sharing Cities Dashboard'. format(args['email']) }, 403 if not Users.verify_hash(args['password'].encode("utf8"), current_user.password.encode("utf8")): return { 'message': 'The password entered does not correspond to the password sent to {}. Please try again' .format(args['email']) }, 403 current_user.activated = True current_user.password = Users.generate_hash( args["password_new"].encode("utf8")).decode("utf8") current_user.commit() return { 'message': '{}\'s account has been registered. Redirect to login'.format( args['email']) }, 201
def post(self) -> (str, int): args = self.parser.parse_args() current_user = Users.find_by_email(args['email']) if current_user \ and Users.verify_hash( args['password'].encode("utf8"), current_user.password.encode("utf8")): if current_user.activated: remember_access_token = timedelta(hours=3) remember_refresh_token = timedelta(hours=6) if args['remember'] is True: remember_access_token = timedelta(weeks=1) remember_refresh_token = timedelta(weeks=2) access_token = create_access_token( identity=current_user, expires_delta=remember_access_token) refresh_token = create_refresh_token( identity=current_user, expires_delta=remember_refresh_token) return { 'message': 'Logged in as {}'.format(current_user.email), 'access_token': access_token, 'refresh_token': refresh_token, 'id': current_user.id, 'fullname': current_user.fullname }, 200 else: return { 'message': 'User {} has not been activated. ' 'Please register when redirected to registration ' 'page'.format(current_user.email) }, 403 else: return {'message': 'Incorrect credentials. Please try again'}, 403
def post(self): args = self.post_reqparser.parse_args() user = None #User must be an admin if not get_jwt_claims()['admin']: abort(HTTPStatus.FORBIDDEN.value, error="administration privileges required") #get user instance using email if args["email"]: user = Users.find_by_email(args["email"]) if not user: abort(HTTPStatus.BAD_REQUEST.value, error="User not found") if args["password"] == args["verify_password"]: user.password = Users.generate_hash(args["password"].encode("utf-8")) try: user.save() user.commit() except exc.SQLAlchemyError: abort(HTTPStatus.BAD_REQUEST.value, error="User password not changed") return {"user": "******".format(args["email"])}, 201
def post(self) -> (str, int): """ POST request endpoint. Set user password to a system generated password and send password to user's email """ args = self.parser.parse_args() current_user = Users.find_by_email(args["email"]) if current_user: system_password = self.generate_random_password( self.system_password_length) if self.send_forgot_password_email(current_user.fullname, current_user.email, system_password): current_user.password = Users.generate_hash( system_password.encode("utf8")).decode("utf8") current_user.commit() return {"message": "success"}, 200 else: return {"message": "could not send email"}, 500 else: return {"message": "cannot find user"}, 403