def post(self): request_data = request.get_json() if request_data is None: error(400, "No json data in request body") check_data_fields( request_data, ["first_name", "last_name", "middle_name", "birth_date"]) cipher = DataCipher(key=current_app.config.get("SECRET_KEY")) decrypted_credentials = cipher.decrypt(request.cookies["oeci_token"]) login_result = (decrypted_credentials["oeci_username"] == "username" and decrypted_credentials["oeci_password"] == "password") if login_result is False: error(401, "Attempted login to OECI failed") record = build_record() response_data = {"data": {"record": record}} current_app.json_encoder = ExpungeModelEncoder return response_data # Json-encoding happens automatically here
def get(self, user_id): """ Fetch a single user's data if a user_id is specified. Otherwise fetch the list of all users. Returned info contains user_id, name, group name,email, admin status, and date_created. """ if user_id: return get_from_user_id(user_id) else: # No user_id given; this is a GET all users request. if not current_user.is_admin: error(403, "Logged in user not admin ") user_db_data = user_db_util.fetchall(g.database) response_data: Dict[str, List[Dict[str, str]]] = {"users": []} for user_entry in user_db_data: response_data["users"].append({ "id": user_entry["user_id"], "email": user_entry["email"], "name": user_entry["name"], "group": user_entry["group_name"], "admin": user_entry["admin"], "timestamp": user_entry["date_created"] }) return jsonify(response_data), 201
def get(self, user_id): """ Fetch the list of users, with their user_id, name, group name, email, admin status, and date_created. """ print("in endpoint; getting with userid = ", user_id) if user_id: user_db_data = user.read(g.database, user_id) print("user db read result: ", user_db_data) if user_db_data: response_data = { "user_id": user_db_data["user_id"], "email": user_db_data["email"], "name": user_db_data["name"], "group_name": user_db_data["group_name"], "admin": user_db_data["admin"], "timestamp": user_db_data["date_created"] } return jsonify(response_data), 201 else: error(404, "User id not recognized") else: user_db_data = user.fetchall(g.database) response_data = {"users": []} for user_entry in user_db_data: response_data["users"].append({ "user_id": user_entry["user_id"], "email": user_entry["email"], "name": user_entry["name"], "group_name": user_entry["group_name"], "admin": user_entry["admin"], "timestamp": user_entry["date_created"] }) return jsonify(response_data), 201
def post(self): data = request.get_json() if data is None: error(400, "No json data in request body") check_data_fields(data, ["email", "password"]) user_db_result = user_db_util.read(g.database, user_db_util.identify_by_email(g.database, data["email"])) if not user_db_result or not check_password_hash(user_db_result["hashed_password"], data["password"]): error(401, "Invalid username or password") user = from_dict(data_class=User, data=user_db_result) User.login_user(user) return jsonify({})
def get_from_user_id(user_id): user_db_data = user_db_util.read(g.database, user_id) if not user_db_data: error(404, "User id not recognized") if not current_user.is_admin and current_user.user_id != user_id: error(403, "Logged in user not admin and doesn't match requested user id.") response_data = { "user_id": user_db_data["user_id"], "email": user_db_data["email"], "name": user_db_data["name"], "group_name": user_db_data["group_name"], "admin": user_db_data["admin"], "timestamp": user_db_data["date_created"]} return jsonify(response_data), 201
def post(self): data = request.get_json() if data == None: error(400, "No json data in request body") check_data_fields(data, ['email', 'password']) user_db_result = get_user_by_email(g.database, data['email']) if (not user_db_result or not check_password_hash(user_db_result['hashed_password'], data['password'])): error(401, 'Invalid username or password') response_data = { 'auth_token': get_auth_token(current_app, user_db_result['user_id']) } return jsonify(response_data)
def post(self): """ Attempts to log in to the OECI web site using the provided username and password if successful, encrypt those credentials and return them in a cookie. If the credentials """ data = request.get_json() if data is None: error(400, "No json data in request body") check_data_fields(data, ["oeci_username", "oeci_password"]) credentials = { "oeci_username": data["oeci_username"], "oeci_password": data["oeci_password"] } login_result = (data["oeci_username"] == "username" and data["oeci_password"] == "password") if not login_result: error(401, "Invalid OECI username or password.") cipher = DataCipher(key=current_app.config.get("JWT_SECRET_KEY")) encrypted_credentials = cipher.encrypt(credentials) response = make_response() response.set_cookie( "oeci_token", # currently nginx/flask app are running as HTTP # secure=True requires HTTPS to maintain secure cookies # https://resources.infosecinstitute.com/securing-cookies-httponly-secure-flags/#gref # We will need an OECILogout endpoint to remove httponly=true cookies from frontend secure=False, httponly=False, samesite="strict", expires=time.time() + 15 * 60, # 15 minutes value=encrypted_credentials) return response, 201
def post(self): request_data = request.get_json() if request_data is None: error(400, "No json data in request body") check_data_fields(request_data, ["first_name", "last_name", "middle_name", "birth_date"]) cipher = DataCipher( key=current_app.config.get("JWT_SECRET_KEY")) decrypted_credentials = cipher.decrypt(request.cookies["oeci_token"]) crawler = Crawler() login_result = crawler.login( decrypted_credentials["oeci_username"], decrypted_credentials["oeci_password"], close_session=False) if login_result is False: error(401, "Attempted login to OECI failed") record = crawler.search( request_data["first_name"], request_data["last_name"], request_data["middle_name"], request_data["birth_date"]) expunger = Expunger(record) expunger.run() response_data = { "data": { "record": record } } current_app.json_encoder = ExpungeModelEncoder return response_data #Json-encoding happens automatically here
def post(self): """ Attempts to log in to the OECI web site using the provided username and password if successful, encrypt those credentials and return them in a cookie. If the credentials """ data = request.get_json() if data is None: error(400, "No json data in request body") check_data_fields(data, ["oeci_username", "oeci_password"]) credentials = { "oeci_username": data["oeci_username"], "oeci_password": data["oeci_password"] } login_result = data["oeci_username"] == "username" and data[ "oeci_password"] == "password" if not login_result: error(401, "Invalid OECI username or password.") cipher = DataCipher(key=current_app.config.get("SECRET_KEY")) encrypted_credentials = cipher.encrypt(credentials) response = make_response() # TODO: We will need an OECILogout endpoint to remove httponly=true cookies from frontend response.set_cookie( "oeci_token", secure=os.getenv("TIER") == "production", httponly=False, samesite="strict", expires=time.time() + 15 * 60, # 15 minutes value=encrypted_credentials, ) return response, 201
def post(self): """ Attempts to log in to the OECI web site using the provided username and password if successful, encrypt those credentials and return them in a cookie. If the credentials """ data = request.get_json() if data is None: error(400, "No json data in request body") check_data_fields(data, ["oeci_username", "oeci_password"]) credentials = {"oeci_username": data["oeci_username"], "oeci_password": data["oeci_password"]} login_result = Crawler().login( credentials["oeci_username"], credentials["oeci_password"], close_session=True) if not login_result: error(401, "Invalid OECI username or password.") cipher = DataCipher( key=current_app.config.get("JWT_SECRET_KEY")) encrypted_credentials = cipher.encrypt(credentials) response = make_response() response.set_cookie( "oeci_token", secure=True, httponly=True, samesite="strict", expires=time.time() + 15 * 60, # 15 minutes value=encrypted_credentials) return response, 201
def get(self, user_id): """ Fetch a single user's data if a user_id is specified. Otherwise fetch the list of all users. Returned info contains user_id, name, group name,email, admin status, and date_created. """ if user_id: user_db_data = user.read(g.database, user_id) if not user_db_data: error(404, "User id not recognized") if not g.logged_in_user_is_admin and g.logged_in_user_id != user_id: error( 403, "Logged in user not admin and doesn't match requested user id." ) response_data = { "user_id": user_db_data["user_id"], "email": user_db_data["email"], "name": user_db_data["name"], "group_name": user_db_data["group_name"], "admin": user_db_data["admin"], "timestamp": user_db_data["date_created"] } return jsonify(response_data), 201 else: # No user_id given; this is a GET all users request. if not g.logged_in_user_is_admin: error(403, "Logged in user not admin ") user_db_data = user.fetchall(g.database) response_data = {"users": []} for user_entry in user_db_data: response_data["users"].append({ "user_id": user_entry["user_id"], "email": user_entry["email"], "name": user_entry["name"], "group_name": user_entry["group_name"], "admin": user_entry["admin"], "timestamp": user_entry["date_created"] }) return jsonify(response_data), 201
def post(self): """ Create a new user with provided email, password, and admin flag. - If required fields are missing in the request, return 400 - Password must be 8 or more characters long. Otherwise return 422 - Email must not already be in use by an existing user. Otherwise return 422 - If success, return 201 with the new user's email, admin flag, and creation timestamp. """ data = request.get_json() if data is None: error(400, "No json data in request body") check_data_fields(data, ["email", "name", "group_name", "password", "admin"]) if len(data["password"]) < 8: error(422, "New password is less than 8 characters long!") password_hash = generate_password_hash(data["password"]) try: create_user_result = user_db_util.create( g.database, email=data["email"], name=data["name"], group_name=data["group_name"], password_hash=password_hash, admin=data["admin"]) except UniqueViolation: error(422, "User with that email address already exists") response_data = { "user_id": create_user_result["user_id"], "email": create_user_result["email"], "admin": create_user_result["admin"], "name": create_user_result["name"], "group_name": create_user_result["group_name"], "timestamp": create_user_result["date_created"] } return jsonify(response_data), 201
def post(self): """ Create a new user with provided email, password, and admin flag. - If required fields are missing in the request, return 400 - Password must be 8 or more characters long. Otherwise return 422 - Email must not already be in use by an existing user. Otherwise return 422 - If success, return 201 with the new user's email, admin flag, and creation timestamp. """ data = request.get_json() if data is None: error(400, "No json data in request body") # print("data received by Users.post():", data) check_data_fields(data, ['email', 'name', 'group_name', 'password', 'admin']) if len(data['password']) < 8: error(422, 'New password is less than 8 characters long!') password_hash = generate_password_hash(data['password']) try: create_user_result = user.create(g.database, email=data['email'], name=data['name'], group_name=data['group_name'], password_hash=password_hash, admin=data['admin']) except UniqueViolation: error(422, 'User with that email address already exists') response_data = { 'email': create_user_result['email'], 'admin': create_user_result['admin'], 'timestamp': create_user_result['date_created'], } # user_id is not required by the frontend here so it is not included. # other endpoints may expose the user_id e.g. for other admin # user-management operations. return jsonify(response_data), 201
def post(self): request_data = request.get_json() if request_data is None: error(400, "No json data in request body") check_data_fields( request_data, ["first_name", "last_name", "middle_name", "birth_date"]) cipher = DataCipher(key=current_app.config.get("SECRET_KEY")) if not "oeci_token" in request.cookies.keys(): error(401, "Missing login credentials to OECI.") decrypted_credentials = cipher.decrypt(request.cookies["oeci_token"]) crawler = Crawler() login_result = crawler.login(decrypted_credentials["oeci_username"], decrypted_credentials["oeci_password"], close_session=False) if login_result is False: error(401, "Attempted login to OECI failed") record = crawler.search(request_data["first_name"], request_data["last_name"], request_data["middle_name"], request_data["birth_date"]) expunger = Expunger(record) expunger.run() try: save_result(request_data, record) except Exception as ex: logging.error("Saving search result failed with exception: %s" % ex, stack_info=True) response_data = {"data": {"record": record}} current_app.json_encoder = ExpungeModelEncoder return response_data #Json-encoding happens automatically here
def put_from_user_id(user_id): user_db_data = user_db_util.read(g.database, user_id) if not user_db_data: error(404, "User id not recognized.") if not current_user.is_admin and current_user.user_id != user_id: error(403, "Logged in user not admin and doesn't match requested user id.") data = request.get_json() if data is None: error(400, "No json data in request body") if not any([key in data.keys() for key in ["email", "name", "group_name", "password", "admin"]]): error(400, "Json data must define one or more of: \ email, name, group_name, password, admin") if ("admin" in data.keys()) and (data["admin"] is True) and ( not current_user.is_admin): error(403, "Logged in user can not grant self admin privileges.") if "password" in data.keys(): if len(data["password"]) < 8: error(422, "New password is less than 8 characters long.") data["hashed_password"] = generate_password_hash(data["password"]) try: update_user_result = user_db_util.update( g.database, user_id, data) except UniqueViolation: error(422, "User with that email address already exists") if update_user_result is None: # Returns None if the user doesn't exist. We already checked this, # but if it still fails, throw 404 error(404, "User id not recognized") response_data = { "user_id": update_user_result["user_id"], "email": update_user_result["email"], "admin": update_user_result["admin"], "name": update_user_result["name"], "group_name": update_user_result["group_name"], "timestamp": update_user_result["date_modified"] } return jsonify(response_data), 200
def check_data_fields(request_json, required_fields): if not all([field in request_json.keys() for field in required_fields]): error(400, "missing one or more required fields: " + str(required_fields))
def authorized(f, admin_required, *args, **kwargs): try: auth_hdr = request.headers.get('Authorization') if auth_hdr == None: error(401, 'Missing Authorization header') auth_hdr_split = auth_hdr.split("Bearer") if not len(auth_hdr_split) == 2: error(401, 'Malformed auth token string, should be: "Bearer [auth_string]"') payload = jwt.decode( auth_hdr_split[1].strip(), current_app.config.get('JWT_SECRET_KEY') ) user_data = get_user_by_id(g.database, payload['sub']) if user_data == []: error(401, 'Invalid auth token claim') if admin_required and not user_data['admin']: error(403, 'Logged in user not admin') #no endpoint code uses the logged in user_id so this is commented for now. This may change. #g.logged_in_user_id = user_data['user_id'] return f(*args, **kwargs) except ( jwt.exceptions.InvalidTokenError, jwt.exceptions.InvalidSignatureError, ): error(401, 'Invalid auth token, signature verification failed') except jwt.exceptions.ExpiredSignatureError: error(401, 'Auth token expired')
def authorized(f, admin_required, *args, **kwargs): try: auth_hdr = request.headers.get('Authorization') if auth_hdr is None: error(401, 'Missing Authorization header') auth_hdr_split = auth_hdr.split("Bearer") if not len(auth_hdr_split) == 2: error( 401, 'Malformed auth token string, \ should be: "Bearer [auth_string]"') payload = jwt.decode(auth_hdr_split[1].strip(), current_app.config.get('JWT_SECRET_KEY'), algorithms=["HS256"]) user_data = user.read(g.database, payload['sub']) if user_data == []: error(401, 'Invalid auth token claim') if admin_required and not user_data['admin']: error(403, 'Logged in user not admin') g.logged_in_user_id = user_data['user_id'] g.logged_in_user_is_admin = user_data['admin'] return f(*args, **kwargs) except ( jwt.exceptions.InvalidTokenError, jwt.exceptions.InvalidSignatureError, ): error(401, 'Invalid auth token, signature verification failed') except jwt.exceptions.ExpiredSignatureError: error(401, 'Auth token expired')
def post(self): request_data = request.get_json() if request_data is None: error(400, "No json data in request body") check_data_fields( request_data, ["first_name", "last_name", "middle_name", "birth_date"]) cipher = DataCipher(key=current_app.config.get("JWT_SECRET_KEY")) decrypted_credentials = cipher.decrypt(request.cookies["oeci_token"]) crawler = Crawler() login_result = (decrypted_credentials["oeci_username"] == "username" and decrypted_credentials["oeci_password"] == "password") if login_result is False: error(401, "Attempted login to OECI failed") response_data = json.loads("""{"data": {"record": { "total_balance_due": 4550.4, "cases": [ { "name": "Doe, John D", "birth_year": 1943, "case_number": "X0001", "citation_number": "C0001", "location": "Multnomah", "date": "Sat, 23 Mar 1963 00:00:00 GMT", "violation_type": "Offense Misdemeanor", "current_status": "Closed", "charges": [ { "name": "Driving Uninsured", "statute": "806010", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Convicted - Failure to Appear" }, "expungement_result": { "type_eligibility": false, "type_eligibility_reason": "Ineligible under 137.225(5)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Violation Driving While Suspended or Revoked", "statute": "811175", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Failure to Obey Traffic Control Device", "statute": "811265", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } } ], "balance_due": 1516.8, "case_detail_link": "https://publicaccess.courts.oregon.gov/PublicAccessLogin/CaseDetail.aspx?CaseID=X0001" }, { "name": "Doe, John D", "birth_year": 1943, "case_number": "X0002", "citation_number": "C0002", "location": "Multnomah", "date": "Thu, 11 Apr 1963 00:00:00 GMT", "violation_type": "Offense Felony", "current_status": "Closed", "charges": [ { "name": "Driving Uninsured", "statute": "806010", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Convicted - Failure to Appear" }, "expungement_result": { "type_eligibility": false, "type_eligibility_reason": "Ineligible under 137.225(5)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Violation Driving While Suspended or Revoked", "statute": "811175", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Failure to Obey Traffic Control Device", "statute": "811265", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } } ], "balance_due": 1516.8, "case_detail_link": "https://publicaccess.courts.oregon.gov/PublicAccessLogin/CaseDetail.aspx?CaseID=X0002" }, { "name": "Doe, John D", "birth_year": 1943, "case_number": "X0003", "citation_number": "", "location": "Multnomah", "date": "Sun, 01 Apr 2012 00:00:00 GMT", "violation_type": "Offense Misdemeanor", "current_status": "Closed", "charges": [ { "name": "Driving Uninsured", "statute": "806010", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Convicted - Failure to Appear" }, "expungement_result": { "type_eligibility": false, "type_eligibility_reason": "Ineligible under 137.225(5)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Violation Driving While Suspended or Revoked", "statute": "811175", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } }, { "name": "Failure to Obey Traffic Control Device", "statute": "811265", "level": "Class B Felony", "date": "Sun, 12 Mar 2017 00:00:00 GMT", "disposition": { "date": "Mon, 12 Jun 2017 00:00:00 GMT", "ruling": "Dismissed" }, "expungement_result": { "type_eligibility": true, "type_eligibility_reason": "Eligible under 137.225(1)(b)", "time_eligibility": null, "time_eligibility_reason": null, "date_of_eligibility": null } } ], "balance_due": 1516.8, "case_detail_link": "https://publicaccess.courts.oregon.gov/PublicAccessLogin/CaseDetail.aspx?CaseID=X0003" } ], "errors": [] } }}""") return response_data #Json-encoding happens automatically here