test_scim_fixed.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. """
  2. Fixed tests for SCIM 2.0 endpoints with proper authentication mocking
  3. """
  4. import json
  5. import pytest
  6. from unittest.mock import patch, MagicMock, Mock
  7. from fastapi.testclient import TestClient
  8. from datetime import datetime, timezone
  9. import time
  10. from open_webui.main import app
  11. from open_webui.models.users import UserModel
  12. from open_webui.models.groups import GroupModel
  13. class TestSCIMEndpointsFixed:
  14. """Test SCIM 2.0 endpoints with proper auth mocking"""
  15. @pytest.fixture
  16. def client(self):
  17. return TestClient(app)
  18. @pytest.fixture
  19. def admin_token(self):
  20. """Mock admin token for authentication"""
  21. return "mock-admin-token"
  22. @pytest.fixture
  23. def mock_admin_user(self):
  24. """Mock admin user"""
  25. return UserModel(
  26. id="admin-123",
  27. name="Admin User",
  28. email="admin@example.com",
  29. role="admin",
  30. profile_image_url="/user.png",
  31. created_at=1234567890,
  32. updated_at=1234567890,
  33. last_active_at=1234567890
  34. )
  35. @pytest.fixture
  36. def mock_user(self):
  37. """Mock regular user"""
  38. return UserModel(
  39. id="user-456",
  40. name="Test User",
  41. email="test@example.com",
  42. role="user",
  43. profile_image_url="/user.png",
  44. created_at=1234567890,
  45. updated_at=1234567890,
  46. last_active_at=1234567890
  47. )
  48. @pytest.fixture
  49. def mock_group(self):
  50. """Mock group"""
  51. return GroupModel(
  52. id="group-789",
  53. user_id="admin-123",
  54. name="Test Group",
  55. description="Test group description",
  56. user_ids=["user-456"],
  57. created_at=1234567890,
  58. updated_at=1234567890
  59. )
  60. @pytest.fixture
  61. def auth_headers(self, admin_token):
  62. """Authorization headers for requests"""
  63. return {"Authorization": f"Bearer {admin_token}"}
  64. @pytest.fixture
  65. def valid_token_data(self):
  66. """Valid token data"""
  67. return {
  68. "id": "admin-123",
  69. "email": "admin@example.com",
  70. "name": "Admin User",
  71. "role": "admin",
  72. "exp": int(time.time()) + 3600 # Valid for 1 hour
  73. }
  74. # Service Provider Config Tests (No auth required)
  75. def test_get_service_provider_config(self, client):
  76. """Test getting SCIM Service Provider Configuration"""
  77. response = client.get("/api/v1/scim/v2/ServiceProviderConfig")
  78. assert response.status_code == 200
  79. data = response.json()
  80. assert "schemas" in data
  81. assert data["schemas"] == ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"]
  82. assert "patch" in data
  83. assert data["patch"]["supported"] == True
  84. assert "filter" in data
  85. assert data["filter"]["supported"] == True
  86. # Mock the entire authentication dependency
  87. @patch('open_webui.routers.scim.get_scim_auth')
  88. @patch('open_webui.models.users.Users.get_users')
  89. @patch('open_webui.models.groups.Groups.get_groups_by_member_id')
  90. def test_get_users_with_mocked_auth(self, mock_get_groups, mock_get_users, mock_get_scim_auth, client, auth_headers, mock_user):
  91. """Test listing SCIM users with mocked authentication"""
  92. # Mock the authentication to always return True
  93. mock_get_scim_auth.return_value = True
  94. # Mock the database calls
  95. mock_get_users.return_value = {
  96. "users": [mock_user],
  97. "total": 1
  98. }
  99. mock_get_groups.return_value = []
  100. response = client.get("/api/v1/scim/v2/Users", headers=auth_headers)
  101. assert response.status_code == 200
  102. data = response.json()
  103. assert data["schemas"] == ["urn:ietf:params:scim:api:messages:2.0:ListResponse"]
  104. assert data["totalResults"] == 1
  105. assert data["itemsPerPage"] == 1
  106. assert data["startIndex"] == 1
  107. assert len(data["Resources"]) == 1
  108. user = data["Resources"][0]
  109. assert user["id"] == "user-456"
  110. assert user["userName"] == "test@example.com"
  111. assert user["displayName"] == "Test User"
  112. assert user["active"] == True
  113. # Alternative approach: Mock at the decode_token level
  114. def test_get_users_with_token_mock(self, client, auth_headers, mock_admin_user, mock_user, valid_token_data):
  115. """Test listing SCIM users with token decoding mocked"""
  116. with patch('open_webui.routers.scim.decode_token') as mock_decode_token, \
  117. patch('open_webui.models.users.Users.get_user_by_id') as mock_get_user_by_id, \
  118. patch('open_webui.models.users.Users.get_users') as mock_get_users, \
  119. patch('open_webui.models.groups.Groups.get_groups_by_member_id') as mock_get_groups:
  120. # Setup mocks
  121. mock_decode_token.return_value = valid_token_data
  122. mock_get_user_by_id.return_value = mock_admin_user
  123. mock_get_users.return_value = {
  124. "users": [mock_user],
  125. "total": 1
  126. }
  127. mock_get_groups.return_value = []
  128. response = client.get("/api/v1/scim/v2/Users", headers=auth_headers)
  129. assert response.status_code == 200
  130. data = response.json()
  131. assert data["totalResults"] == 1
  132. # Test authentication failures
  133. def test_unauthorized_access_no_header(self, client):
  134. """Test accessing SCIM endpoints without authentication header"""
  135. response = client.get("/api/v1/scim/v2/Users")
  136. assert response.status_code == 401
  137. def test_unauthorized_access_invalid_token(self, client):
  138. """Test accessing SCIM endpoints with invalid token"""
  139. with patch('open_webui.routers.scim.decode_token') as mock_decode_token:
  140. mock_decode_token.return_value = None # Invalid token
  141. response = client.get("/api/v1/scim/v2/Users", headers={"Authorization": "Bearer invalid-token"})
  142. assert response.status_code == 401
  143. def test_non_admin_access(self, client, mock_user):
  144. """Test accessing SCIM endpoints as non-admin user"""
  145. with patch('open_webui.routers.scim.decode_token') as mock_decode_token, \
  146. patch('open_webui.models.users.Users.get_user_by_id') as mock_get_user_by_id:
  147. # Mock token for non-admin user
  148. mock_decode_token.return_value = {"id": "user-456"}
  149. mock_get_user_by_id.return_value = mock_user # Non-admin user
  150. response = client.get("/api/v1/scim/v2/Users", headers={"Authorization": "Bearer user-token"})
  151. assert response.status_code == 403
  152. # Create user test with proper mocking
  153. @patch('open_webui.routers.scim.get_scim_auth')
  154. @patch('open_webui.models.users.Users.get_user_by_email')
  155. @patch('open_webui.models.users.Users.insert_new_user')
  156. def test_create_user(self, mock_insert_user, mock_get_user_by_email, mock_get_scim_auth, client, auth_headers):
  157. """Test creating a SCIM user"""
  158. mock_get_scim_auth.return_value = True
  159. mock_get_user_by_email.return_value = None # User doesn't exist
  160. new_user = UserModel(
  161. id="new-user-123",
  162. name="New User",
  163. email="newuser@example.com",
  164. role="user",
  165. profile_image_url="/user.png",
  166. created_at=1234567890,
  167. updated_at=1234567890,
  168. last_active_at=1234567890
  169. )
  170. mock_insert_user.return_value = new_user
  171. create_data = {
  172. "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  173. "userName": "newuser@example.com",
  174. "displayName": "New User",
  175. "emails": [{"value": "newuser@example.com", "primary": True}],
  176. "active": True
  177. }
  178. response = client.post("/api/v1/scim/v2/Users", headers=auth_headers, json=create_data)
  179. assert response.status_code == 201
  180. data = response.json()
  181. assert data["userName"] == "newuser@example.com"
  182. assert data["displayName"] == "New User"
  183. # Group tests
  184. @patch('open_webui.routers.scim.get_scim_auth')
  185. @patch('open_webui.models.groups.Groups.get_groups')
  186. @patch('open_webui.models.users.Users.get_user_by_id')
  187. def test_get_groups(self, mock_get_user_by_id, mock_get_groups, mock_get_scim_auth, client, auth_headers, mock_group, mock_user):
  188. """Test listing SCIM groups"""
  189. mock_get_scim_auth.return_value = True
  190. mock_get_groups.return_value = [mock_group]
  191. mock_get_user_by_id.return_value = mock_user
  192. response = client.get("/api/v1/scim/v2/Groups", headers=auth_headers)
  193. assert response.status_code == 200
  194. data = response.json()
  195. assert data["schemas"] == ["urn:ietf:params:scim:api:messages:2.0:ListResponse"]
  196. assert data["totalResults"] == 1
  197. assert len(data["Resources"]) == 1
  198. group = data["Resources"][0]
  199. assert group["id"] == "group-789"
  200. assert group["displayName"] == "Test Group"