test_auth_ldap.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. """
  2. LDAP authentication tests for ArchiveBox.
  3. Tests LDAP configuration, validation, and integration with Django.
  4. Per CLAUDE.md: NO MOCKS, NO SKIPS - all tests use real code paths.
  5. """
  6. import os
  7. import sys
  8. import tempfile
  9. import unittest
  10. from pathlib import Path
  11. class TestLDAPConfig(unittest.TestCase):
  12. """Test LDAP configuration loading and validation."""
  13. def test_ldap_config_defaults(self):
  14. """Test that LDAP config loads with correct defaults."""
  15. from archivebox.config.ldap import LDAP_CONFIG
  16. # Check default values
  17. self.assertFalse(LDAP_CONFIG.LDAP_ENABLED)
  18. self.assertIsNone(LDAP_CONFIG.LDAP_SERVER_URI)
  19. self.assertIsNone(LDAP_CONFIG.LDAP_BIND_DN)
  20. self.assertIsNone(LDAP_CONFIG.LDAP_BIND_PASSWORD)
  21. self.assertIsNone(LDAP_CONFIG.LDAP_USER_BASE)
  22. self.assertEqual(LDAP_CONFIG.LDAP_USER_FILTER, "(uid=%(user)s)")
  23. self.assertEqual(LDAP_CONFIG.LDAP_USERNAME_ATTR, "username")
  24. self.assertEqual(LDAP_CONFIG.LDAP_FIRSTNAME_ATTR, "givenName")
  25. self.assertEqual(LDAP_CONFIG.LDAP_LASTNAME_ATTR, "sn")
  26. self.assertEqual(LDAP_CONFIG.LDAP_EMAIL_ATTR, "mail")
  27. self.assertFalse(LDAP_CONFIG.LDAP_CREATE_SUPERUSER)
  28. def test_ldap_config_validation_disabled(self):
  29. """Test that validation passes when LDAP is disabled."""
  30. from archivebox.config.ldap import LDAPConfig
  31. config = LDAPConfig(LDAP_ENABLED=False)
  32. is_valid, error_msg = config.validate_ldap_config()
  33. self.assertTrue(is_valid)
  34. self.assertEqual(error_msg, "")
  35. def test_ldap_config_validation_missing_fields(self):
  36. """Test that validation fails when required fields are missing."""
  37. from archivebox.config.ldap import LDAPConfig
  38. # Enable LDAP but don't provide required fields
  39. config = LDAPConfig(LDAP_ENABLED=True)
  40. is_valid, error_msg = config.validate_ldap_config()
  41. self.assertFalse(is_valid)
  42. self.assertIn("LDAP_* config options must all be set", error_msg)
  43. self.assertIn("LDAP_SERVER_URI", error_msg)
  44. self.assertIn("LDAP_BIND_DN", error_msg)
  45. self.assertIn("LDAP_BIND_PASSWORD", error_msg)
  46. self.assertIn("LDAP_USER_BASE", error_msg)
  47. def test_ldap_config_validation_complete(self):
  48. """Test that validation passes when all required fields are provided."""
  49. from archivebox.config.ldap import LDAPConfig
  50. config = LDAPConfig(
  51. LDAP_ENABLED=True,
  52. LDAP_SERVER_URI="ldap://localhost:389",
  53. LDAP_BIND_DN="cn=admin,dc=example,dc=com",
  54. LDAP_BIND_PASSWORD="password",
  55. LDAP_USER_BASE="ou=users,dc=example,dc=com",
  56. )
  57. is_valid, error_msg = config.validate_ldap_config()
  58. self.assertTrue(is_valid)
  59. self.assertEqual(error_msg, "")
  60. def test_ldap_config_in_get_config(self):
  61. """Test that LDAP_CONFIG is included in get_CONFIG()."""
  62. from archivebox.config import get_CONFIG
  63. all_config = get_CONFIG()
  64. self.assertIn('LDAP_CONFIG', all_config)
  65. self.assertEqual(all_config['LDAP_CONFIG'].__class__.__name__, 'LDAPConfig')
  66. class TestLDAPIntegration(unittest.TestCase):
  67. """Test LDAP integration with Django settings."""
  68. def test_django_settings_without_ldap_enabled(self):
  69. """Test that Django settings work correctly when LDAP is disabled."""
  70. # Import Django settings (LDAP_ENABLED should be False by default)
  71. from django.conf import settings
  72. # Should have default authentication backends
  73. self.assertIn("django.contrib.auth.backends.RemoteUserBackend", settings.AUTHENTICATION_BACKENDS)
  74. self.assertIn("django.contrib.auth.backends.ModelBackend", settings.AUTHENTICATION_BACKENDS)
  75. # LDAP backend should not be present when disabled
  76. ldap_backends = [b for b in settings.AUTHENTICATION_BACKENDS if 'ldap' in b.lower()]
  77. self.assertEqual(len(ldap_backends), 0, "LDAP backend should not be present when LDAP_ENABLED=False")
  78. def test_django_settings_with_ldap_library_check(self):
  79. """Test that Django settings check for LDAP libraries when enabled."""
  80. # Try to import django-auth-ldap to see if it's available
  81. try:
  82. import django_auth_ldap
  83. import ldap
  84. ldap_available = True
  85. except ImportError:
  86. ldap_available = False
  87. # If LDAP libraries are not available, settings should handle gracefully
  88. if not ldap_available:
  89. # Settings should have loaded without LDAP backend
  90. from django.conf import settings
  91. ldap_backends = [b for b in settings.AUTHENTICATION_BACKENDS if 'ldap' in b.lower()]
  92. self.assertEqual(len(ldap_backends), 0, "LDAP backend should not be present when libraries unavailable")
  93. class TestLDAPAuthBackend(unittest.TestCase):
  94. """Test custom LDAP authentication backend."""
  95. def test_ldap_backend_class_exists(self):
  96. """Test that ArchiveBoxLDAPBackend class is defined."""
  97. from archivebox.ldap.auth import ArchiveBoxLDAPBackend
  98. self.assertTrue(hasattr(ArchiveBoxLDAPBackend, 'authenticate_ldap_user'))
  99. def test_ldap_backend_inherits_correctly(self):
  100. """Test that ArchiveBoxLDAPBackend has correct inheritance."""
  101. from archivebox.ldap.auth import ArchiveBoxLDAPBackend
  102. # Should have authenticate_ldap_user method (from base or overridden)
  103. self.assertTrue(callable(getattr(ArchiveBoxLDAPBackend, 'authenticate_ldap_user', None)))
  104. class TestArchiveBoxWithLDAP(unittest.TestCase):
  105. """Test ArchiveBox commands with LDAP configuration."""
  106. def setUp(self):
  107. """Set up test environment."""
  108. self.work_dir = tempfile.mkdtemp(prefix='archivebox-ldap-test-')
  109. def test_archivebox_init_without_ldap(self):
  110. """Test that archivebox init works without LDAP enabled."""
  111. import subprocess
  112. # Run archivebox init
  113. result = subprocess.run(
  114. [sys.executable, '-m', 'archivebox', 'init'],
  115. cwd=self.work_dir,
  116. capture_output=True,
  117. timeout=45,
  118. env={
  119. **os.environ,
  120. 'DATA_DIR': self.work_dir,
  121. 'LDAP_ENABLED': 'False',
  122. }
  123. )
  124. # Should succeed
  125. self.assertEqual(result.returncode, 0, f"archivebox init failed: {result.stderr.decode()}")
  126. def test_archivebox_version_with_ldap_config(self):
  127. """Test that archivebox version works with LDAP config set."""
  128. import subprocess
  129. # Run archivebox version with LDAP config env vars
  130. result = subprocess.run(
  131. [sys.executable, '-m', 'archivebox', 'version'],
  132. capture_output=True,
  133. timeout=10,
  134. env={
  135. **os.environ,
  136. 'LDAP_ENABLED': 'False',
  137. 'LDAP_SERVER_URI': 'ldap://localhost:389',
  138. }
  139. )
  140. # Should succeed
  141. self.assertEqual(result.returncode, 0, f"archivebox version failed: {result.stderr.decode()}")
  142. class TestLDAPConfigValidationInArchiveBox(unittest.TestCase):
  143. """Test LDAP config validation when running ArchiveBox commands."""
  144. def setUp(self):
  145. """Set up test environment."""
  146. self.work_dir = tempfile.mkdtemp(prefix='archivebox-ldap-validation-')
  147. def test_archivebox_init_with_incomplete_ldap_config(self):
  148. """Test that archivebox init fails with helpful error when LDAP config is incomplete."""
  149. import subprocess
  150. # Run archivebox init with LDAP enabled but missing required fields
  151. result = subprocess.run(
  152. [sys.executable, '-m', 'archivebox', 'init'],
  153. cwd=self.work_dir,
  154. capture_output=True,
  155. timeout=45,
  156. env={
  157. **os.environ,
  158. 'DATA_DIR': self.work_dir,
  159. 'LDAP_ENABLED': 'True',
  160. # Missing: LDAP_SERVER_URI, LDAP_BIND_DN, etc.
  161. }
  162. )
  163. # Should fail with validation error
  164. self.assertNotEqual(result.returncode, 0, "Should fail with incomplete LDAP config")
  165. # Check error message
  166. stderr = result.stderr.decode()
  167. self.assertIn("LDAP_* config options must all be set", stderr,
  168. f"Expected validation error message in: {stderr}")
  169. if __name__ == '__main__':
  170. unittest.main()