123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- import uuid
- import pytest
- from r2r import R2RClient, R2RException
- # @pytest.fixture # (scope="session")
- # def client(config):
- # """A client logged in as a superuser."""
- # client = R2RClient(config.base_url)
- # client.users.login(config.superuser_email, config.superuser_password)
- # yield client
- @pytest.fixture
- def normal_user_client(mutable_client):
- """Create a normal user and log in with that user."""
- # client = R2RClient(config.base_url)
- email = f"normal_{uuid.uuid4()}@test.com"
- password = "normal_password"
- user_resp = mutable_client.users.create(email, password)
- mutable_client.users.login(email, password)
- yield mutable_client
- # Cleanup: Try deleting the normal user if exists
- try:
- mutable_client.users.login(email, password)
- mutable_client.users.delete(
- mutable_client.users.me()["results"]["id"], password
- )
- except R2RException:
- pass
- @pytest.fixture
- def another_normal_user_client(config):
- """Create another normal user and log in with that user."""
- client = R2RClient(config.base_url)
- email = f"another_{uuid.uuid4()}@test.com"
- password = "another_password"
- user_resp = client.users.create(email, password)
- client.users.login(email, password)
- yield client
- # Cleanup: Try deleting the user if exists
- try:
- client.users.login(email, password)
- client.users.delete(client.users.me()["results"]["id"], password)
- except R2RException:
- pass
- @pytest.fixture
- def user_owned_collection(normal_user_client):
- """Create a collection owned by the normal user."""
- resp = normal_user_client.collections.create(
- name="User Owned Collection",
- description="A collection owned by a normal user",
- )
- coll_id = resp["results"]["id"]
- yield coll_id
- # Cleanup
- try:
- normal_user_client.collections.delete(coll_id)
- except R2RException:
- pass
- @pytest.fixture
- def superuser_owned_collection(client):
- """Create a collection owned by the superuser."""
- resp = client.collections.create(
- name="Superuser Owned Collection",
- description="A collection owned by superuser",
- )
- coll_id = resp["results"]["id"]
- yield coll_id
- # Cleanup
- try:
- client.collections.delete(coll_id)
- except R2RException:
- pass
- def test_non_member_cannot_view_collection(
- normal_user_client, superuser_owned_collection
- ):
- """A normal user (not a member of a superuser-owned collection) tries to view it."""
- # The normal user is not added to the superuser collection, should fail
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.collections.retrieve(superuser_owned_collection)
- assert (
- exc_info.value.status_code == 403
- ), "Non-member should not be able to view collection."
- def test_collection_owner_can_view_collection(
- normal_user_client, user_owned_collection
- ):
- """The owner should be able to view their own collection."""
- coll = normal_user_client.collections.retrieve(user_owned_collection)[
- "results"
- ]
- assert (
- coll["id"] == user_owned_collection
- ), "Owner cannot view their own collection."
- def test_collection_member_can_view_collection(
- client, normal_user_client, user_owned_collection
- ):
- """A user added to a collection should be able to view it."""
- # Create another user and add them to the user's collection
- new_user_email = f"temp_member_{uuid.uuid4()}@test.com"
- new_user_password = "temp_member_password"
- # Store normal user's email before any logouts
- normal_user_email = normal_user_client.users.me()["results"]["email"]
- # Create a new user and log in as them
- member_client = R2RClient(normal_user_client.base_url)
- member_client.users.create(new_user_email, new_user_password)
- member_client.users.login(new_user_email, new_user_password)
- member_id = member_client.users.me()["results"]["id"]
- # Owner adds the new user to the collection
- normal_user_client.users.logout()
- normal_user_client.users.login(normal_user_email, "normal_password")
- normal_user_client.collections.add_user(user_owned_collection, member_id)
- # The member now can view the collection
- coll = member_client.collections.retrieve(user_owned_collection)["results"]
- assert coll["id"] == user_owned_collection
- def test_non_owner_member_cannot_edit_collection(
- user_owned_collection, another_normal_user_client, normal_user_client
- ):
- """A member who is not the owner should not be able to edit the collection."""
- # Add another normal user to the owner's collection
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Another normal user tries to update collection
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.collections.update(
- user_owned_collection, name="Malicious Update"
- )
- assert (
- exc_info.value.status_code == 403
- ), "Non-owner member should not be able to edit."
- def test_non_owner_member_cannot_delete_collection(
- user_owned_collection, another_normal_user_client, normal_user_client
- ):
- """A member who is not the owner should not be able to delete the collection."""
- # Add the other user
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Another user tries to delete
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.collections.delete(user_owned_collection)
- assert (
- exc_info.value.status_code == 403
- ), "Non-owner member should not be able to delete."
- def test_non_owner_member_cannot_add_other_users(
- user_owned_collection, another_normal_user_client, normal_user_client
- ):
- """A member who is not the owner should not be able to add other users."""
- # Another user tries to add a third user
- third_email = f"third_user_{uuid.uuid4()}@test.com"
- third_password = "third_password"
- # Need to create third user as a superuser or owner
- normal_user_email = normal_user_client.users.me()["results"]["email"]
- normal_user_client.users.logout()
- # Login as normal user again
- # NOTE: We assume normal_password known here; in a real scenario, store it or use fixtures more dynamically
- # This code snippet assumes we have these credentials available.
- # If not, manage credentials store in fixture creation.
- normal_user_client.users.login(normal_user_email, "normal_password")
- third_user_resp = normal_user_client.users.create(
- third_email, third_password
- )
- third_user_id = third_user_resp["results"]["id"]
- # Add another user as a member
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Now, another_normal_user_client tries to add the third user
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.collections.add_user(
- user_owned_collection, third_user_id
- )
- assert (
- exc_info.value.status_code == 403
- ), "Non-owner member should not be able to add users."
- def test_owner_can_remove_member_from_collection(
- user_owned_collection, another_normal_user_client, normal_user_client
- ):
- """The owner should be able to remove a member from their collection."""
- # Add another user to the collection
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Remove them
- remove_resp = normal_user_client.collections.remove_user(
- user_owned_collection, another_user_id
- )["results"]
- assert remove_resp["success"], "Owner could not remove member."
- # The removed user should no longer have access
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.collections.retrieve(user_owned_collection)
- assert (
- exc_info.value.status_code == 403
- ), "Removed user still has access after removal."
- def test_superuser_can_access_any_collection(client, user_owned_collection):
- """A superuser should be able to view and edit any collection."""
- # Superuser can view
- coll = client.collections.retrieve(user_owned_collection)["results"]
- assert (
- coll["id"] == user_owned_collection
- ), "Superuser cannot view a user collection."
- # Superuser can also update
- updated = client.collections.update(
- user_owned_collection, name="Superuser Edit"
- )["results"]
- assert (
- updated["name"] == "Superuser Edit"
- ), "Superuser cannot edit collection."
- def test_unauthenticated_cannot_access_collections(
- config, user_owned_collection
- ):
- """An unauthenticated (no login) client should not access protected endpoints."""
- unauth_client = R2RClient(config.base_url)
- # we must CREATE + LOGIN as superuser is default user for unauth in basic config
- user_name = f"unauth_user_{uuid.uuid4()}@email.com"
- unauth_client.users.create(user_name, "unauth_password")
- unauth_client.users.login(user_name, "unauth_password")
- with pytest.raises(R2RException) as exc_info:
- unauth_client.collections.retrieve(user_owned_collection)
- assert (
- exc_info.value.status_code == 403
- ), "Unaurthorized user should get 403"
- def test_user_cannot_add_document_to_collection_they_cannot_edit(
- client, normal_user_client
- ):
- """A normal user who is just a member (not owner) of a collection should not be able to add documents."""
- # Create a collection as normal user (owner)
- resp = normal_user_client.collections.create(
- name="Owned by user", description="desc"
- )
- coll_id = resp["results"]["id"]
- # Create a second user and add them as member
- second_email = f"second_{uuid.uuid4()}@test.com"
- second_password = "pwd"
- client.users.logout()
- second_client = R2RClient(normal_user_client.base_url)
- second_client.users.create(second_email, second_password)
- second_client.users.login(second_email, second_password)
- second_id = second_client.users.me()["results"]["id"]
- # Owner adds second user as a member
- email_of_normal_user = normal_user_client.users.me()["results"]["email"]
- normal_user_client.users.logout()
- # Re-login owner (assuming we stored the original user's creds)
- # For demonstration, we assume we know the normal_user_client creds or re-use fixtures carefully.
- # In a real test environment, you'd maintain credentials more robustly.
- # Here we rely on the normal_user_client fixture being re-instantiated per test if needed.
- normal_user_client.users.login(email_of_normal_user, "normal_password")
- normal_user_client.collections.add_user(coll_id, second_id)
- # Create a document as owner
- doc_resp = normal_user_client.documents.create(raw_text="Test Document")
- doc_id = doc_resp["results"]["document_id"]
- # Now second user tries to add another document (which they do not have edit rights for)
- second_client.users.logout()
- second_client.users.login(second_email, second_password)
- # Another doc created by second user (just for attempt)
- doc2_resp = second_client.documents.create(raw_text="Doc by second user")
- doc2_id = doc2_resp["results"]["document_id"]
- # Second user tries to add their doc2_id to the owner’s collection
- with pytest.raises(R2RException) as exc_info:
- second_client.collections.add_document(coll_id, doc2_id)
- assert (
- exc_info.value.status_code == 403
- ), "Non-owner member should not add documents."
- # Cleanup
- normal_user_client.collections.delete(coll_id)
- def test_user_cannot_remove_document_from_collection_they_cannot_edit(
- normal_user_client,
- ):
- """A user who is just a member should not remove documents."""
- # Create a collection
- resp = normal_user_client.collections.create(
- name="Removable", description="desc"
- )
- coll_id = resp["results"]["id"]
- # Create a document in it
- doc_resp = normal_user_client.documents.create(raw_text="Doc in coll")
- doc_id = doc_resp["results"]["document_id"]
- normal_user_client.collections.add_document(coll_id, doc_id)
- # Create another user and add as member
- another_email = f"amember_{uuid.uuid4()}@test.com"
- another_password = "memberpwd"
- member_client = R2RClient(normal_user_client.base_url)
- member_client.users.create(another_email, another_password)
- member_client.users.login(another_email, another_password)
- member_id = member_client.users.me()["results"]["id"]
- user_email = normal_user_client.users.me()["results"]["email"]
- # Add member to collection
- normal_user_client.users.logout()
- normal_user_client.users.login(user_email, "normal_password")
- normal_user_client.collections.add_user(coll_id, member_id)
- # Member tries to remove the document
- with pytest.raises(R2RException) as exc_info:
- member_client.collections.remove_document(coll_id, doc_id)
- assert (
- exc_info.value.status_code == 403
- ), "Member should not remove documents."
- # Cleanup
- normal_user_client.collections.delete(coll_id)
- def test_normal_user_cannot_make_another_user_superuser(normal_user_client):
- """A normal user tries to update another user to superuser, should fail."""
- # Create another user
- email = f"regular_{uuid.uuid4()}@test.com"
- password = "not_superuser"
- new_user_resp = normal_user_client.users.create(email, password)
- new_user_id = new_user_resp["results"]["id"]
- # Try updating their superuser status
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.users.update(new_user_id, is_superuser=True)
- assert (
- exc_info.value.status_code == 403
- ), "Non-superuser should not grant superuser status."
- def test_normal_user_cannot_view_other_users_if_not_superuser(
- normal_user_client,
- ):
- """A normal user tries to list all users, should fail."""
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.users.list()
- assert (
- exc_info.value.status_code == 403
- ), "Non-superuser should not list all users."
- def test_normal_user_cannot_update_other_users_details(
- normal_user_client, client
- ):
- """A normal user tries to update another normal user's details."""
- # Create another normal user
- email = f"other_normal_{uuid.uuid4()}@test.com"
- password = "pwd123"
- client.users.logout()
- another_client = R2RClient(normal_user_client.base_url)
- another_client.users.create(email, password)
- another_client.users.login(email, password)
- another_user_id = another_client.users.me()["results"]["id"]
- another_client.users.logout()
- # Try to update as first normal user (not superuser, not same user)
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.users.update(another_user_id, name="Hacked Name")
- assert (
- exc_info.value.status_code == 403
- ), "Non-superuser should not update another user's info."
- # Additional Tests for Strengthened Coverage
- def test_owner_cannot_promote_member_to_superuser_via_collection(
- user_owned_collection, normal_user_client, another_normal_user_client
- ):
- """
- Ensures that being a collection owner doesn't confer the right
- to promote a user to superuser.
- """
- # Add another user to the collection
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Try to update the member's superuser status
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.users.update(another_user_id, is_superuser=True)
- assert (
- exc_info.value.status_code == 403
- ), "Collection owners should not grant superuser status."
- def test_member_cannot_view_other_users_info(
- user_owned_collection, normal_user_client, another_normal_user_client
- ):
- """
- A member (non-owner) of a collection should not be able to retrieve other users' details
- outside of their allowed scope.
- """
- # Add the other normal user as a member
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # As another_normal_user_client (a member), try to retrieve owner user details
- owner_id = normal_user_client.users.me()["results"]["id"]
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.users.retrieve(owner_id)
- assert (
- exc_info.value.status_code == 403
- ), "Members should not be able to view other users' details."
- def test_unauthenticated_user_cannot_join_collection(
- config, user_owned_collection
- ):
- """
- An unauthenticated user should not be able to join or view collections.
- """
- unauth_client = R2RClient(config.base_url)
- # we must CREATE + LOGIN as superuser is default user for unauth in basic config
- user_name = f"unauth_user_{uuid.uuid4()}@email.com"
- unauth_client.users.create(user_name, "unauth_password")
- unauth_client.users.login(user_name, "unauth_password")
- # No login performed here, client is unauthenticated
- with pytest.raises(R2RException) as exc_info:
- unauth_client.collections.retrieve(user_owned_collection)
- assert exc_info.value.status_code in [
- 401,
- 403,
- ], "Unauthenticated user should not access collections."
- def test_non_owner_cannot_remove_users_they_did_not_add(
- user_owned_collection, normal_user_client, another_normal_user_client
- ):
- """
- A member who is not the owner cannot remove other members from the collection.
- """
- # Add another user as a member
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Now try removing that user as another_normal_user_client
- with pytest.raises(R2RException) as exc_info:
- another_normal_user_client.collections.remove_user(
- user_owned_collection, another_user_id
- )
- assert (
- exc_info.value.status_code == 403
- ), "Non-owner member should not remove other users."
- def test_owner_cannot_access_deleted_member_info_after_removal(
- user_owned_collection, normal_user_client, another_normal_user_client
- ):
- """
- After the owner removes a user from the collection, ensure that attempts to
- perform collection-specific actions with that user fail.
- """
- # Add another user to the collection
- another_user_id = another_normal_user_client.users.me()["results"]["id"]
- normal_user_client.collections.add_user(
- user_owned_collection, another_user_id
- )
- # Remove them
- normal_user_client.collections.remove_user(
- user_owned_collection, another_user_id
- )
- # Now, try listing collections for that removed user (as owner),
- # if there's an endpoint that filters by user, to ensure no special access remains.
- # If no such endpoint exists, this test can be adapted to try another relevant action.
- # For demonstration, we might attempt to retrieve user details as owner:
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.users.retrieve(another_user_id)
- # We expect a 403 because normal_user_client is not superuser and not that user.
- assert (
- exc_info.value.status_code == 403
- ), "Owner should not access removed member's user info."
- def test_member_cannot_add_document_to_non_existent_collection(
- normal_user_client,
- ):
- """
- A member tries to add a document to a collection that doesn't exist.
- """
- fake_coll_id = str(uuid.uuid4())
- doc_resp = normal_user_client.documents.create(raw_text="Test Doc")
- doc_id = doc_resp["results"]["document_id"]
- with pytest.raises(R2RException) as exc_info:
- normal_user_client.collections.add_document(fake_coll_id, doc_id)
- assert exc_info.value.status_code in [
- 400,
- 404,
- ], "Expected error when adding doc to non-existent collection."
|