change ldap auth to ldap backend to be able to program without ldap
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
162
fet2020/authentications/backends.py
Normal file
162
fet2020/authentications/backends.py
Normal 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
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user