async def test_42cbae986650_upgrade(resource_path): # before "Add policy_id and revoke columns to request table" migration alembic_main(["--raiseerr", "downgrade", "c0a92da5ac69"]) # insert a request uuid = "571c6a1a-f21f-11ea-adc1-0242ac120002" date = str(datetime.now()) sql_resource_path = resource_path.replace("'", "''") # escape single quotes insert_stmt = f"INSERT INTO requests(\"request_id\", \"username\", \"resource_path\", \"resource_id\", \"resource_display_name\", \"status\", \"created_time\", \"updated_time\") VALUES ('{uuid}', 'username', '{sql_resource_path}', 'my_resource', 'My Resource', 'DRAFT', '{date}', '{date}')" await db.scalar(db.text(insert_stmt)) # check that the request data was inserted correctly data = await db.all(db.text("SELECT * FROM requests")) request = {k: str(v) for row in data for k, v in row.items()} assert request == { "request_id": uuid, "username": "******", "resource_id": "my_resource", "resource_display_name": "My Resource", "status": "DRAFT", "resource_path": resource_path, "created_time": date, "updated_time": date, } # run "Add policy_id and revoke columns to request table" migration alembic_main(["--raiseerr", "upgrade", "42cbae986650"]) # check that the migration updated the request data correctly data = await db.all(db.text("SELECT * FROM requests")) assert len(data) == 1 request = {k: v for k, v in data[0].items()} assert resource_path not in request assert request["policy_id"] == get_auto_policy_id_for_resource_path(resource_path) assert request["revoke"] == False
def upgrade(): # get the list of existing policies from Arborist if not config["LOCAL_MIGRATION"]: existing_policies = list_policies(arborist_client) # add the new columns op.add_column("requests", sa.Column("policy_id", sa.String())) op.add_column("requests", sa.Column("revoke", Boolean)) # get the list of existing resource_paths in the database connection = op.get_bind() results = connection.execute("SELECT resource_path FROM requests").fetchall() existing_resource_paths = set(r[0] for r in results) # add the `policy_id` corresponding to each row's `resource_path` # and default `revoke` to False for resource_path in existing_resource_paths: policy_id = get_auto_policy_id_for_resource_path(resource_path) if not config["LOCAL_MIGRATION"] and policy_id not in existing_policies: create_arborist_policy(arborist_client, resource_path) connection.execute( f"UPDATE requests SET policy_id='{escape(policy_id)}', revoke=False WHERE resource_path='{escape(resource_path)}'" ) # now that there are no null values, make the columns non-nullable op.alter_column("requests", "policy_id", nullable=False) op.alter_column("requests", "revoke", nullable=False) # drop the `resource_path` column, replaced by `policy_id` op.drop_column("requests", "resource_path")
def test_create_request_with_redirect_resource_paths(client): """ When a redirect is configured for the requested resource, a redirect URL should be returned to the client. (creating with resource_paths) """ # create a request data = { "username": "******", "resource_path": "/resource-with-redirect/resource", "resource_id": "uniqid", "resource_display_name": "My Resource", } res = client.post("/request", json=data) assert res.status_code == 201, res.text request_data = res.json() request_id = request_data.get("request_id") assert request_id, "POST /request did not return a request_id" assert request_data == { "request_id": request_id, "username": data["username"], "policy_id": get_auto_policy_id_for_resource_path(data["resource_path"]), "resource_id": data["resource_id"], "resource_display_name": data["resource_display_name"], "status": config["DEFAULT_INITIAL_STATUS"], "redirect_url": f"http://localhost?something=&request_id={request_id}&resource_id={data['resource_id']}&resource_display_name=My+Resource", # just ensure revoke, created_time and updated_time are there: "revoke": False, "created_time": request_data["created_time"], "updated_time": request_data["updated_time"], }
def test_create_request_without_username(client): """ When a username is not provided in the body, the request is created using the username from the provided access token. """ fake_jwt = "1.2.3" # create a request data = { "resource_path": "/my/resource", "resource_id": "uniqid", "resource_display_name": "My Resource", } res = client.post("/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 201, res.text request_data = res.json() request_id = request_data.get("request_id") assert request_id, "POST /request did not return a request_id" assert request_data == { "request_id": request_id, "username": "******", # username from access_token_patcher "policy_id": get_auto_policy_id_for_resource_path(data["resource_path"]), "resource_id": data["resource_id"], "resource_display_name": data["resource_display_name"], "status": config["DEFAULT_INITIAL_STATUS"], # just ensure revoke, created_time and updated_time are there: "revoke": False, "created_time": request_data["created_time"], "updated_time": request_data["updated_time"], }
def list_policies_patcher(test_data): """ This fixture patches the list_policies method with a mock implementation based on the test_data provided which is a dictionary consisting of resource_path(s) and policy_id wherever appropriate """ resource_paths = (test_data["resource_paths"] if "resource_paths" in test_data else [test_data["resource_path"]]) expanded_permissions = (test_data["permissions"] if "permissions" in test_data else [{ "id": permission, "description": "", "action": { "service": "*", "method": permission, }, } for permission in ["reader", "storage_reader"]]) policy_id = (test_data["policy_id"] if "policy_id" in test_data else get_auto_policy_id_for_resource_path(resource_paths[0])) future = asyncio.Future() future.set_result({ "policies": [ { "id": policy_id, "resource_paths": resource_paths, "roles": [{ "id": "reader", "description": "", "permissions": expanded_permissions, }], }, ] }) list_policies_mock = MagicMock() list_policies_mock.return_value = future policy_expand_patch = patch( "requestor.routes.query.arborist.list_policies", list_policies_mock) policy_expand_patch.start() yield policy_expand_patch.stop()
def test_create_get_and_list_request(client): fake_jwt = "1.2.3" # list requests: empty res = client.get("/request", headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 200 assert res.json() == [] # create a request data = { "username": "******", "resource_path": "/my/resource", "resource_id": "uniqid", "resource_display_name": "My Resource", } res = client.post( "/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"} ) assert res.status_code == 201, res.text request_data = res.json() request_id = request_data.get("request_id") assert request_id, "POST /request did not return a request_id" assert request_data == { "request_id": request_id, "username": data["username"], "policy_id": get_auto_policy_id_for_resource_path(data["resource_path"]), "resource_id": data["resource_id"], "resource_display_name": data["resource_display_name"], "status": config["DEFAULT_INITIAL_STATUS"], # just ensure revoke, created_time and updated_time are there: "revoke": False, "created_time": request_data["created_time"], "updated_time": request_data["updated_time"], } # get the request res = client.get(f"/request/{request_id}") assert res.status_code == 200, res.text assert res.json() == request_data # list requests res = client.get("/request", headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 200, res.text assert res.json() == [request_data]
def test_create_duplicate_request(client, data): """ Users can only request access to a resource once. (username, resource_path) should be unique, except if other requests statuses are in DRAFT_STATUSES or FINAL_STATUSES. """ fake_jwt = "1.2.3" # create a request data = { "username": "******", "resource_path": "/my/resource", "resource_id": "uniqid", "resource_display_name": "My Resource", } res = client.post("/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 201, res.text request_data = res.json() request_id = request_data.get("request_id") assert request_id, "POST /request did not return a request_id" assert request_data == { "request_id": request_id, "username": data["username"], "policy_id": get_auto_policy_id_for_resource_path(data["resource_path"]), "resource_id": data["resource_id"], "resource_display_name": data["resource_display_name"], "status": config["DEFAULT_INITIAL_STATUS"], # just ensure revoke, created_time and updated_time are there: "revoke": False, "created_time": request_data["created_time"], "updated_time": request_data["updated_time"], } # create a request with the same username and resource_path. # since the previous request is still a draft, it should work. res = client.post("/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 201, res.text new_request_id = request_data.get("request_id") assert new_request_id == request_id # update the orignal request's status to a non-final, non-draft status res = client.put(f"/request/{request_id}", json={"status": "INTERMEDIATE_STATUS"}) assert res.status_code == 200, res.text # attempt to create a request with the same username and resource_path. # it should not work: the previous request is in progress. res = client.post("/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 409, res.text # update the orignal request's status to a final status status = config["FINAL_STATUSES"][-1] res = client.put(f"/request/{request_id}", json={"status": status}) assert res.status_code == 200, res.text # create a request with the same username and resource_path. # now it should work: the previous request is not in progress anymore. res = client.post("/request", json=data, headers={"Authorization": f"bearer {fake_jwt}"}) assert res.status_code == 201, res.text
def test_get_auto_policy_id_for_resource_path(client): # single resource_path without policy_id or role_ids resource_path = "/study/123456" assert (get_auto_policy_id_for_resource_path(resource_path) == "study.123456_accessor")