Native LDAP support (#5226)

* Added basic ldap support

* Added new settings to config template

* Added missing apk dependency to dockerfile

* update requirements.txt

* Remove requirements and added docs

* Update advanced.md

* Added more ldap options

* allow direct bind
* add ldap debug mode
* add ldap global_options
* add start_tls
* use get_boolean_setting

* Update advanced.md to use a warning box

* fix: style

* Update advanced.md

* Remove ldap install instructions for non docker installs
This commit is contained in:
Lukas 2023-10-04 22:52:36 +02:00 committed by GitHub
parent a6dbe185c6
commit 8ec04028d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 3 deletions

View File

@ -28,7 +28,9 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
# PostgreSQL support
libpq-dev \
# MySQL / MariaDB support
default-libmysqlclient-dev mariadb-client && \
default-libmysqlclient-dev mariadb-client \
# LDAP support
libldap2-dev libsasl2-dev && \
apt-get autoclean && apt-get autoremove
# [Optional] Uncomment this line to install global node packages.

View File

@ -58,7 +58,7 @@ RUN apk add --no-cache \
# Image format support
libjpeg libwebp zlib \
# Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils \
py3-pip py3-pillow py3-cffi py3-brotli pango poppler-utils openldap \
# SQLite support
sqlite \
# PostgreSQL support
@ -84,7 +84,7 @@ RUN if [ `apk --print-arch` = "armv7" ]; then \
fi
RUN apk add --no-cache --virtual .build-deps \
gcc g++ musl-dev openssl-dev libffi-dev cargo python3-dev \
gcc g++ musl-dev openssl-dev libffi-dev cargo python3-dev openldap-dev \
# Image format dev libs
jpeg-dev openjpeg-dev libwebp-dev zlib-dev \
# DB specific dev libs

View File

@ -291,6 +291,63 @@ AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [
"sesame.backends.ModelBackend", # Magic link login django-sesame
])
# LDAP support
LDAP_AUTH = get_boolean_setting("INVENTREE_LDAP_ENABLED", "ldap.enabled", False)
if LDAP_AUTH:
import ldap
from django_auth_ldap.config import LDAPSearch
AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend")
# debug mode to troubleshoot configuration
LDAP_DEBUG = get_boolean_setting("INVENTREE_LDAP_DEBUG", "ldap.debug", False)
if LDAP_DEBUG:
if "loggers" not in LOGGING:
LOGGING["loggers"] = {}
LOGGING["loggers"]["django_auth_ldap"] = {"level": "DEBUG", "handlers": ["console"]}
# get global options from dict and use ldap.OPT_* as keys and values
global_options_dict = get_setting("INVENTREE_LDAP_GLOBAL_OPTIONS", "ldap.global_options", {}, dict)
global_options = {}
for k, v in global_options_dict.items():
# keys are always ldap.OPT_* constants
k_attr = getattr(ldap, k, None)
if not k.startswith("OPT_") or k_attr is None:
print(f"[LDAP] ldap.global_options, key '{k}' not found, skipping...")
continue
# values can also be other strings, e.g. paths
v_attr = v
if v.startswith("OPT_"):
v_attr = getattr(ldap, v, None)
if v_attr is None:
print(f"[LDAP] ldap.global_options, value key '{v}' not found, skipping...")
continue
global_options[k_attr] = v_attr
AUTH_LDAP_GLOBAL_OPTIONS = global_options
if LDAP_DEBUG:
print("[LDAP] ldap.global_options =", global_options)
AUTH_LDAP_SERVER_URI = get_setting("INVENTREE_LDAP_SERVER_URI", "ldap.server_uri")
AUTH_LDAP_START_TLS = get_boolean_setting("INVENTREE_LDAP_START_TLS", "ldap.start_tls", False)
AUTH_LDAP_BIND_DN = get_setting("INVENTREE_LDAP_BIND_DN", "ldap.bind_dn")
AUTH_LDAP_BIND_PASSWORD = get_setting("INVENTREE_LDAP_BIND_PASSWORD", "ldap.bind_password")
AUTH_LDAP_USER_SEARCH = LDAPSearch(
get_setting("INVENTREE_LDAP_SEARCH_BASE_DN", "ldap.search_base_dn"),
ldap.SCOPE_SUBTREE,
str(get_setting("INVENTREE_LDAP_SEARCH_FILTER_STR", "ldap.search_filter_str", "(uid= %(user)s)"))
)
AUTH_LDAP_USER_DN_TEMPLATE = get_setting("INVENTREE_LDAP_USER_DN_TEMPLATE", "ldap.user_dn_template")
AUTH_LDAP_USER_ATTR_MAP = get_setting("INVENTREE_LDAP_USER_ATTR_MAP", "ldap.user_attr_map", {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}, dict)
AUTH_LDAP_ALWAYS_UPDATE_USER = get_boolean_setting("INVENTREE_LDAP_ALWAYS_UPDATE_USER", "ldap.always_update_user", True)
AUTH_LDAP_CACHE_TIMEOUT = get_setting("INVENTREE_LDAP_CACHE_TIMEOUT", "ldap.cache_timeout", 3600, int)
DEBUG_TOOLBAR_ENABLED = DEBUG and get_setting('INVENTREE_DEBUG_TOOLBAR', 'debug_toolbar', False)
# If the debug toolbar is enabled, add the modules

View File

@ -233,6 +233,43 @@ remote_login_header: HTTP_REMOTE_USER
# KEYCLOAK_URL: 'https://keycloak.custom/auth'
# KEYCLOAK_REALM: 'master'
# Add LDAP support
# ldap:
# enabled: false
# debug: false # enable debug mode to troubleshoot ldap configuration
# server_uri: ldaps://example.org
# bind_dn: cn=admin,dc=example,dc=org
# bind_password: admin_password
# search_base_dn: cn=Users,dc=example,dc=org
# # enable TLS encryption over the standard LDAP port,
# # see: https://django-auth-ldap.readthedocs.io/en/latest/reference.html#auth-ldap-start-tls
# # start_tls: false
# # uncomment if you want to use direct bind, bind_dn and bin_password is not necessary then
# # user_dn_template: "uid=%(user)s,dc=example,dc=org"
# # uncomment to set advanced global options, see https://www.python-ldap.org/en/latest/reference/ldap.html#ldap-options
# # for all available options (keys and values starting with OPT_ get automatically converted to python-ldap keys)
# # global_options:
# # OPT_X_TLS_REQUIRE_CERT: OPT_X_TLS_NEVER
# # OPT_X_TLS_CACERTFILE: /opt/inventree/ldapca.pem
# # uncomment for advanced filter search, default: uid=%(user)s
# # search_filter_str:
# # uncomment for advanced user attribute mapping (in the format <InvenTree attribute>: <LDAP attribute>)
# # user_attr_map:
# # first_name: givenName
# # last_name: sn
# # email: mail
# # always update the user on each login, default: true
# # always_update_user: true
# # cache timeout to reduce traffic with LDAP server, default: 3600 (1h)
# # cache_timeout: 3600
# Customization options
# Add custom messages to the login page or main interface navbar or exchange the logo
# Use environment variable INVENTREE_CUSTOMIZE or INVENTREE_CUSTOM_LOGO

View File

@ -14,3 +14,7 @@ mariadb>=1.0.7,<1.1.0
# gunicorn web server
gunicorn>=20.1.0
# LDAP required packages
django-auth-ldap # Django integration for ldap auth
python-ldap # LDAP auth support

View File

@ -40,3 +40,30 @@ The installer code is used to identify the way InvenTree was installed. If you v
| DIO | Installed using digital ocean marketplace[^1] | No |
[^1]: Starting with fresh installs of 0.12.0 this code is set. Versions installed before 0.12.0 do not have this code set even after upgrading to 0.12.0.
## Authentication
### LDAP
You can link your InvenTree server to an LDAP server.
!!! warning "Important"
This feature is currently only available for docker installs.
Next you can start configuring the connection. Either use the config file or set the environment variables.
| config key | ENV Variable | Description |
| --- | --- | --- |
| `ldap.enabled` | `INVENTREE_LDAP_ENABLED` | Set this to `True` to enable LDAP. |
| `ldap.debug` | `INVENTREE_LDAP_DEBUG` | Set this to `True` to activate debug mode, useful for troubleshooting ldap configurations. |
| `ldap.server_uri` | `INVENTREE_LDAP_SERVER_URI` | LDAP Server URI, e.g. `ldaps://example.org` |
| `ldap.start_tls` | `INVENTREE_LDAP_START_TLS` | Enable TLS encryption over the standard LDAP port, [see](https://django-auth-ldap.readthedocs.io/en/latest/reference.html#auth-ldap-start-tls). (You can set TLS options via `ldap.global_options`) |
| `ldap.bind_dn` | `INVENTREE_LDAP_BIND_DN` | LDAP bind dn, e.g. `cn=admin,dc=example,dc=org` |
| `ldap.bind_password` | `INVENTREE_LDAP_BIND_PASSWORD` | LDAP bind password |
| `ldap.search_base_dn` | `INVENTREE_LDAP_SEARCH_BASE_DN` | LDAP search base dn, e.g. `cn=Users,dc=example,dc=org` |
| `ldap.user_dn_template` | `INVENTREE_LDAP_USER_DN_TEMPLATE` | use direct bind as auth user, `ldap.bind_dn` and `ldap.bin_password` is not necessary then, e.g. `uid=%(user)s,dc=example,dc=org` |
| `ldap.global_options` | `INVENTREE_LDAP_GLOBAL_OPTIONS` | set advanced options as dict, e.g. TLS settings. For a list of all available options, see [python-ldap docs](https://www.python-ldap.org/en/latest/reference/ldap.html#ldap-options). (keys and values starting with OPT_ get automatically converted to `python-ldap` keys) |
| `ldap.search_filter_str`| `INVENTREE_LDAP_SEARCH_FILTER_STR` | LDAP search filter str, default: `uid=%(user)s` |
| `ldap.user_attr_map` | `INVENTREE_LDAP_USER_ATTR_MAP` | LDAP <-> Inventree user attribute map, can be json if used as env, in yml directly specify the object. default: `{"first_name": "givenName", "last_name": "sn", "email": "mail"}` |
| `ldap.always_update_user` | `INVENTREE_LDAP_ALWAYS_UPDATE_USER` | Always update the user on each login, default: `true` |
| `ldap.cache_timeout` | `INVENTREE_LDAP_CACHE_TIMEOUT` | cache timeout to reduce traffic with LDAP server, default: `3600` (1h) |