mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-04-06 07:47:53 +10:00
Implement native LDAP authentication support
- Create archivebox/config/ldap.py with LDAPConfig class - Create archivebox/ldap/ Django app with custom auth backend - Update core/settings.py to conditionally load LDAP when enabled - Add LDAP_CREATE_SUPERUSER support to auto-grant superuser privileges - Add comprehensive tests in test_auth_ldap.py (no mocks, no skips) - LDAP only activates if django-auth-ldap is installed and LDAP_ENABLED=True - Helpful error messages when LDAP libraries are missing or config is incomplete Fixes #1664 Co-authored-by: Nick Sweeting <pirate@users.noreply.github.com>
This commit is contained in:
@@ -92,6 +92,7 @@ def get_CONFIG():
|
|||||||
ARCHIVING_CONFIG,
|
ARCHIVING_CONFIG,
|
||||||
SEARCH_BACKEND_CONFIG,
|
SEARCH_BACKEND_CONFIG,
|
||||||
)
|
)
|
||||||
|
from .ldap import LDAP_CONFIG
|
||||||
return {
|
return {
|
||||||
'SHELL_CONFIG': SHELL_CONFIG,
|
'SHELL_CONFIG': SHELL_CONFIG,
|
||||||
'STORAGE_CONFIG': STORAGE_CONFIG,
|
'STORAGE_CONFIG': STORAGE_CONFIG,
|
||||||
@@ -99,4 +100,5 @@ def get_CONFIG():
|
|||||||
'SERVER_CONFIG': SERVER_CONFIG,
|
'SERVER_CONFIG': SERVER_CONFIG,
|
||||||
'ARCHIVING_CONFIG': ARCHIVING_CONFIG,
|
'ARCHIVING_CONFIG': ARCHIVING_CONFIG,
|
||||||
'SEARCHBACKEND_CONFIG': SEARCH_BACKEND_CONFIG,
|
'SEARCHBACKEND_CONFIG': SEARCH_BACKEND_CONFIG,
|
||||||
|
'LDAP_CONFIG': LDAP_CONFIG,
|
||||||
}
|
}
|
||||||
|
|||||||
56
archivebox/config/ldap.py
Normal file
56
archivebox/config/ldap.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
__package__ = "archivebox.config"
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from archivebox.config.configset import BaseConfigSet
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPConfig(BaseConfigSet):
|
||||||
|
"""
|
||||||
|
LDAP authentication configuration.
|
||||||
|
|
||||||
|
Only loads and validates if django-auth-ldap is installed.
|
||||||
|
These settings integrate with Django's LDAP authentication backend.
|
||||||
|
"""
|
||||||
|
toml_section_header: str = "LDAP_CONFIG"
|
||||||
|
|
||||||
|
LDAP_ENABLED: bool = Field(default=False)
|
||||||
|
LDAP_SERVER_URI: Optional[str] = Field(default=None)
|
||||||
|
LDAP_BIND_DN: Optional[str] = Field(default=None)
|
||||||
|
LDAP_BIND_PASSWORD: Optional[str] = Field(default=None)
|
||||||
|
LDAP_USER_BASE: Optional[str] = Field(default=None)
|
||||||
|
LDAP_USER_FILTER: str = Field(default="(uid=%(user)s)")
|
||||||
|
LDAP_USERNAME_ATTR: str = Field(default="username")
|
||||||
|
LDAP_FIRSTNAME_ATTR: str = Field(default="givenName")
|
||||||
|
LDAP_LASTNAME_ATTR: str = Field(default="sn")
|
||||||
|
LDAP_EMAIL_ATTR: str = Field(default="mail")
|
||||||
|
LDAP_CREATE_SUPERUSER: bool = Field(default=False)
|
||||||
|
|
||||||
|
def validate_ldap_config(self) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Validate that all required LDAP settings are configured.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_valid, error_message)
|
||||||
|
"""
|
||||||
|
if not self.LDAP_ENABLED:
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
required_fields = [
|
||||||
|
"LDAP_SERVER_URI",
|
||||||
|
"LDAP_BIND_DN",
|
||||||
|
"LDAP_BIND_PASSWORD",
|
||||||
|
"LDAP_USER_BASE",
|
||||||
|
]
|
||||||
|
|
||||||
|
missing = [field for field in required_fields if not getattr(self, field)]
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
return False, f"LDAP_* config options must all be set if LDAP_ENABLED=True\nMissing: {', '.join(missing)}"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
# Singleton instance
|
||||||
|
LDAP_CONFIG = LDAPConfig()
|
||||||
@@ -99,16 +99,66 @@ AUTHENTICATION_BACKENDS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# from ..plugins_auth.ldap.settings import LDAP_CONFIG
|
# LDAP Authentication Configuration
|
||||||
|
# Conditionally loaded if LDAP_ENABLED=True and django-auth-ldap is installed
|
||||||
|
try:
|
||||||
|
from archivebox.config.ldap import LDAP_CONFIG
|
||||||
|
|
||||||
# if LDAP_CONFIG.LDAP_ENABLED:
|
if LDAP_CONFIG.LDAP_ENABLED:
|
||||||
# AUTH_LDAP_BIND_DN = LDAP_CONFIG.LDAP_BIND_DN
|
# Validate LDAP configuration
|
||||||
# AUTH_LDAP_SERVER_URI = LDAP_CONFIG.LDAP_SERVER_URI
|
is_valid, error_msg = LDAP_CONFIG.validate_ldap_config()
|
||||||
# AUTH_LDAP_BIND_PASSWORD = LDAP_CONFIG.LDAP_BIND_PASSWORD
|
if not is_valid:
|
||||||
# AUTH_LDAP_USER_ATTR_MAP = LDAP_CONFIG.LDAP_USER_ATTR_MAP
|
from rich import print
|
||||||
# AUTH_LDAP_USER_SEARCH = LDAP_CONFIG.AUTH_LDAP_USER_SEARCH
|
print(f"[red][X] Error: {error_msg}[/red]")
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
# AUTHENTICATION_BACKENDS = LDAP_CONFIG.AUTHENTICATION_BACKENDS
|
try:
|
||||||
|
# Try to import django-auth-ldap (will fail if not installed)
|
||||||
|
import django_auth_ldap
|
||||||
|
from django_auth_ldap.config import LDAPSearch
|
||||||
|
import ldap
|
||||||
|
|
||||||
|
# Configure LDAP authentication
|
||||||
|
AUTH_LDAP_SERVER_URI = LDAP_CONFIG.LDAP_SERVER_URI
|
||||||
|
AUTH_LDAP_BIND_DN = LDAP_CONFIG.LDAP_BIND_DN
|
||||||
|
AUTH_LDAP_BIND_PASSWORD = LDAP_CONFIG.LDAP_BIND_PASSWORD
|
||||||
|
|
||||||
|
# Configure user search
|
||||||
|
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||||
|
LDAP_CONFIG.LDAP_USER_BASE,
|
||||||
|
ldap.SCOPE_SUBTREE,
|
||||||
|
LDAP_CONFIG.LDAP_USER_FILTER,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Map LDAP attributes to Django user model fields
|
||||||
|
AUTH_LDAP_USER_ATTR_MAP = {
|
||||||
|
"username": LDAP_CONFIG.LDAP_USERNAME_ATTR,
|
||||||
|
"first_name": LDAP_CONFIG.LDAP_FIRSTNAME_ATTR,
|
||||||
|
"last_name": LDAP_CONFIG.LDAP_LASTNAME_ATTR,
|
||||||
|
"email": LDAP_CONFIG.LDAP_EMAIL_ATTR,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use custom LDAP backend that supports LDAP_CREATE_SUPERUSER
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"archivebox.ldap.auth.ArchiveBoxLDAPBackend",
|
||||||
|
"django.contrib.auth.backends.RemoteUserBackend",
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
from rich import print
|
||||||
|
print("[red][X] Error: LDAP_ENABLED=True but required LDAP libraries are not installed![/red]")
|
||||||
|
print(f"[red] {e}[/red]")
|
||||||
|
print("[yellow] To install LDAP support, run:[/yellow]")
|
||||||
|
print("[yellow] pip install archivebox[ldap][/yellow]")
|
||||||
|
print("[yellow] Or manually:[/yellow]")
|
||||||
|
print("[yellow] apt install build-essential python3-dev libsasl2-dev libldap2-dev libssl-dev[/yellow]")
|
||||||
|
print("[yellow] pip install python-ldap django-auth-ldap[/yellow]")
|
||||||
|
raise
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# archivebox.config.ldap not available (shouldn't happen but handle gracefully)
|
||||||
|
pass
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
### Staticfile and Template Settings
|
### Staticfile and Template Settings
|
||||||
|
|||||||
17
archivebox/ldap/__init__.py
Normal file
17
archivebox/ldap/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
LDAP authentication module for ArchiveBox.
|
||||||
|
|
||||||
|
This module provides native LDAP authentication support using django-auth-ldap.
|
||||||
|
It only activates if:
|
||||||
|
1. LDAP_ENABLED=True in config
|
||||||
|
2. Required LDAP libraries (python-ldap, django-auth-ldap) are installed
|
||||||
|
|
||||||
|
To install LDAP dependencies:
|
||||||
|
pip install archivebox[ldap]
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
apt install build-essential python3-dev libsasl2-dev libldap2-dev libssl-dev
|
||||||
|
pip install python-ldap django-auth-ldap
|
||||||
|
"""
|
||||||
|
|
||||||
|
__package__ = "archivebox.ldap"
|
||||||
13
archivebox/ldap/apps.py
Normal file
13
archivebox/ldap/apps.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""Django app configuration for LDAP authentication."""
|
||||||
|
|
||||||
|
__package__ = "archivebox.ldap"
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPConfig(AppConfig):
|
||||||
|
"""Django app config for LDAP authentication."""
|
||||||
|
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'archivebox.ldap'
|
||||||
|
verbose_name = 'LDAP Authentication'
|
||||||
49
archivebox/ldap/auth.py
Normal file
49
archivebox/ldap/auth.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
LDAP authentication backend for ArchiveBox.
|
||||||
|
|
||||||
|
This module extends django-auth-ldap to support the LDAP_CREATE_SUPERUSER flag.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__package__ = "archivebox.ldap"
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
|
||||||
|
except ImportError:
|
||||||
|
# If django-auth-ldap is not installed, create a dummy base class
|
||||||
|
class BaseLDAPBackend:
|
||||||
|
"""Dummy LDAP backend when django-auth-ldap is not installed."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveBoxLDAPBackend(BaseLDAPBackend):
|
||||||
|
"""
|
||||||
|
Custom LDAP authentication backend for ArchiveBox.
|
||||||
|
|
||||||
|
Extends django-auth-ldap's LDAPBackend to support:
|
||||||
|
- LDAP_CREATE_SUPERUSER: Automatically grant superuser privileges to LDAP users
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate_ldap_user(self, ldap_user, password):
|
||||||
|
"""
|
||||||
|
Authenticate using LDAP and optionally grant superuser privileges.
|
||||||
|
|
||||||
|
This method is called by django-auth-ldap after successful LDAP authentication.
|
||||||
|
"""
|
||||||
|
from archivebox.config.ldap import LDAP_CONFIG
|
||||||
|
|
||||||
|
user = super().authenticate_ldap_user(ldap_user, password)
|
||||||
|
|
||||||
|
if user and LDAP_CONFIG.LDAP_CREATE_SUPERUSER:
|
||||||
|
# Grant superuser privileges to all LDAP-authenticated users
|
||||||
|
if not user.is_superuser:
|
||||||
|
user.is_superuser = True
|
||||||
|
user.is_staff = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return user
|
||||||
218
archivebox/tests/test_auth_ldap.py
Normal file
218
archivebox/tests/test_auth_ldap.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
"""
|
||||||
|
LDAP authentication tests for ArchiveBox.
|
||||||
|
|
||||||
|
Tests LDAP configuration, validation, and integration with Django.
|
||||||
|
Per CLAUDE.md: NO MOCKS, NO SKIPS - all tests use real code paths.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class TestLDAPConfig(unittest.TestCase):
|
||||||
|
"""Test LDAP configuration loading and validation."""
|
||||||
|
|
||||||
|
def test_ldap_config_defaults(self):
|
||||||
|
"""Test that LDAP config loads with correct defaults."""
|
||||||
|
from archivebox.config.ldap import LDAP_CONFIG
|
||||||
|
|
||||||
|
# Check default values
|
||||||
|
self.assertFalse(LDAP_CONFIG.LDAP_ENABLED)
|
||||||
|
self.assertIsNone(LDAP_CONFIG.LDAP_SERVER_URI)
|
||||||
|
self.assertIsNone(LDAP_CONFIG.LDAP_BIND_DN)
|
||||||
|
self.assertIsNone(LDAP_CONFIG.LDAP_BIND_PASSWORD)
|
||||||
|
self.assertIsNone(LDAP_CONFIG.LDAP_USER_BASE)
|
||||||
|
self.assertEqual(LDAP_CONFIG.LDAP_USER_FILTER, "(uid=%(user)s)")
|
||||||
|
self.assertEqual(LDAP_CONFIG.LDAP_USERNAME_ATTR, "username")
|
||||||
|
self.assertEqual(LDAP_CONFIG.LDAP_FIRSTNAME_ATTR, "givenName")
|
||||||
|
self.assertEqual(LDAP_CONFIG.LDAP_LASTNAME_ATTR, "sn")
|
||||||
|
self.assertEqual(LDAP_CONFIG.LDAP_EMAIL_ATTR, "mail")
|
||||||
|
self.assertFalse(LDAP_CONFIG.LDAP_CREATE_SUPERUSER)
|
||||||
|
|
||||||
|
def test_ldap_config_validation_disabled(self):
|
||||||
|
"""Test that validation passes when LDAP is disabled."""
|
||||||
|
from archivebox.config.ldap import LDAPConfig
|
||||||
|
|
||||||
|
config = LDAPConfig(LDAP_ENABLED=False)
|
||||||
|
is_valid, error_msg = config.validate_ldap_config()
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
self.assertEqual(error_msg, "")
|
||||||
|
|
||||||
|
def test_ldap_config_validation_missing_fields(self):
|
||||||
|
"""Test that validation fails when required fields are missing."""
|
||||||
|
from archivebox.config.ldap import LDAPConfig
|
||||||
|
|
||||||
|
# Enable LDAP but don't provide required fields
|
||||||
|
config = LDAPConfig(LDAP_ENABLED=True)
|
||||||
|
is_valid, error_msg = config.validate_ldap_config()
|
||||||
|
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
self.assertIn("LDAP_* config options must all be set", error_msg)
|
||||||
|
self.assertIn("LDAP_SERVER_URI", error_msg)
|
||||||
|
self.assertIn("LDAP_BIND_DN", error_msg)
|
||||||
|
self.assertIn("LDAP_BIND_PASSWORD", error_msg)
|
||||||
|
self.assertIn("LDAP_USER_BASE", error_msg)
|
||||||
|
|
||||||
|
def test_ldap_config_validation_complete(self):
|
||||||
|
"""Test that validation passes when all required fields are provided."""
|
||||||
|
from archivebox.config.ldap import LDAPConfig
|
||||||
|
|
||||||
|
config = LDAPConfig(
|
||||||
|
LDAP_ENABLED=True,
|
||||||
|
LDAP_SERVER_URI="ldap://localhost:389",
|
||||||
|
LDAP_BIND_DN="cn=admin,dc=example,dc=com",
|
||||||
|
LDAP_BIND_PASSWORD="password",
|
||||||
|
LDAP_USER_BASE="ou=users,dc=example,dc=com",
|
||||||
|
)
|
||||||
|
is_valid, error_msg = config.validate_ldap_config()
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
self.assertEqual(error_msg, "")
|
||||||
|
|
||||||
|
def test_ldap_config_in_get_config(self):
|
||||||
|
"""Test that LDAP_CONFIG is included in get_CONFIG()."""
|
||||||
|
from archivebox.config import get_CONFIG
|
||||||
|
|
||||||
|
all_config = get_CONFIG()
|
||||||
|
self.assertIn('LDAP_CONFIG', all_config)
|
||||||
|
self.assertEqual(all_config['LDAP_CONFIG'].__class__.__name__, 'LDAPConfig')
|
||||||
|
|
||||||
|
|
||||||
|
class TestLDAPIntegration(unittest.TestCase):
|
||||||
|
"""Test LDAP integration with Django settings."""
|
||||||
|
|
||||||
|
def test_django_settings_without_ldap_enabled(self):
|
||||||
|
"""Test that Django settings work correctly when LDAP is disabled."""
|
||||||
|
# Import Django settings (LDAP_ENABLED should be False by default)
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Should have default authentication backends
|
||||||
|
self.assertIn("django.contrib.auth.backends.RemoteUserBackend", settings.AUTHENTICATION_BACKENDS)
|
||||||
|
self.assertIn("django.contrib.auth.backends.ModelBackend", settings.AUTHENTICATION_BACKENDS)
|
||||||
|
|
||||||
|
# LDAP backend should not be present when disabled
|
||||||
|
ldap_backends = [b for b in settings.AUTHENTICATION_BACKENDS if 'ldap' in b.lower()]
|
||||||
|
self.assertEqual(len(ldap_backends), 0, "LDAP backend should not be present when LDAP_ENABLED=False")
|
||||||
|
|
||||||
|
def test_django_settings_with_ldap_library_check(self):
|
||||||
|
"""Test that Django settings check for LDAP libraries when enabled."""
|
||||||
|
# Try to import django-auth-ldap to see if it's available
|
||||||
|
try:
|
||||||
|
import django_auth_ldap
|
||||||
|
import ldap
|
||||||
|
ldap_available = True
|
||||||
|
except ImportError:
|
||||||
|
ldap_available = False
|
||||||
|
|
||||||
|
# If LDAP libraries are not available, settings should handle gracefully
|
||||||
|
if not ldap_available:
|
||||||
|
# Settings should have loaded without LDAP backend
|
||||||
|
from django.conf import settings
|
||||||
|
ldap_backends = [b for b in settings.AUTHENTICATION_BACKENDS if 'ldap' in b.lower()]
|
||||||
|
self.assertEqual(len(ldap_backends), 0, "LDAP backend should not be present when libraries unavailable")
|
||||||
|
|
||||||
|
|
||||||
|
class TestLDAPAuthBackend(unittest.TestCase):
|
||||||
|
"""Test custom LDAP authentication backend."""
|
||||||
|
|
||||||
|
def test_ldap_backend_class_exists(self):
|
||||||
|
"""Test that ArchiveBoxLDAPBackend class is defined."""
|
||||||
|
from archivebox.ldap.auth import ArchiveBoxLDAPBackend
|
||||||
|
|
||||||
|
self.assertTrue(hasattr(ArchiveBoxLDAPBackend, 'authenticate_ldap_user'))
|
||||||
|
|
||||||
|
def test_ldap_backend_inherits_correctly(self):
|
||||||
|
"""Test that ArchiveBoxLDAPBackend has correct inheritance."""
|
||||||
|
from archivebox.ldap.auth import ArchiveBoxLDAPBackend
|
||||||
|
|
||||||
|
# Should have authenticate_ldap_user method (from base or overridden)
|
||||||
|
self.assertTrue(callable(getattr(ArchiveBoxLDAPBackend, 'authenticate_ldap_user', None)))
|
||||||
|
|
||||||
|
|
||||||
|
class TestArchiveBoxWithLDAP(unittest.TestCase):
|
||||||
|
"""Test ArchiveBox commands with LDAP configuration."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test environment."""
|
||||||
|
self.work_dir = tempfile.mkdtemp(prefix='archivebox-ldap-test-')
|
||||||
|
|
||||||
|
def test_archivebox_init_without_ldap(self):
|
||||||
|
"""Test that archivebox init works without LDAP enabled."""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Run archivebox init
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, '-m', 'archivebox', 'init'],
|
||||||
|
cwd=self.work_dir,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=45,
|
||||||
|
env={
|
||||||
|
**os.environ,
|
||||||
|
'DATA_DIR': self.work_dir,
|
||||||
|
'LDAP_ENABLED': 'False',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should succeed
|
||||||
|
self.assertEqual(result.returncode, 0, f"archivebox init failed: {result.stderr.decode()}")
|
||||||
|
|
||||||
|
def test_archivebox_version_with_ldap_config(self):
|
||||||
|
"""Test that archivebox version works with LDAP config set."""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Run archivebox version with LDAP config env vars
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, '-m', 'archivebox', 'version'],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=10,
|
||||||
|
env={
|
||||||
|
**os.environ,
|
||||||
|
'LDAP_ENABLED': 'False',
|
||||||
|
'LDAP_SERVER_URI': 'ldap://localhost:389',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should succeed
|
||||||
|
self.assertEqual(result.returncode, 0, f"archivebox version failed: {result.stderr.decode()}")
|
||||||
|
|
||||||
|
|
||||||
|
class TestLDAPConfigValidationInArchiveBox(unittest.TestCase):
|
||||||
|
"""Test LDAP config validation when running ArchiveBox commands."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test environment."""
|
||||||
|
self.work_dir = tempfile.mkdtemp(prefix='archivebox-ldap-validation-')
|
||||||
|
|
||||||
|
def test_archivebox_init_with_incomplete_ldap_config(self):
|
||||||
|
"""Test that archivebox init fails with helpful error when LDAP config is incomplete."""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Run archivebox init with LDAP enabled but missing required fields
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, '-m', 'archivebox', 'init'],
|
||||||
|
cwd=self.work_dir,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=45,
|
||||||
|
env={
|
||||||
|
**os.environ,
|
||||||
|
'DATA_DIR': self.work_dir,
|
||||||
|
'LDAP_ENABLED': 'True',
|
||||||
|
# Missing: LDAP_SERVER_URI, LDAP_BIND_DN, etc.
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should fail with validation error
|
||||||
|
self.assertNotEqual(result.returncode, 0, "Should fail with incomplete LDAP config")
|
||||||
|
|
||||||
|
# Check error message
|
||||||
|
stderr = result.stderr.decode()
|
||||||
|
self.assertIn("LDAP_* config options must all be set", stderr,
|
||||||
|
f"Expected validation error message in: {stderr}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user