Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions archivebox/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ def get_CONFIG():
ARCHIVING_CONFIG,
SEARCH_BACKEND_CONFIG,
)
from .ldap import LDAP_CONFIG
return {
'SHELL_CONFIG': SHELL_CONFIG,
'STORAGE_CONFIG': STORAGE_CONFIG,
'GENERAL_CONFIG': GENERAL_CONFIG,
'SERVER_CONFIG': SERVER_CONFIG,
'ARCHIVING_CONFIG': ARCHIVING_CONFIG,
'SEARCHBACKEND_CONFIG': SEARCH_BACKEND_CONFIG,
'LDAP_CONFIG': LDAP_CONFIG,
}
56 changes: 56 additions & 0 deletions archivebox/config/ldap.py
Original file line number Diff line number Diff line change
@@ -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()
70 changes: 60 additions & 10 deletions archivebox/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,66 @@
]


# from ..plugins_auth.ldap.settings import LDAP_CONFIG

# if LDAP_CONFIG.LDAP_ENABLED:
# AUTH_LDAP_BIND_DN = LDAP_CONFIG.LDAP_BIND_DN
# AUTH_LDAP_SERVER_URI = LDAP_CONFIG.LDAP_SERVER_URI
# AUTH_LDAP_BIND_PASSWORD = LDAP_CONFIG.LDAP_BIND_PASSWORD
# AUTH_LDAP_USER_ATTR_MAP = LDAP_CONFIG.LDAP_USER_ATTR_MAP
# AUTH_LDAP_USER_SEARCH = LDAP_CONFIG.AUTH_LDAP_USER_SEARCH

# AUTHENTICATION_BACKENDS = LDAP_CONFIG.AUTHENTICATION_BACKENDS
# 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:
# Validate LDAP configuration
is_valid, error_msg = LDAP_CONFIG.validate_ldap_config()
if not is_valid:
from rich import print
print(f"[red][X] Error: {error_msg}[/red]")
raise ValueError(error_msg)

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
Expand Down
17 changes: 17 additions & 0 deletions archivebox/ldap/__init__.py
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions archivebox/ldap/apps.py
Original file line number Diff line number Diff line change
@@ -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 changes: 49 additions & 0 deletions archivebox/ldap/auth.py
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading