change ldap auth to ldap backend to be able to program without ldap

This commit is contained in:
2025-01-26 13:10:35 +01:00
parent dd401538d3
commit 5cac8f8275
6 changed files with 179 additions and 151 deletions

View File

@@ -11,109 +11,6 @@ host = "ldap://juri.fet.htu.tuwien.ac.at"
port = 389 port = 389
def authentication(username, password):
# no empty passwords
if password is None or password.strip() == "":
return None
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
firstname = ""
surname = ""
mail = ""
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return None
# get member infos from ldap
c.search(
search_base=userdn,
search_filter=f"(uid={username})",
search_scope="SUBTREE",
attributes=["givenName", "sn", "mail"],
)
firstname = c.response[0]["attributes"]["givenName"][0]
surname = c.response[0]["attributes"]["sn"][0]
mail = c.response[0]["attributes"]["mail"][0]
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return None
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return None
# get member or if not then create a new member
try:
member = Member.objects.get(mailaccount=mail)
# set username if not equal
if member.username != username:
member.username = username
logger.info("User '%s' saved.", username)
member.save()
except Member.DoesNotExist:
member = Member()
member.firstname = firstname
member.surname = surname
member.username = username
member.nickname = username
member.mailaccount = mail
logger.info("Member '%s' created.", username)
member.save()
logger.info("User '%s' logged in.", username)
return username
def get_finance_perm(username, password):
# no empty passwords
if password is None or password.strip() == "":
return None
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
finance_perm = None
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return None
# check if member has finance permission
c.search(
search_base="CN=finance,OU=Groups,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at",
search_filter="(objectClass=posixGroup)",
search_scope="SUBTREE",
attributes=["memberUid"],
)
if username in c.response[0]["attributes"]["memberUid"]:
logger.info("User '%s' has finance permission.", username)
finance_perm = True
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return None
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return None
return finance_perm
def change_password(username, old_password, new_password): def change_password(username, old_password, new_password):
server = Server(host, port=port, use_ssl=True) server = Server(host, port=port, use_ssl=True)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at" userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"

View File

@@ -0,0 +1,162 @@
import logging
from django.contrib.auth.models import Group, User
from django.contrib.auth.backends import ModelBackend
from ldap3 import Connection, Server
from ldap3.core.exceptions import LDAPBindError
from members.models import Member
logger = logging.getLogger(__name__)
host = "ldap://juri.fet.htu.tuwien.ac.at"
port = 389
class LdapBackend(ModelBackend):
def _check_ldap_user(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return False
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
return True
def _check_fet_member(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
# Get member infos from ldap
c.search(
search_base=userdn,
search_filter=f"(uid={username})",
search_scope="SUBTREE",
attributes=["givenName", "sn", "mail"],
)
firstname = c.response[0]["attributes"]["givenName"][0]
surname = c.response[0]["attributes"]["sn"][0]
mail = c.response[0]["attributes"]["mail"][0]
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
# Try to get the member. If it not exists, then create a new member.
try:
member = Member.objects.get(mailaccount=mail)
# Set username if not equal
if member.username != username:
member.username = username
logger.info("Member '%s' saved.", username)
member.save()
except Member.DoesNotExist:
member = Member()
member.firstname = firstname
member.surname = surname
member.username = username
member.nickname = username
member.mailaccount = mail
logger.info("Member '%s' created.", username)
member.save()
return True
def _check_finance_perm(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
# Check if member has finance permission
c.search(
search_base="CN=finance,OU=Groups,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at",
search_filter="(objectClass=posixGroup)",
search_scope="SUBTREE",
attributes=["memberUid"],
)
if username in c.response[0]["attributes"]["memberUid"]:
logger.info("User '%s' has finance permission.", username)
return True
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
return False
def authenticate(self, request, username=None, password=None):
if username is None or password is None:
return
# Set username to lower because fet2020 can only work with lowercase letters.
username = username.lower()
if not self._check_ldap_user(username, password):
return
if not self._check_fet_member(username, password):
return
try:
user = User.objects.get(username)
except User.DoesNotExist:
user = User.objects.create_user(username)
finally:
if not self.user_can_authenticate(user):
logger.info("User '%s' is inactive.", user.get_username())
return
# Add user to all groups
for elem in Group.objects.all():
elem.user_set.add(user)
# Delete finance group if no permission
if not self._check_finance_perm(username, password):
finance_group = Group.objects.get(name="finance")
finance_group.user_set.remove(user)
return user
class DebugBackend(ModelBackend):
def authenticate(self, request, username, password, **kwargs):
if (user := super().authenticate(request, username, password, **kwargs)) is None:
logger.info("User '%s' can't login. The user may not be a superuser.", username)
return None
# Try to get the member. If it not exists, then create a new member.
try:
member = Member.objects.get(mailaccount=user.email)
except Member.DoesNotExist:
member = Member()
member.username = username
member.nickname = username
member.mailaccount = user.email
logger.info("Member '%s' created.", username)
member.save()
return user

View File

@@ -1,54 +1,17 @@
import logging import logging
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .authentications import authentication, change_password, get_finance_perm from .authentications import change_password
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class LoginForm(AuthenticationForm): # TODO: fix me
def clean(self):
username = self.cleaned_data.get("username").lower()
password = self.cleaned_data.get("password")
if username is not None and password:
if (auth_user := authentication(username, password)) is None:
raise ValidationError(
"Bitte Benutzername und Passwort korrekt eingeben.",
code="invalid_login",
)
try:
self.user_cache = User.objects.get(username=auth_user.lower())
except User.DoesNotExist:
self.user_cache = User.objects.create_user(auth_user.lower())
finally:
self.confirm_login_allowed(self.user_cache)
# add user to all groups
for elem in Group.objects.all():
elem.user_set.add(self.user_cache)
# delete finance group if no permission
if not get_finance_perm(username, password):
finance_group = Group.objects.get(name="finance")
finance_group.user_set.remove(self.user_cache)
return self.cleaned_data
class LdapPasswordChangeForm(PasswordChangeForm): class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self): def clean_old_password(self):
old_password = self.cleaned_data.get("old_password") old_password = self.cleaned_data.get("old_password")
if not authentication(self.user, old_password):
raise ValidationError(
self.error_messages["password_incorrect"],
code="password_incorrect",
)
return old_password return old_password
def save(self): def save(self):

View File

@@ -10,11 +10,10 @@ from django.urls import reverse_lazy
from documents.etherpadlib import del_ep_cookie from documents.etherpadlib import del_ep_cookie
from .decorators import authenticated_user from .decorators import authenticated_user
from .forms import LdapPasswordChangeForm, LoginForm from .forms import LdapPasswordChangeForm
class AuthLoginView(LoginView): class AuthLoginView(LoginView):
authentication_form = LoginForm
redirect_authenticated_user = True redirect_authenticated_user = True
template_name = "authentications/login.html" template_name = "authentications/login.html"

View File

@@ -5,7 +5,8 @@ import environ
env = environ.Env( env = environ.Env(
# set casting, default value # set casting, default value
DEBUG=(bool, True), DEBUG=(str, "True"),
LDAP=(str, "False"),
MYSQL_HOST=(str, "mysql"), MYSQL_HOST=(str, "mysql"),
MYSQL_PORT=(int, 3306), MYSQL_PORT=(int, 3306),
MYSQL_DATABASE=(str, "fet2020db"), MYSQL_DATABASE=(str, "fet2020db"),
@@ -23,7 +24,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# DEBUGGING # DEBUGGING
DEBUG = env("DEBUG") DEBUG = (env("DEBUG").lower() == "true")
LDAP = (env("LDAP").lower() == "true")
# MODELS # MODELS
@@ -63,8 +65,13 @@ INSTALLED_APPS = [
# AUTHENTICATIONS # AUTHENTICATIONS
if not DEBUG and LDAP:
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "authentications.backends.LdapBackend",
]
else:
AUTHENTICATION_BACKENDS = [
"authentications.backends.DebugBackend",
] ]
LOGIN_REDIRECT_URL = "home" LOGIN_REDIRECT_URL = "home"

View File

@@ -17,7 +17,7 @@
{% if site_url %} {% if site_url %}
<a class="button" href="{{ site_url }}">Zurück zur FET Homepage</a> <a class="button" href="{{ site_url }}">Zurück zur FET Homepage</a>
{% endif %} {% endif %}
<a class="button" href="{% url 'authentications:password_change' %}">Passwort ändern</a> <!-- TODO: FIXME: <a class="button" href="{% url 'authentications:password_change' %}">Passwort ändern</a> -->
<a class="button" href="{% url 'admin:logout' %}">{% translate 'Log out' %}</a> <a class="button" href="{% url 'admin:logout' %}">{% translate 'Log out' %}</a>
{% endblock %} {% endblock %}
</div> </div>