Commit 6b77fb50 authored by Joachim Kołodziejski's avatar Joachim Kołodziejski
Browse files

Added LDAP util function (without integration yet) and LDAP related env variables

parent 9d72cd99
Loading
Loading
Loading
Loading
+1 −0
Changes for pyproject.toml: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ dependencies = [
    "django-rq~=4.0.1",
    "django-tasks-rq~=0.12.0",
    "gunicorn~=25.2.0",
    "ldap3~=2.9.1",
    "matplotlib>=3.10.8",
    "psycopg[binary]~=3.3.3",
    "tzdata~=2025.3",
+11 −0
Changes for sample.env: 11 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -11,3 +11,14 @@ SITE_URL="http://localhost:8000"
# DATABASE_URL="sqlite:///db.sqlite3"
# SCHOOL_YEAR_MONTH=9
# SCHOOL_YEAR_DAY=1
LDAP_HOST="ldap.example.com"
# LDAP_PORT=389
# LDAP_TLS=false
# LDAP_CA_CERTS_FILE=
LDAP_BIND_DN="cn=myuser,ou=mygroup,ou=users,dc=ldap,dc=example,dc=com"
LDAP_BIND_PASSWORD="myuserpassword"
# LDAP_TIMEOUT="30"
LDAP_GROUP_SEARCH_BASE_TEMPLATE="ou=%(group)s,ou=users,dc=ad,dc=staszic,dc=waw,dc=pl"
LDAP_GROUP_FILTER_TEMPLATE="(&(objectClass=posixAccount)(memberOf=cn=%(group)s,ou=users,dc=ldap,dc=example,dc=com))"
LDAP_NAME_FIELD="displayName"
LDAP_EMAIL_FIELD="mail"

tmp_ldap.py

0 → 100644
+59 −0
Changes for tmp_ldap.py: 59 added lines, 0 removed lines.
Original line number Diff line number Diff line
import ssl

import ldap3
from django.conf import settings

def get_ldap_attr(obj: dict, attr: str) -> str:
    if attr not in obj["attributes"]:
        raise ValueError(f"LDAP: {obj['raw_dn']} has no {attr} attribute")
    val = obj["attributes"][attr]
    if isinstance(val, list):
        if len(val) == 0:
            raise ValueError(f"LDAP: {obj['raw_dn']} has no {attr} attribute")
        if len(val) > 1:
            raise ValueError(
                f"LDAP: {obj['raw_dn']} has more than one {attr} attribute"
            )
        val = val[0]
    if isinstance(val, bytes):
        val = val.decode("utf-8")
    if not isinstance(val, str):
        raise ValueError(
            f"LDAP: {obj['raw_dn']} attribute {attr} could not be converted to string."
        )
    return val

def render_group_template(template: str, group: str) -> str:
    return template % {"group": ldap3.utils.conv.escape_filter_chars(group)}

def query(groups: list[str]) -> dict[str, list[dict[str, str]]]:
    return {
        group: [
            {
                "name": get_ldap_attr(obj, settings.LDAP_NAME_FIELD),
                "email": get_ldap_attr(obj, settings.LDAP_EMAIL_FIELD),
            }
            for obj in ldap3.Connection(
                server=ldap3.Server(
                    host=settings.LDAP_HOST,
                    port=settings.LDAP_PORT,
                    tls=ldap3.Tls(
                        ca_certs_file=settings.LDAP_TLS_CA_CERTS_FILE,
                        validate=ssl.CERT_REQUIRED,
                    ) if settings.LDAP_TLS else None,
                    use_ssl=False,
                ),
                user=settings.LDAP_BIND_DN,
                password=settings.LDAP_BIND_PASSWORD,
                auto_bind=ldap3.AUTO_BIND_TLS_BEFORE_BIND if settings.LDAP_TLS else ldap3.AUTO_BIND_NO_TLS,
                client_strategy=ldap3.SAFE_SYNC,
                raise_exceptions=True,
            ).search(
                search_base=render_group_template(settings.LDAP_GROUP_SEARCH_BASE_TEMPLATE, group),
                search_filter=render_group_template(settings.LDAP_GROUP_FILTER_TEMPLATE, group),
                attributes=ldap3.ALL_ATTRIBUTES,
                time_limit=settings.LDAP_TIMEOUT,
            )[2]
        ]
        for group in groups
    }
+23 −0
Changes for uv.lock: 23 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -382,6 +382,18 @@ wheels = [
    { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" },
]

[[package]]
name = "ldap3"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/ac/96bd5464e3edbc61595d0d69989f5d9969ae411866427b2500a8e5b812c0/ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f", size = 398830, upload-time = "2021-07-18T06:34:21.786Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/4e/f6/71d6ec9f18da0b2201287ce9db6afb1a1f637dedb3f0703409558981c723/ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", size = 432192, upload-time = "2021-07-18T06:34:12.905Z" },
]

[[package]]
name = "matplotlib"
version = "3.10.8"
@@ -633,6 +645,15 @@ wheels = [
    { url = "https://files.pythonhosted.org/packages/98/5a/291d89f44d3820fffb7a04ebc8f3ef5dda4f542f44a5daea0c55a84abf45/psycopg_binary-3.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:165f22ab5a9513a3d7425ffb7fcc7955ed8ccaeef6d37e369d6cc1dff1582383", size = 3652796, upload-time = "2026-02-18T16:52:14.02Z" },
]

[[package]]
name = "pyasn1"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
]

[[package]]
name = "pyparsing"
version = "3.3.2"
@@ -739,6 +760,7 @@ dependencies = [
    { name = "django-rq" },
    { name = "django-tasks-rq" },
    { name = "gunicorn" },
    { name = "ldap3" },
    { name = "matplotlib" },
    { name = "psycopg", extra = ["binary"] },
    { name = "tzdata" },
@@ -757,6 +779,7 @@ requires-dist = [
    { name = "django-rq", specifier = "~=4.0.1" },
    { name = "django-tasks-rq", specifier = "~=0.12.0" },
    { name = "gunicorn", specifier = "~=25.2.0" },
    { name = "ldap3", specifier = "~=2.9.1" },
    { name = "matplotlib", specifier = ">=3.10.8" },
    { name = "psycopg", extras = ["binary"], specifier = "~=3.3.3" },
    { name = "tzdata", specifier = "~=2025.3" },
+26 −0
Changes for wybory/settings.py: 26 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -32,6 +32,17 @@ env = environ.Env(
    DATABASE_URL=(str, "sqlite:///db.sqlite3"),
    SCHOOL_YEAR_MONTH=(int, 9),
    SCHOOL_YEAR_DAY=(int, 1),
    LDAP_HOST=str,
    LDAP_PORT=(int, 389),
    LDAP_TLS=(bool, False),
    LDAP_CA_CERTS_FILE=(str, ""),
    LDAP_BIND_DN=str,
    LDAP_BIND_PASSWORD=str,
    LDAP_TIMEOUT=(int, 30),
    LDAP_GROUP_SEARCH_BASE_TEMPLATE=str,
    LDAP_GROUP_FILTER_TEMPLATE=str,
    LDAP_NAME_FIELD=str,
    LDAP_EMAIL_FIELD=str,
)

environ.Env.read_env(BASE_DIR / '.env')
@@ -93,6 +104,21 @@ RQ_QUEUES = {

RQ_SHOW_ADMIN_LINK = True

# ldap
LDAP_HOST = env("LDAP_HOST")
LDAP_PORT = env("LDAP_PORT")
LDAP_TLS = env("LDAP_TLS")
LDAP_CA_CERTS_FILE = env("LDAP_CA_CERTS_FILE")
if LDAP_CA_CERTS_FILE == "":
    LDAP_CA_CERTS_FILE = None
LDAP_BIND_DN = env("LDAP_BIND_DN")
LDAP_BIND_PASSWORD = env("LDAP_BIND_PASSWORD")
LDAP_TIMEOUT = env("LDAP_TIMEOUT")
LDAP_GROUP_SEARCH_BASE_TEMPLATE = env("LDAP_GROUP_SEARCH_BASE_TEMPLATE")
LDAP_GROUP_FILTER_TEMPLATE = env("LDAP_GROUP_FILTER_TEMPLATE")
LDAP_NAME_FIELD = env("LDAP_NAME_FIELD")
LDAP_EMAIL_FIELD = env("LDAP_EMAIL_FIELD")

# Application definition

INSTALLED_APPS = [