Skip to content

Commit 5e82115

Browse files
committed
fix
1 parent 8db80a6 commit 5e82115

File tree

5 files changed

+209
-44
lines changed

5 files changed

+209
-44
lines changed

Diff for: backend/ee/onyx/server/middleware/tenant_tracking.py

+3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ async def set_tenant_id(
2525
) -> Response:
2626
try:
2727
if MULTI_TENANT:
28+
print("Getting tenant id")
2829
tenant_id = await _get_tenant_id_from_request(request, logger)
30+
print("Tenant id", tenant_id)
31+
print("Request cookies", request.cookies)
2932
else:
3033
tenant_id = POSTGRES_DEFAULT_SCHEMA
3134

Diff for: backend/tests/integration/common_utils/managers/chat.py

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def send_message(
7575
alternate_assistant_id=alternate_assistant_id,
7676
use_existing_user_message=use_existing_user_message,
7777
)
78+
print(user_performing_action.headers)
79+
print(user_performing_action.cookies)
7880

7981
response = requests.post(
8082
f"{API_SERVER_URL}/chat/send-message",
@@ -83,6 +85,7 @@ def send_message(
8385
if user_performing_action
8486
else GENERAL_HEADERS,
8587
stream=True,
88+
cookies=user_performing_action.cookies,
8689
)
8790

8891
return ChatSessionManager.analyze_response(response)

Diff for: backend/tests/integration/multitenant_tests/invitation/invite_various_organizations.py renamed to backend/tests/integration/multitenant_tests/invitation/test_user_invitation.py

+46-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
INVITED_BASIC_USER_EMAIL = "[email protected]"
77

88

9-
def test_user_invitation_flow(reset_multitenant: None) -> None:
9+
def test_admin_can_invite_users(reset_multitenant: None) -> None:
10+
"""Test that an admin can invite both registered and non-registered users."""
1011
# Create first user (admin)
1112
admin_user: DATestUser = UserManager.create(name="admin")
1213
assert UserManager.is_role(admin_user, UserRole.ADMIN)
@@ -19,16 +20,45 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
1920
UserManager.invite_user(invited_user.email, admin_user)
2021
UserManager.invite_user(INVITED_BASIC_USER_EMAIL, admin_user)
2122

23+
# Verify users are in the invited users list
24+
invited_users = UserManager.get_invited_users(admin_user)
25+
assert invited_user.email in [
26+
user.email for user in invited_users
27+
], f"User {invited_user.email} not found in invited users list"
28+
29+
30+
def test_non_registered_user_gets_basic_role(reset_multitenant: None) -> None:
31+
"""Test that a non-registered user gets a BASIC role when they register after being invited."""
32+
# Create admin user
33+
admin_user: DATestUser = UserManager.create(name="admin")
34+
assert UserManager.is_role(admin_user, UserRole.ADMIN)
35+
36+
# Admin user invites a non-registered user
37+
UserManager.invite_user(INVITED_BASIC_USER_EMAIL, admin_user)
38+
39+
# Non-registered user registers
2240
invited_basic_user: DATestUser = UserManager.create(
2341
name=INVITED_BASIC_USER, email=INVITED_BASIC_USER_EMAIL
2442
)
2543
assert UserManager.is_role(invited_basic_user, UserRole.BASIC)
2644

27-
# Verify the user is in the invited users list
28-
invited_users = UserManager.get_invited_users(admin_user)
29-
assert invited_user.email in [
30-
user.email for user in invited_users
31-
], f"User {invited_user.email} not found in invited users list"
45+
46+
def test_user_can_accept_invitation(reset_multitenant: None) -> None:
47+
"""Test that a user can accept an invitation and join the organization with BASIC role."""
48+
# Create admin user
49+
admin_user: DATestUser = UserManager.create(name="admin")
50+
assert UserManager.is_role(admin_user, UserRole.ADMIN)
51+
52+
# Create a user to be invited
53+
invited_user_email = "[email protected]"
54+
55+
# Admin user invites the user
56+
UserManager.invite_user(invited_user_email, admin_user)
57+
58+
# User registers with the same email as the invitation
59+
invited_user: DATestUser = UserManager.create(
60+
name="invited_user", email=invited_user_email
61+
)
3262

3363
# Get user info to check tenant information
3464
user_info = UserManager.get_user_info(invited_user)
@@ -41,16 +71,17 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
4171
)
4272
assert invited_tenant_id is not None, "Expected to find an invitation tenant_id"
4373

74+
# User accepts invitation
4475
UserManager.accept_invitation(invited_tenant_id, invited_user)
4576

46-
# Get updated user info after accepting invitation
47-
updated_user_info = UserManager.get_user_info(invited_user)
77+
# User needs to reauthenticate after accepting invitation
78+
# Simulate this by creating a new user instance with the same credentials
79+
authenticated_user: DATestUser = UserManager.create(
80+
name="invited_user", email=invited_user_email
81+
)
4882

49-
# Verify the user is no longer in the invited users list
50-
updated_invited_users = UserManager.get_invited_users(admin_user)
51-
assert invited_user.email not in [
52-
user.email for user in updated_invited_users
53-
], f"User {invited_user.email} should not be in invited users list after accepting"
83+
# Get updated user info after accepting invitation and reauthenticating
84+
updated_user_info = UserManager.get_user_info(authenticated_user)
5485

5586
# Verify the user has BASIC role in the organization
5687
assert (
@@ -64,7 +95,7 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
6495

6596
# Check if the invited user is in the list of users with BASIC role
6697
invited_user_emails = [user.email for user in user_page.items]
67-
assert invited_user.email in invited_user_emails, (
68-
f"User {invited_user.email} not found in the list of basic users "
98+
assert invited_user_email in invited_user_emails, (
99+
f"User {invited_user_email} not found in the list of basic users "
69100
f"in the organization. Available users: {invited_user_emails}"
70101
)

Diff for: backend/tests/integration/multitenant_tests/syncing/test_search_permissions.py

+112-27
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
from tests.integration.common_utils.test_models import DATestUser
1212

1313

14-
def test_multi_tenant_access_control(reset_multitenant: None) -> None:
15-
# Creating an admin user (first user created is automatically an admin and also proviions the tenant
14+
def setup_test_tenants(reset_multitenant: None):
15+
"""Helper function to set up test tenants with documents and users."""
16+
# Creating an admin user for Tenant 1
1617
admin_user1: DATestUser = UserManager.create(
1718
1819
)
19-
2020
assert UserManager.is_role(admin_user1, UserRole.ADMIN)
2121

2222
# Create Tenant 2 and its Admin User
@@ -35,6 +35,16 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
3535
api_key_1.headers.update(admin_user1.headers)
3636
LLMProviderManager.create(user_performing_action=admin_user1)
3737

38+
# Create connectors for Tenant 2
39+
cc_pair_2: DATestCCPair = CCPairManager.create_from_scratch(
40+
user_performing_action=admin_user2,
41+
)
42+
api_key_2: DATestAPIKey = APIKeyManager.create(
43+
user_performing_action=admin_user2,
44+
)
45+
api_key_2.headers.update(admin_user2.headers)
46+
LLMProviderManager.create(user_performing_action=admin_user2)
47+
3848
# Seed documents for Tenant 1
3949
cc_pair_1.documents = []
4050
doc1_tenant1 = DocumentManager.seed_doc_with_content(
@@ -49,16 +59,6 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
4959
)
5060
cc_pair_1.documents.extend([doc1_tenant1, doc2_tenant1])
5161

52-
# Create connectors for Tenant 2
53-
cc_pair_2: DATestCCPair = CCPairManager.create_from_scratch(
54-
user_performing_action=admin_user2,
55-
)
56-
api_key_2: DATestAPIKey = APIKeyManager.create(
57-
user_performing_action=admin_user2,
58-
)
59-
api_key_2.headers.update(admin_user2.headers)
60-
LLMProviderManager.create(user_performing_action=admin_user2)
61-
6262
# Seed documents for Tenant 2
6363
cc_pair_2.documents = []
6464
doc1_tenant2 = DocumentManager.seed_doc_with_content(
@@ -84,21 +84,36 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
8484
user_performing_action=admin_user2
8585
)
8686

87+
return {
88+
"admin_user1": admin_user1,
89+
"admin_user2": admin_user2,
90+
"chat_session1": chat_session1,
91+
"chat_session2": chat_session2,
92+
"tenant1_doc_ids": tenant1_doc_ids,
93+
"tenant2_doc_ids": tenant2_doc_ids,
94+
}
95+
96+
97+
def test_tenant1_can_access_own_documents(reset_multitenant: None) -> None:
98+
"""Test that Tenant 1 can access its own documents but not Tenant 2's."""
99+
test_data = setup_test_tenants(reset_multitenant)
100+
87101
# User 1 sends a message and gets a response
88102
response1 = ChatSessionManager.send_message(
89-
chat_session_id=chat_session1.id,
103+
chat_session_id=test_data["chat_session1"].id,
90104
message="What is in Tenant 1's documents?",
91-
user_performing_action=admin_user1,
105+
user_performing_action=test_data["admin_user1"],
92106
)
107+
93108
# Assert that the search tool was used
94109
assert response1.tool_name == "run_search"
95110

96111
response_doc_ids = {doc["document_id"] for doc in response1.tool_result or []}
97-
assert tenant1_doc_ids.issubset(
112+
assert test_data["tenant1_doc_ids"].issubset(
98113
response_doc_ids
99114
), "Not all Tenant 1 document IDs are in the response"
100115
assert not response_doc_ids.intersection(
101-
tenant2_doc_ids
116+
test_data["tenant2_doc_ids"]
102117
), "Tenant 2 document IDs should not be in the response"
103118

104119
# Assert that the contents are correct
@@ -107,21 +122,28 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
107122
for doc in response1.tool_result or []
108123
), "Tenant 1 Document Content not found in any document"
109124

125+
126+
def test_tenant2_can_access_own_documents(reset_multitenant: None) -> None:
127+
"""Test that Tenant 2 can access its own documents but not Tenant 1's."""
128+
test_data = setup_test_tenants(reset_multitenant)
129+
110130
# User 2 sends a message and gets a response
111131
response2 = ChatSessionManager.send_message(
112-
chat_session_id=chat_session2.id,
132+
chat_session_id=test_data["chat_session2"].id,
113133
message="What is in Tenant 2's documents?",
114-
user_performing_action=admin_user2,
134+
user_performing_action=test_data["admin_user2"],
115135
)
136+
116137
# Assert that the search tool was used
117138
assert response2.tool_name == "run_search"
139+
118140
# Assert that the tool_result contains Tenant 2's documents
119141
response_doc_ids = {doc["document_id"] for doc in response2.tool_result or []}
120-
assert tenant2_doc_ids.issubset(
142+
assert test_data["tenant2_doc_ids"].issubset(
121143
response_doc_ids
122144
), "Not all Tenant 2 document IDs are in the response"
123145
assert not response_doc_ids.intersection(
124-
tenant1_doc_ids
146+
test_data["tenant1_doc_ids"]
125147
), "Tenant 1 document IDs should not be in the response"
126148

127149
# Assert that the contents are correct
@@ -130,28 +152,91 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
130152
for doc in response2.tool_result or []
131153
), "Tenant 2 Document Content not found in any document"
132154

155+
156+
def test_tenant1_cannot_access_tenant2_documents(reset_multitenant: None) -> None:
157+
"""Test that Tenant 1 cannot access Tenant 2's documents."""
158+
test_data = setup_test_tenants(reset_multitenant)
159+
133160
# User 1 tries to access Tenant 2's documents
134161
response_cross = ChatSessionManager.send_message(
135-
chat_session_id=chat_session1.id,
162+
chat_session_id=test_data["chat_session1"].id,
136163
message="What is in Tenant 2's documents?",
137-
user_performing_action=admin_user1,
164+
user_performing_action=test_data["admin_user1"],
138165
)
166+
139167
# Assert that the search tool was used
140168
assert response_cross.tool_name == "run_search"
169+
141170
# Assert that the tool_result is empty or does not contain Tenant 2's documents
142171
response_doc_ids = {doc["document_id"] for doc in response_cross.tool_result or []}
172+
143173
# Ensure none of Tenant 2's document IDs are in the response
144-
assert not response_doc_ids.intersection(tenant2_doc_ids)
174+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
175+
176+
177+
def test_tenant2_cannot_access_tenant1_documents(reset_multitenant: None) -> None:
178+
"""Test that Tenant 2 cannot access Tenant 1's documents."""
179+
test_data = setup_test_tenants(reset_multitenant)
145180

146181
# User 2 tries to access Tenant 1's documents
147182
response_cross2 = ChatSessionManager.send_message(
148-
chat_session_id=chat_session2.id,
183+
chat_session_id=test_data["chat_session2"].id,
149184
message="What is in Tenant 1's documents?",
150-
user_performing_action=admin_user2,
185+
user_performing_action=test_data["admin_user2"],
151186
)
187+
152188
# Assert that the search tool was used
153189
assert response_cross2.tool_name == "run_search"
190+
154191
# Assert that the tool_result is empty or does not contain Tenant 1's documents
155192
response_doc_ids = {doc["document_id"] for doc in response_cross2.tool_result or []}
193+
156194
# Ensure none of Tenant 1's document IDs are in the response
157-
assert not response_doc_ids.intersection(tenant1_doc_ids)
195+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])
196+
197+
198+
def test_multi_tenant_access_control(reset_multitenant: None) -> None:
199+
"""Legacy test for multi-tenant access control."""
200+
test_data = setup_test_tenants(reset_multitenant)
201+
202+
# User 1 sends a message and gets a response with only Tenant 1's documents
203+
response1 = ChatSessionManager.send_message(
204+
chat_session_id=test_data["chat_session1"].id,
205+
message="What is in Tenant 1's documents?",
206+
user_performing_action=test_data["admin_user1"],
207+
)
208+
assert response1.tool_name == "run_search"
209+
response_doc_ids = {doc["document_id"] for doc in response1.tool_result or []}
210+
assert test_data["tenant1_doc_ids"].issubset(response_doc_ids)
211+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
212+
213+
# User 2 sends a message and gets a response with only Tenant 2's documents
214+
response2 = ChatSessionManager.send_message(
215+
chat_session_id=test_data["chat_session2"].id,
216+
message="What is in Tenant 2's documents?",
217+
user_performing_action=test_data["admin_user2"],
218+
)
219+
assert response2.tool_name == "run_search"
220+
response_doc_ids = {doc["document_id"] for doc in response2.tool_result or []}
221+
assert test_data["tenant2_doc_ids"].issubset(response_doc_ids)
222+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])
223+
224+
# User 1 tries to access Tenant 2's documents and fails
225+
response_cross = ChatSessionManager.send_message(
226+
chat_session_id=test_data["chat_session1"].id,
227+
message="What is in Tenant 2's documents?",
228+
user_performing_action=test_data["admin_user1"],
229+
)
230+
assert response_cross.tool_name == "run_search"
231+
response_doc_ids = {doc["document_id"] for doc in response_cross.tool_result or []}
232+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
233+
234+
# User 2 tries to access Tenant 1's documents and fails
235+
response_cross2 = ChatSessionManager.send_message(
236+
chat_session_id=test_data["chat_session2"].id,
237+
message="What is in Tenant 1's documents?",
238+
user_performing_action=test_data["admin_user2"],
239+
)
240+
assert response_cross2.tool_name == "run_search"
241+
response_doc_ids = {doc["document_id"] for doc in response_cross2.tool_result or []}
242+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])

0 commit comments

Comments
 (0)