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
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):
server = Server(host, port=port, use_ssl=True)
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
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.models import Group, User
from django.contrib.auth.forms import PasswordChangeForm
from django.core.exceptions import ValidationError
from .authentications import authentication, change_password, get_finance_perm
from .authentications import change_password
logger = logging.getLogger(__name__)
class LoginForm(AuthenticationForm):
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
# TODO: fix me
class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self):
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
def save(self):

View File

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

View File

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

View File

@@ -17,7 +17,7 @@
{% if site_url %}
<a class="button" href="{{ site_url }}">Zurück zur FET Homepage</a>
{% 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>
{% endblock %}
</div>