Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    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({})
Exemple #5
0
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
Exemple #6
0
    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
Exemple #9
0
    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
Exemple #10
0
    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
Exemple #11
0
    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
Exemple #12
0
    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
Exemple #13
0
    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
Exemple #14
0
    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
Exemple #15
0
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
Exemple #16
0
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))
Exemple #17
0
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')
Exemple #18
0
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')
Exemple #19
0
    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