Configurable LDAP user promotion to superuser or staff (#1000)
* refactor: segregated ldap and fwd auth settings into imports and added variable validations * added: LDAP users listed in configuration variables are promoted to staff or superuser
This commit is contained in:
parent
aefd678dca
commit
5e6c94318c
|
|
@ -0,0 +1,6 @@
|
|||
from os import environ
|
||||
|
||||
TA_AUTH_PROXY_USERNAME_HEADER = (
|
||||
environ.get("TA_AUTH_PROXY_USERNAME_HEADER") or "HTTP_REMOTE_USER"
|
||||
)
|
||||
TA_AUTH_PROXY_LOGOUT_URL = environ.get("TA_AUTH_PROXY_LOGOUT_URL")
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
from os import environ
|
||||
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
AUTH_LDAP_SERVER_URI = environ.get("TA_LDAP_SERVER_URI")
|
||||
|
||||
AUTH_LDAP_BIND_DN = environ.get("TA_LDAP_BIND_DN")
|
||||
|
||||
AUTH_LDAP_BIND_PASSWORD = environ.get("TA_LDAP_BIND_PASSWORD")
|
||||
|
||||
"""
|
||||
Given Names are *_technically_* different from Personal names, as people
|
||||
who change their names have different given names and personal names,
|
||||
and they go by personal names. Additionally, "LastName" is actually
|
||||
incorrect for many cultures, such as Korea, where the
|
||||
family name comes first, and the personal name comes last.
|
||||
|
||||
But we all know people are going to try to guess at these, so still want
|
||||
to include names that people will guess, hence using first/last as well.
|
||||
"""
|
||||
|
||||
AUTH_LDAP_USER_ATTR_MAP_USERNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_USERNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_UID")
|
||||
or "uid"
|
||||
)
|
||||
|
||||
AUTH_LDAP_USER_ATTR_MAP_PERSONALNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_PERSONALNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_FIRSTNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_GIVENNAME")
|
||||
or "givenName"
|
||||
)
|
||||
|
||||
AUTH_LDAP_USER_ATTR_MAP_SURNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_SURNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_LASTNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_FAMILYNAME")
|
||||
or "sn"
|
||||
)
|
||||
|
||||
AUTH_LDAP_USER_ATTR_MAP_EMAIL = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_EMAIL")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_MAIL")
|
||||
or "mail"
|
||||
)
|
||||
|
||||
AUTH_LDAP_USER_BASE = environ.get("TA_LDAP_USER_BASE")
|
||||
|
||||
AUTH_LDAP_USER_FILTER = environ.get("TA_LDAP_USER_FILTER")
|
||||
|
||||
# pylint: disable=no-member
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
AUTH_LDAP_USER_BASE,
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(&("
|
||||
+ AUTH_LDAP_USER_ATTR_MAP_USERNAME
|
||||
+ "=%(user)s)"
|
||||
+ AUTH_LDAP_USER_FILTER
|
||||
+ ")",
|
||||
)
|
||||
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"username": AUTH_LDAP_USER_ATTR_MAP_USERNAME,
|
||||
"first_name": AUTH_LDAP_USER_ATTR_MAP_PERSONALNAME,
|
||||
"last_name": AUTH_LDAP_USER_ATTR_MAP_SURNAME,
|
||||
"email": AUTH_LDAP_USER_ATTR_MAP_EMAIL,
|
||||
}
|
||||
|
||||
if bool(environ.get("TA_LDAP_DISABLE_CERT_CHECK")):
|
||||
# pylint: disable=global-at-module-level
|
||||
global AUTH_LDAP_GLOBAL_OPTIONS
|
||||
AUTH_LDAP_GLOBAL_OPTIONS = {
|
||||
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
|
||||
}
|
||||
|
||||
# Promote specific usernames to staff or superuser permission levels
|
||||
_ldap_superuser_username_config = (
|
||||
environ.get("TA_LDAP_PROMOTE_USERNAMES_TO_SUPERUSER") or ""
|
||||
)
|
||||
_ldap_superuser_usernames = []
|
||||
if _ldap_superuser_username_config:
|
||||
_ldap_superuser_usernames = [
|
||||
u.strip() for u in _ldap_superuser_username_config.split(",")
|
||||
]
|
||||
|
||||
_ldap_staff_username_config = (
|
||||
environ.get("TA_LDAP_PROMOTE_USERNAMES_TO_STAFF") or ""
|
||||
)
|
||||
_ldap_staff_usernames = []
|
||||
if _ldap_staff_username_config:
|
||||
_ldap_staff_usernames = [
|
||||
u.strip() for u in _ldap_staff_username_config.split(",")
|
||||
]
|
||||
|
||||
if _ldap_staff_usernames or _ldap_superuser_usernames:
|
||||
import django_auth_ldap.backend
|
||||
|
||||
def create_user(sender, user=None, ldap_user=None, **kwargs):
|
||||
if user.ldap_username in _ldap_superuser_usernames and not (
|
||||
user.is_superuser and user.is_staff
|
||||
):
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
elif user.ldap_username in _ldap_staff_usernames and not user.is_staff:
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
|
||||
django_auth_ldap.backend.populate_user.connect(create_user)
|
||||
|
|
@ -81,6 +81,7 @@ class Command(BaseCommand):
|
|||
"""run all commands"""
|
||||
self.stdout.write(LOGO)
|
||||
self.stdout.write(TOPIC)
|
||||
self._additional_auth_vars_expectations()
|
||||
self._expected_vars()
|
||||
self._unexpected_vars()
|
||||
self._elastic_user_overwrite()
|
||||
|
|
@ -89,6 +90,50 @@ class Command(BaseCommand):
|
|||
self._disable_static_auth()
|
||||
self._create_superuser()
|
||||
|
||||
def _additional_auth_vars_expectations(self):
|
||||
"""conditionally add additional expectations for auth modes"""
|
||||
ldap_required_env = [
|
||||
"TA_LDAP_SERVER_URI",
|
||||
"TA_LDAP_BIND_DN",
|
||||
"TA_LDAP_BIND_PASSWORD",
|
||||
"TA_LDAP_USER_BASE",
|
||||
"TA_LDAP_USER_FILTER",
|
||||
]
|
||||
_login_auth_mode = (
|
||||
os.environ.get("TA_LOGIN_AUTH_MODE") or "single"
|
||||
).casefold()
|
||||
if _login_auth_mode == "local":
|
||||
UNEXPECTED_ENV_VARS["TA_LDAP"] = (
|
||||
"TA_LDAP is not valid with current auth mode"
|
||||
)
|
||||
UNEXPECTED_ENV_VARS["TA_ENABLE_AUTH_PROXY"] = (
|
||||
"TA_ENABLE_AUTH_PROXY is not valid with current auth mode"
|
||||
)
|
||||
elif _login_auth_mode == "ldap":
|
||||
EXPECTED_ENV_VARS.extend(ldap_required_env)
|
||||
UNEXPECTED_ENV_VARS["TA_ENABLE_AUTH_PROXY"] = (
|
||||
"TA_ENABLE_AUTH_PROXY is not valid with current auth mode"
|
||||
)
|
||||
elif _login_auth_mode == "forwardauth":
|
||||
UNEXPECTED_ENV_VARS["TA_LDAP"] = (
|
||||
"TA_LDAP is not valid with current auth mode"
|
||||
)
|
||||
elif _login_auth_mode == "ldap_local":
|
||||
EXPECTED_ENV_VARS.extend(ldap_required_env)
|
||||
UNEXPECTED_ENV_VARS["TA_ENABLE_AUTH_PROXY"] = (
|
||||
"TA_ENABLE_AUTH_PROXY is not valid with current auth mode"
|
||||
)
|
||||
else:
|
||||
if bool(os.environ.get("TA_LDAP")):
|
||||
EXPECTED_ENV_VARS.extend(ldap_required_env)
|
||||
UNEXPECTED_ENV_VARS["TA_ENABLE_AUTH_PROXY"] = (
|
||||
"TA_ENABLE_AUTH_PROXY is not valid with current auth mode"
|
||||
)
|
||||
if bool(os.environ.get("TA_ENABLE_AUTH_PROXY")):
|
||||
UNEXPECTED_ENV_VARS["TA_LDAP"] = (
|
||||
"TA_LDAP is not valid with current auth mode"
|
||||
)
|
||||
|
||||
def _expected_vars(self):
|
||||
"""check if expected env vars are set"""
|
||||
self.stdout.write("[1] checking expected env vars")
|
||||
|
|
|
|||
|
|
@ -104,98 +104,6 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = "config.wsgi.application"
|
||||
|
||||
if bool(environ.get("TA_LDAP")):
|
||||
# pylint: disable=global-at-module-level
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
global AUTH_LDAP_SERVER_URI
|
||||
AUTH_LDAP_SERVER_URI = environ.get("TA_LDAP_SERVER_URI")
|
||||
|
||||
global AUTH_LDAP_BIND_DN
|
||||
AUTH_LDAP_BIND_DN = environ.get("TA_LDAP_BIND_DN")
|
||||
|
||||
global AUTH_LDAP_BIND_PASSWORD
|
||||
AUTH_LDAP_BIND_PASSWORD = environ.get("TA_LDAP_BIND_PASSWORD")
|
||||
|
||||
"""
|
||||
Since these are new environment variables, taking the opporunity to use
|
||||
more accurate env names.
|
||||
Given Names are *_technically_* different from Personal names, as people
|
||||
who change their names have different given names and personal names,
|
||||
and they go by personal names. Additionally, "LastName" is actually
|
||||
incorrect for many cultures, such as Korea, where the
|
||||
family name comes first, and the personal name comes last.
|
||||
|
||||
But we all know people are going to try to guess at these, so still want
|
||||
to include names that people will guess, hence using first/last as well.
|
||||
"""
|
||||
|
||||
# Attribute mapping options
|
||||
|
||||
global AUTH_LDAP_USER_ATTR_MAP_USERNAME
|
||||
AUTH_LDAP_USER_ATTR_MAP_USERNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_USERNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_UID")
|
||||
or "uid"
|
||||
)
|
||||
|
||||
global AUTH_LDAP_USER_ATTR_MAP_PERSONALNAME
|
||||
AUTH_LDAP_USER_ATTR_MAP_PERSONALNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_PERSONALNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_FIRSTNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_GIVENNAME")
|
||||
or "givenName"
|
||||
)
|
||||
|
||||
global AUTH_LDAP_USER_ATTR_MAP_SURNAME
|
||||
AUTH_LDAP_USER_ATTR_MAP_SURNAME = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_SURNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_LASTNAME")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_FAMILYNAME")
|
||||
or "sn"
|
||||
)
|
||||
|
||||
global AUTH_LDAP_USER_ATTR_MAP_EMAIL
|
||||
AUTH_LDAP_USER_ATTR_MAP_EMAIL = (
|
||||
environ.get("TA_LDAP_USER_ATTR_MAP_EMAIL")
|
||||
or environ.get("TA_LDAP_USER_ATTR_MAP_MAIL")
|
||||
or "mail"
|
||||
)
|
||||
|
||||
global AUTH_LDAP_USER_BASE
|
||||
AUTH_LDAP_USER_BASE = environ.get("TA_LDAP_USER_BASE")
|
||||
|
||||
global AUTH_LDAP_USER_FILTER
|
||||
AUTH_LDAP_USER_FILTER = environ.get("TA_LDAP_USER_FILTER")
|
||||
|
||||
global AUTH_LDAP_USER_SEARCH
|
||||
# pylint: disable=no-member
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
AUTH_LDAP_USER_BASE,
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(&("
|
||||
+ AUTH_LDAP_USER_ATTR_MAP_USERNAME
|
||||
+ "=%(user)s)"
|
||||
+ AUTH_LDAP_USER_FILTER
|
||||
+ ")",
|
||||
)
|
||||
|
||||
global AUTH_LDAP_USER_ATTR_MAP
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"username": AUTH_LDAP_USER_ATTR_MAP_USERNAME,
|
||||
"first_name": AUTH_LDAP_USER_ATTR_MAP_PERSONALNAME,
|
||||
"last_name": AUTH_LDAP_USER_ATTR_MAP_SURNAME,
|
||||
"email": AUTH_LDAP_USER_ATTR_MAP_EMAIL,
|
||||
}
|
||||
|
||||
if bool(environ.get("TA_LDAP_DISABLE_CERT_CHECK")):
|
||||
global AUTH_LDAP_GLOBAL_OPTIONS
|
||||
AUTH_LDAP_GLOBAL_OPTIONS = {
|
||||
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
|
||||
}
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
|
|
@ -229,16 +137,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
|
||||
AUTH_USER_MODEL = "user.Account"
|
||||
|
||||
# Forward-auth authentication
|
||||
if bool(environ.get("TA_ENABLE_AUTH_PROXY")):
|
||||
TA_AUTH_PROXY_USERNAME_HEADER = (
|
||||
environ.get("TA_AUTH_PROXY_USERNAME_HEADER") or "HTTP_REMOTE_USER"
|
||||
)
|
||||
TA_AUTH_PROXY_LOGOUT_URL = environ.get("TA_AUTH_PROXY_LOGOUT_URL")
|
||||
|
||||
MIDDLEWARE.append("user.src.remote_user_auth.HttpRemoteUserMiddleware")
|
||||
|
||||
|
||||
# Configure Authentication Backend Combinations
|
||||
_login_auth_mode = (environ.get("TA_LOGIN_AUTH_MODE") or "single").casefold()
|
||||
if _login_auth_mode == "local":
|
||||
|
|
@ -247,24 +145,33 @@ if _login_auth_mode == "local":
|
|||
)
|
||||
elif _login_auth_mode == "ldap":
|
||||
AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",)
|
||||
from .ldap_settings import * # noqa: F403 F401
|
||||
elif _login_auth_mode == "forwardauth":
|
||||
from .fwd_auth_settings import * # noqa: F403 F401
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.RemoteUserBackend",
|
||||
)
|
||||
MIDDLEWARE.append("user.src.remote_user_auth.HttpRemoteUserMiddleware")
|
||||
elif _login_auth_mode == "ldap_local":
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django_auth_ldap.backend.LDAPBackend",
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
)
|
||||
from .ldap_settings import * # noqa: F403 F401
|
||||
else:
|
||||
# If none of these cases match, AUTHENTICATION_BACKENDS is unset, which
|
||||
# means the ModelBackend should be used by default
|
||||
if bool(environ.get("TA_LDAP")):
|
||||
AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",)
|
||||
from .ldap_settings import * # noqa: F403 F401
|
||||
if bool(environ.get("TA_ENABLE_AUTH_PROXY")):
|
||||
from .fwd_auth_settings import * # noqa: F403 F401
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
"django.contrib.auth.backends.RemoteUserBackend",
|
||||
)
|
||||
MIDDLEWARE.append("user.src.remote_user_auth.HttpRemoteUserMiddleware")
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
|
|
|
|||
Loading…
Reference in New Issue