Loading pyproject.toml +1 −0 Changes for pyproject.toml: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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", Loading sample.env +11 −0 Changes for sample.env: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 } uv.lock +23 −0 Changes for uv.lock: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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" Loading Loading @@ -739,6 +760,7 @@ dependencies = [ { name = "django-rq" }, { name = "django-tasks-rq" }, { name = "gunicorn" }, { name = "ldap3" }, { name = "matplotlib" }, { name = "psycopg", extra = ["binary"] }, { name = "tzdata" }, Loading @@ -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" }, Loading wybory/settings.py +26 −0 Changes for wybory/settings.py: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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') Loading Loading @@ -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 = [ Loading Loading
pyproject.toml +1 −0 Changes for pyproject.toml: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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", Loading
sample.env +11 −0 Changes for sample.env: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 }
uv.lock +23 −0 Changes for uv.lock: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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" Loading Loading @@ -739,6 +760,7 @@ dependencies = [ { name = "django-rq" }, { name = "django-tasks-rq" }, { name = "gunicorn" }, { name = "ldap3" }, { name = "matplotlib" }, { name = "psycopg", extra = ["binary"] }, { name = "tzdata" }, Loading @@ -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" }, Loading
wybory/settings.py +26 −0 Changes for wybory/settings.py: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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') Loading Loading @@ -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 = [ Loading