Compare commits

...

40 Commits

Author SHA1 Message Date
c46d84b023 Fix status codes, Refractor views 2019-06-13 01:42:47 +02:00
48abf000fc Add realm detail and realm admin view tests 2019-06-13 01:42:15 +02:00
a635d16a8a Change status codes, refractoring 2019-06-12 17:54:54 +02:00
df298dc6e0 Implement test for realm add and realm list views 2019-06-12 17:54:10 +02:00
8523ce0825 Refractor views, Add status codes to realm add 2019-06-12 03:47:32 +02:00
b82c610a9f Add realm add tests 2019-06-12 03:46:48 +02:00
b9b23d6627 Merge branch 'master' of https://git.wiai.de/mgoetz/ldap_account_manager 2019-06-12 02:14:45 +02:00
47034c9eb6 Add base dn setter 2019-06-12 02:11:31 +02:00
55ed18967c Refractor main view 2019-06-12 02:10:58 +02:00
6fa1ffbdff Initialize testing 2019-06-12 02:10:10 +02:00
MG
7b0bd4f17e Merge branch 'master' of git.stuve-bamberg.de:mgoetz/ldap_account_manager 2019-06-07 17:53:35 +02:00
MG
eca52bec63 Add toast infos 2019-06-07 17:51:44 +02:00
MG
a63426d1ea Add toast message on mail send 2019-06-07 17:34:13 +02:00
MG
09c422ed15 Fix #78; Fix empty django accounts 2019-06-07 15:58:13 +02:00
Linux User
15ab8b2ab6 Add email user and password for secure mail accounts 2019-05-30 20:02:25 +00:00
2237e35770 Implement working password change 2019-05-29 00:55:34 +02:00
8aca00e943 Implement pw reset without django user password setting 2019-05-28 23:53:44 +02:00
62d7b9fe8a Try to fix change pw 2019-05-28 23:22:58 +02:00
604f7af5ff Fix thuy again 2019-05-28 21:17:20 +02:00
b00ecd576b Standardize realm detail page 2019-05-28 20:33:15 +02:00
MG
7734a0d36f UI tweak 2019-05-18 17:53:47 +02:00
fac000e39c Add aktive user count 2019-05-17 14:14:35 +02:00
ee67e23e5f Fix wrong env setting for DELETION_WAIT_DAYS 2019-05-17 13:59:56 +02:00
16722682aa Remove now ignored migrations 2019-05-17 13:41:41 +02:00
fb960855f0 Ignore migrations and external data 2019-05-17 13:39:29 +02:00
MG
94dd1dd7a3 Add migrations, Removed migrations from ignore 2019-05-16 20:01:27 +02:00
c1d7c2b914 Remove group modify link if user gets deleted 2019-05-16 15:08:04 +02:00
b8416e7f8b Implement changable deletion wait time 2019-05-16 14:39:02 +02:00
a614b1f160 Update user deletion button visability 2019-05-16 14:29:00 +02:00
c2955968bc Change deletion message 2019-05-16 14:09:34 +02:00
00cea530ab Add deletion sign for normal user view 2019-05-16 02:30:05 +02:00
6cd5753056 Implement send deletion mail 2019-05-16 02:16:13 +02:00
0dc9ee0991 Add deletion information on user view, Implement deltion cancel operation 2019-05-16 01:48:35 +02:00
95a18884bb Implement deletion information on user list 2019-05-16 01:21:10 +02:00
10cda0783a Removed unused imports 2019-05-16 00:57:33 +02:00
f453d5745c Implement delete command 2019-05-16 00:56:58 +02:00
815fff4094 Merge branch 'master' into feature_deletion_system 2019-05-15 23:43:15 +02:00
6a23e196ac Merge branch 'master' of https://git.wiai.de/mgoetz/ldap_account_manager 2019-05-15 23:42:31 +02:00
ab9052bcac Inint implementation init system 2019-05-15 23:41:38 +02:00
62ffd04fbd Add new deleted user model, Add command to list deletable users 2019-05-15 17:40:55 +02:00
34 changed files with 1034 additions and 226 deletions

1
.gitignore vendored
View File

@ -118,5 +118,6 @@ dmypy.json
migrations/
logs/
db/
data/
!docker/ldap/data/var/
*.mdb

View File

@ -28,3 +28,15 @@ LDAP_GROUP_NAME_ATTR=cn
EMAIL_BACKEND=file
DEFAULT_FROM_EMAIL=
SERVER_EMAIL=
DELETION_WAIT_DAYS=14
#EMAIL_BACKEND=smtp
#EMAIL_HOST=smtp.uni-bamberg.de
#EMAIL_PORT=587
#EMAIL_USE_TLS=False
#EMAIL_USE_SSL=False
#DEFAULT_FROM_EMAIL=vergesslich@uni-bamberg.de
##DEFAULT_FROM_EMAIL=fachschaft-wiai.stuve@uni-bamberg.de
#SERVER_EMAIL=fachschaft-wiai.stuve@uni-bamberg.de

32
example.env Normal file
View File

@ -0,0 +1,32 @@
DJANGO_SETTINGS_MODULE=core.docker_settings
DOMAIN=localhost
SITE_NAME=LAMa
SECRET_KEY=supersecret
ALLOWED_HOSTS=localhost
DATABASE_HOST=dblama
DATABASE_PORT=5432
POSTGRES_USER=lama
POSTGRES_PASSWORD=secret
DEBUG=True
LDAP_SERVER_URI=ldap://ldap:389
LDAP_ADMIN_USER_NAME=cn=admin,dc=test,dc=de
LDAP_ADMIN_USER_PASSWORD=secret
LDAP_USER_ENTRY=dc=test,dc=de
LDAP_USER_SELECTOR=(uid=%(user)s)
LDAP_GROUP_ENTRY=dc=test,dc=de
LDAP_GROUP_SELECTOR=(objectClass=groupOfNames)
LDAP_GROUP_NAME_ATTR=cn
EMAIL_BACKEND=file
DEFAULT_FROM_EMAIL=
SERVER_EMAIL=
DELETION_WAIT_DAYS=14

View File

@ -3,4 +3,4 @@ python-ldap==3.2.0
django-auth-ldap==1.7.0
django-ldapdb==1.3.0
Jinja2==2.10
Pillow==2.2.1
Pillow==2.2.1

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Realm
from .models import Realm, DeletedUser
# Register your models here.
admin.site.register(Realm)
# admin.site.register(DeletedUser)
admin.site.register(DeletedUser)

View File

@ -0,0 +1,64 @@
import json
from django.core.exceptions import ObjectDoesNotExist
from django.core.management.base import BaseCommand
from django.utils import timezone
from account_helper.models import DeletedUser
from account_manager.models import LdapGroup, LdapUser
class Command(BaseCommand):
help = 'Get and delete the deleted marked users'
def add_arguments(self, parser):
parser.add_argument(
'--delete',
action='store_true',
help='Delete users which deletion time is lower than the current date',
)
parser.add_argument(
'--json',
action='store_true',
help='Return an json encoded String',
)
parser.add_argument(
'--all',
action='store_true',
help='Delete all marked user, --delete is required',
)
def handle(self, *args, **options):
if options['all']:
deletables = DeletedUser.objects.all()
else:
deletables = DeletedUser.objects.filter(deletion_date__lte=timezone.now())
output = ""
if options['json']:
json_output = {'deletables': []}
for deletable in deletables:
json_output['deletables'].append({'ldap_dn': deletable.ldap_dn, 'username': deletable.user.username})
output = json.dumps(json_output)
else:
for user in deletables:
output += f'{user}\n'
if options['delete']:
LdapUser.base_dn = LdapUser.ROOT_DN
for user in deletables:
ldap_user = LdapUser.objects.get(dn=user.ldap_dn)
LdapGroup.remove_user_from_groups(ldap_user.dn)
ldap_user.delete()
try:
user.user.delete()
user.delete()
except ObjectDoesNotExist:
pass
if not options['json']:
output += '\nSuccessfully deleted all listed users'
if output:
self.stdout.write(self.style.SUCCESS(output))
else:
for deletable in deletables:
self.stdout.write(self.style.SUCCESS(deletable))

View File

@ -1,5 +1,7 @@
from django.contrib.auth.models import Group, User
from django.db import models
from django.utils import timezone
from django.conf import settings
# Create your models here.
@ -14,9 +16,15 @@ class Realm(models.Model):
return f'{self.name} - {self.ldap_base_dn}'
# class DeletedUser(models.Model):
# deletion_date = models.DateField(auto_now=True)
# user = models.ForeignKey(User, on_delete=models.CASCADE)
#
# def __str__(self):
# return f'{self.user.username} - {self.deletion_date}'
def get_deletion_time():
return timezone.now() + timezone.timedelta(settings.DELETION_WAIT_DAYS)
class DeletedUser(models.Model):
deletion_marker_date = models.DateField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
ldap_dn = models.CharField(max_length=512, unique=True)
deletion_date = models.DateField(default=get_deletion_time)
def __str__(self):
return f'{self.user.username} - {self.deletion_marker_date} - {self.deletion_date} - {self.ldap_dn}'

View File

@ -1,7 +1,8 @@
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm
from account_manager.utils.django_user import update_dajngo_user
from .models import LdapUser, LdapGroup
from django.forms import modelformset_factory
import logging
@ -83,6 +84,10 @@ class LdapPasswordResetForm(PasswordResetForm):
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
LdapUser.base_dn = LdapUser.ROOT_DN
ldap_users = LdapUser.objects.filter(email=email)
for ldap_user in ldap_users:
update_dajngo_user(ldap_user)
logger.debug('Pasword reset get users')
active_users = UserModel._default_manager.filter(**{
'%s__iexact' % UserModel.get_email_field_name(): email,
@ -90,3 +95,11 @@ class LdapPasswordResetForm(PasswordResetForm):
})
logger.debug((u for u in active_users))
return (u for u in active_users)
class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self):
"""
Validates that the old_password field is correct.
"""
return "ralf"

View File

@ -1,20 +1,19 @@
import logging
import re
from smtplib import SMTPAuthenticationError, SMTPConnectError, SMTPException
from socket import timeout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError
from django.shortcuts import render, redirect, HttpResponse
from datetime import datetime, timedelta
from django.shortcuts import render, redirect
from ldap import LDAPError
from account_helper.models import Realm
from account_manager.utils.mail_utils import realm_send_mail
from account_manager.utils.main_views import render_permission_denied_view, render_realm_detail_view, \
get_users_home_view
from .forms import RealmAddForm, RealmUpdateForm
from .models import LdapGroup, LdapUser
from ldap import LDAPError
logger = logging.getLogger(__name__)
@ -27,44 +26,20 @@ def is_realm_admin(view_func):
admin_group__user__username__contains=request.user.username)) > 0):
return view_func(request, *args, **kwargs)
else:
return redirect('permission-denied')
return render_permission_denied_view(request)
return decorator
@login_required
def realm_list(request):
user = request.user
if user.is_superuser:
django_user = request.user
if django_user.is_superuser:
realms = Realm.objects.order_by('name').all()
else:
realms = Realm.objects.filter(admin_group__user__username__contains=user.username).order_by('name').order_by(
'name')
show_user = request.GET.get('show_user', False)
if show_user or (len(realms) == 0 and not user.is_superuser):
try:
LdapUser.base_dn = LdapUser.ROOT_DN
user = LdapUser.objects.get(username=user.username)
realm_base_dn = re.compile('(uid=[a-zA-Z0-9_]*),(ou=[a-zA-Z_]*),(.*)').match(user.dn).group(3)
realm = Realm.objects.get(ldap_base_dn=realm_base_dn)
realms = Realm.objects.filter(admin_group__user__username__contains=django_user.username).order_by('name')
return redirect('user-detail', realm.id, user.dn)
except ObjectDoesNotExist as err:
logger.info('Anmeldung fehlgeschlagen', err)
return HttpResponse("Invalid login. Please try again.")
elif len(realms) == 1:
return redirect('realm-detail', realms[0].id)
else:
realm_wrappers = []
for realm in realms:
realm_wrappers.append(_get_group_user_count_wrapper(realm))
return render(request, 'realm/realm_home.jinja2', {'realms': realms, 'realm_wrappers': realm_wrappers})
def _get_group_user_count_wrapper(realm):
LdapUser.base_dn = f'ou=people,{realm.ldap_base_dn}'
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
return {'realm': realm, 'group_count': LdapGroup.objects.count(), 'user_count': LdapUser.objects.count()}
return get_users_home_view(request, django_user, realms)
@login_required
@ -79,21 +54,21 @@ def realm_add(request):
try:
base_dn_available(ldap_base_dn)
realm_obj = Realm.objects.create(name=name, ldap_base_dn=ldap_base_dn)
realm_obj.save()
return redirect('realm-detail', realm_obj.id)
realm = Realm.objects.create(name=name, ldap_base_dn=ldap_base_dn)
realm.save()
return render_realm_detail_view(request, realm.id, status_code=201)
except IntegrityError as err:
# TODO: Load no extra fail view, use current add view
return render(request, 'realm/realm_add_failed.jinja2',
{'realm_name': name, 'error': err})
{'realm_name': name, 'error': err}, status=409)
except LDAPError as err:
logger.debug("Ldap Error", err)
return render(request, 'realm/realm_add_failed.jinja2',
{'realm_name': name})
{'realm_name': name}, status=409)
else:
form = RealmAddForm()
return render(request, 'realm/realm_add.jinja2', {'realms': realms, 'form': form})
else:
redirect('permission-denied')
return render_permission_denied_view(request)
def base_dn_available(base_dn):
@ -105,28 +80,7 @@ def base_dn_available(base_dn):
@login_required
@is_realm_admin
def realm_detail(request, realm_id):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = realm.ldap_base_dn
inactive_users = LdapUser.get_inactive_users().count()
logger.info(inactive_users)
ldap_admin_group, ldap_default_group = get_default_admin_group(realm)
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm, 'ldap_admin_group': ldap_admin_group, 'ldap_default_group': ldap_default_group,
'inactive_user_count': inactive_users, 'users_count': LdapUser.objects.all().count()})
def get_default_admin_group(realm):
ldap_admin_group = None
ldap_default_group = None
if realm.admin_group:
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
ldap_admin_group = LdapGroup.objects.get(name=realm.admin_group.name)
if realm.default_group:
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
ldap_default_group = LdapGroup.objects.get(name=realm.default_group.name)
return ldap_admin_group, ldap_default_group
return render_realm_detail_view(request, realm_id)
@login_required
@ -134,19 +88,17 @@ def get_default_admin_group(realm):
def realm_update(request, realm_id):
if request.user.is_superuser:
realm = Realm.objects.get(id=realm_id)
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
ldap_admin_group = None
if realm.admin_group:
ldap_admin_group = LdapGroup.objects.get(name=realm.admin_group.name)
ldap_default_group = None
if realm.default_group:
ldap_default_group = LdapGroup.objects.get(name=realm.default_group.name)
data = {'id': realm.id,
'ldap_base_dn': realm.ldap_base_dn,
'name': realm.name,
'email': realm.email,
'admin_group': ldap_admin_group,
'default_group': ldap_default_group}
ldap_admin_group = None if not realm.admin_group else LdapGroup.objects.get(name=realm.admin_group.name)
ldap_default_group = None if not realm.default_group else LdapGroup.objects.get(name=realm.default_group.name)
form_data = {'id': realm.id,
'ldap_base_dn': realm.ldap_base_dn,
'name': realm.name,
'email': realm.email,
'admin_group': ldap_admin_group,
'default_group': ldap_default_group}
if request.method == 'POST':
form = RealmUpdateForm(request.POST)
if form.is_valid():
@ -154,23 +106,16 @@ def realm_update(request, realm_id):
realm.ldap_base_dn = form.cleaned_data['ldap_base_dn']
realm.email = form.cleaned_data['email']
admin_ldap_group = form.cleaned_data['admin_group']
if admin_ldap_group:
realm.admin_group, _ = Group.objects.get_or_create(name=admin_ldap_group.name)
else:
realm.admin_group = None
realm.admin_group = None if not admin_ldap_group else admin_ldap_group.get_django_group()
default_ldap_group = form.cleaned_data['default_group']
if default_ldap_group:
realm.default_group, _ = Group.objects.get_or_create(name=default_ldap_group.name)
else:
realm.default_group = None
realm.default_group = None if not default_ldap_group else default_ldap_group.get_django_group()
realm.save()
return redirect('realm-detail', realm.id)
return render_realm_detail_view(request, realm_id, status_code=200)
return render(request, 'realm/realm_update.jinja2', {'realm': realm, 'form': form}, status=422)
else:
form = RealmUpdateForm(initial=data)
form = RealmUpdateForm(initial=form_data)
return render(request, 'realm/realm_update.jinja2', {'realm': realm, 'form': form})
else:
realm = Realm.objects.get(id=realm_id)
return render(request, 'realm/realm_update.jinja2', {'realm': realm})
return render_permission_denied_view(request)
@login_required
@ -209,38 +154,27 @@ def realm_delete(request, realm_id):
def permission_denied(request):
return render(request, 'permission_denied.jinja2', {})
return render_permission_denied_view(request)
def realm_email_test(request, realm_id):
realm = Realm.objects.get(id=realm_id)
ldap_admin_group, ldap_default_group = get_default_admin_group(realm)
test_msg = f'Du hast die Mail Konfiguration für {realm.name} erfolgreich abgeschlossen.'
success_msg = 'Test erfolgreich'
error_msg_auth = f'Mail konnte nicht versendet werden, Anmeldedaten inkorrekt.'
error_msg_connect = f'Mail konnte nicht versendet werden. Verbindungsaufbau abgelehnt. ' \
f'Bitte überprüfen sie die Server Addresse und den Port'
error_msg_timeout = f'Mail konnte nicht versendet werden. Zeitüberschreitung beim Verbindungsaufbau. ' \
f'Bitte überprüfen sie die Server Addresse und den Port'
error_msg_smtp = f'Mail konnte nicht versendet werden. Bitte kontaktieren sie den Administrator'
try:
realm_send_mail(realm, realm.email, f'{realm.name} Test Mail',
f'Du hast die Mail Konfiguration für {realm.name} erfolgreich abgeschlossen.')
except SMTPAuthenticationError as err:
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm, 'error': f'Mail konnte nicht versendet werden, Anmeldedaten inkorrekt.',
'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group})
except SMTPConnectError as err:
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm,
'error': f'Mail konnte nicht versendet werden. Verbindungsaufbau abgelehnt. Bitte überprüfen sie die Server Addresse und den Port',
'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group})
except timeout as err:
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm,
'error': f'Mail konnte nicht versendet werden. Zeitüberschreitung beim Verbindungsaufbau. Bitte überprüfen sie die Server Addresse und den Port',
'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group})
realm_send_mail(realm, realm.email, f'{realm.name} Test Mail', test_msg)
except SMTPAuthenticationError:
return render_realm_detail_view(request, realm_id, error_headline="Testmail", error_text=error_msg_auth)
except SMTPConnectError:
return render_realm_detail_view(request, realm_id, error_headline="Testmail", error_text=error_msg_connect)
except timeout:
return render_realm_detail_view(request, realm_id, error_headline="Testmail", error_text=error_msg_timeout)
except SMTPException:
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm,
'error': f'Mail konnte nicht versendet werden. Bitte kontaktieren sie den Administrator',
'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group})
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm, 'notice': 'Test erfolgreich', 'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group})
return render_realm_detail_view(request, realm_id, error_headline="Testmail", error_text=error_msg_smtp)
return render_realm_detail_view(request, realm_id, success_headline="Testmail", success_text=success_msg)

View File

@ -4,17 +4,22 @@ import os
import re
from datetime import datetime, timedelta
from django.contrib.auth.models import User
from django.contrib.auth.models import User, Group
from django.core.exceptions import ObjectDoesNotExist
from django.db import OperationalError
from django.db.models import Q
from ldap import NO_SUCH_OBJECT, ALREADY_EXISTS
from ldapdb.models import fields as ldap_fields
from ldapdb.models.base import Model
from account_helper.models import DeletedUser
from account_manager.utils.dbldap import get_filterstr
from account_manager.utils.mail_utils import send_welcome_mail
logger = logging.getLogger(__name__)
import ldap
class LdapUser(Model):
"""
@ -57,12 +62,29 @@ class LdapUser(Model):
else:
raise ALREADY_EXISTS('User already exists')
@staticmethod
def get_extended_user(ldap_user):
wrapper = {'user': ldap_user}
try:
wrapper['deleted_user'] = DeletedUser.objects.get(ldap_dn=ldap_user.dn)
except ObjectDoesNotExist:
wrapper['deleted_user'] = {}
try:
django_user = User.objects.get(username=ldap_user.username)
if django_user.last_login:
wrapper['active'] = True
else:
wrapper['active'] = False
except ObjectDoesNotExist:
wrapper['active'] = False
return wrapper
@staticmethod
def password_reset(user, raw_password):
LdapUser.base_dn = LdapUser.ROOT_DN
ldap_user = LdapUser.objects.get(username=user.username)
ldap_user.password = raw_password
LdapUser.base_dn = re.compile('(uid=[a-zA-Z0-9_]*),(.*)').match(ldap_user.dn).group(2)
LdapUser.base_dn = re.compile('(uid=[a-zA-Z0-9_-]*),(.*)').match(ldap_user.dn).group(2)
ldap_user.save()
@staticmethod
@ -110,6 +132,13 @@ class LdapUser(Model):
return (LdapUser.objects.filter(last_login__lte=last_semester) | LdapUser.objects.exclude(
last_login__lte=datetime.now() + timedelta(days=1)))
def get_users_realm_base_dn(self):
return re.compile('(uid=[a-zA-Z0-9_-]*),(ou=[a-zA-Z_-]*),(.*)').match(self.dn).group(3)
@staticmethod
def set_root_dn(realm):
LdapUser.base_dn = f'ou=people,{realm.ldap_base_dn}'
class LdapGroup(Model):
"""
@ -130,6 +159,24 @@ class LdapGroup(Model):
LdapGroup.base_dn = group_base_dn
return LdapGroup.objects.filter(members=user.dn)
@staticmethod
def remove_user_from_groups(ldap_user_dn, user_groups=None):
if not user_groups:
LdapGroup.base_dn = LdapGroup.ROOT_DN
user_groups = LdapGroup.objects.filter(members__contains=ldap_user_dn)
for group in user_groups:
LdapGroup.base_dn = re.compile('cn=([a-zA-Z0-9_-]*),(ou=[a-zA-Z_]*.*)').match(group.dn).group(2)
group.members.remove(ldap_user_dn)
group.save()
def get_django_group(self):
django_group, _ = Group.objects.get_or_create(name=self.name)
return django_group
@staticmethod
def set_root_dn(realm):
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
def __str__(self):
return self.name

View File

View File

View File

@ -1,3 +1,3 @@
from django.test import TestCase
# Create your tests here.
# Create your tests here.

View File

@ -0,0 +1,430 @@
import logging
from django.contrib.auth.models import User, Group
from django.test import TestCase
# Create your tests here.
from django.urls import reverse
from account_helper.models import Realm
from account_manager.models import LdapUser, LdapGroup
class RealmHomeViewTest(TestCase):
@classmethod
def setUpTestData(cls):
# User.objects.get_or_create(username="test", email="test@test.de")
User.objects.create_superuser(
username='test_superuser',
password=RealmHomeViewTest.get_password(),
email='test@test.de',
is_staff=True,
is_superuser=True,
is_active=True,
)
def create_ldap_objects(self):
self.realm_1, _ = Realm.objects.get_or_create(name="test_realm_1",
ldap_base_dn="ou=test,ou=fachschaften,dc=test,dc=de")
self.realm_2, _ = Realm.objects.get_or_create(name="test_realm_2",
ldap_base_dn="ou=test2,ou=fachschaften,dc=test,dc=de")
LdapUser.set_root_dn(self.realm_1)
self.ldap_user_multiple_admin, _ = LdapUser.objects.get_or_create(username="test_multi_admin",
email="test@test.de",
password=RealmHomeViewTest.get_password(),
first_name="max",
last_name="musterstudent")
self.ldap_user_admin, _ = LdapUser.objects.get_or_create(username="test_admin", email="test@test.de",
password=RealmHomeViewTest.get_password(),
first_name="max",
last_name="musterstudent")
self.ldap_user, _ = LdapUser.objects.get_or_create(username="test", email="test@test.de",
password=RealmHomeViewTest.get_password(),
first_name="max",
last_name="musterstudent")
LdapGroup.set_root_dn(self.realm_1)
self.realm_1_ldap_group = LdapGroup.objects.create(name="test_realm_1_admin_group",
members=[self.ldap_user_multiple_admin.dn,
self.ldap_user_admin.dn])
LdapGroup.set_root_dn(self.realm_1)
self.realm_2_ldap_group = LdapGroup.objects.create(name="test_realm_2_admin_group",
members=[self.ldap_user_multiple_admin.dn])
logging.disable(logging.DEBUG)
self.realm_1.admin_group = self.realm_1_ldap_group.get_django_group()
self.realm_1.save()
self.realm_2.admin_group = self.realm_2_ldap_group.get_django_group()
self.realm_2.save()
@classmethod
def get_password(cls):
return "12345678"
def setUp(self):
self.create_ldap_objects()
self.django_superuser = User.objects.get(username="test_superuser")
def tearDown(self):
self.clear_ldap_objects()
self.django_superuser.delete()
logging.disable(logging.NOTSET)
def clear_ldap_objects(self):
self.realm_1.delete()
self.realm_2.delete()
self.ldap_user_multiple_admin.delete()
self.ldap_user_admin.delete()
self.ldap_user.delete()
self.realm_1_ldap_group.delete()
self.realm_2_ldap_group.delete()
def test_without_login(self):
response = self.client.get(reverse('realm-home'))
self.assertEqual(response.status_code, 302)
def test_with_user_login(self):
self.client.login(username=self.ldap_user.username, password=RealmHomeViewTest.get_password())
response = self.client.get(reverse('realm-home'))
self.assertContains(response, 'Profil löschen', status_code=200)
self.client.logout()
def test_with_admin_login(self):
self.client.login(username=self.ldap_user_admin.username, password=RealmHomeViewTest.get_password())
response = self.client.get(reverse('realm-home'))
self.assertContains(response, 'Bereich ', status_code=200)
self.client.logout()
def test_with_admin_multiple_realms_login(self):
self.client.login(username=self.ldap_user_multiple_admin.username, password=RealmHomeViewTest.get_password())
response = self.client.get(reverse('realm-home'))
self.assertContains(response, 'Bereiche', status_code=200)
self.client.logout()
def test_with_superuser_login(self):
self.client.login(username=self.django_superuser.username, password=RealmHomeViewTest.get_password())
response = self.client.get(reverse('realm-home'))
self.assertContains(response, 'Bereiche', status_code=200)
self.client.logout()
class RealmAddViewTest(TestCase):
@classmethod
def setUpTestData(cls):
realm, _ = Realm.objects.get_or_create(name="test", ldap_base_dn="ou=test,ou=fachschaften,dc=test,dc=de")
LdapUser.set_root_dn(realm)
LdapUser.objects.get_or_create(username="test", email="test@test.de",
password=RealmAddViewTest.get_password(),
first_name="max",
last_name="musterstudent")
User.objects.get_or_create(username="test", email="test@test.de")
logging.disable(logging.DEBUG)
@classmethod
def get_password(cls):
return "12345678"
def setUp(self):
self.realm = Realm.objects.get(name="test")
LdapUser.set_root_dn(self.realm)
self.ldap_user = LdapUser.objects.get(username="test")
self.django_user = User.objects.get(username="test")
self.django_superuser = User.objects.create_superuser(
username='superuser_test',
password='test',
email='test@test.de',
is_staff=True,
is_superuser=True,
is_active=True,
)
def tearDown(self):
self.realm.delete()
self.ldap_user.delete()
self.django_user.delete()
logging.disable(logging.NOTSET)
def test_without_login(self):
response = self.client.get(reverse('realm-add'))
self.assertEqual(response.status_code, 302)
def test_with_login(self):
self.client.login(username=self.ldap_user.username, password=RealmAddViewTest.get_password())
response = self.client.get(reverse('realm-add'))
self.assertContains(response, 'Leider hast du keine Rechte', status_code=403)
self.client.logout()
def test_with_login_and_post_valid_form(self):
self.client.login(username=self.ldap_user.username, password=RealmAddViewTest.get_password())
response = self.client.post(reverse('realm-add'),
{'name': 'test', 'ldap_base_dn': 'ou=test,ou=fachschaften,dc=test,dc=de'})
self.assertContains(response, 'Leider hast du keine Rechte', status_code=403)
self.client.logout()
def test_with_super_user_login(self):
self.client.login(username=self.django_superuser.username, password='test')
response = self.client.get(reverse('realm-add'))
self.assertContains(response, 'Neuen Bereich anlegen', status_code=200)
self.client.logout()
def test_with_super_user_login_add_realm(self):
realm = Realm.objects.get(name=self.realm.name)
realm.delete()
self.client.login(username=self.django_superuser.username, password='test')
response = self.client.post(reverse('realm-add'),
{'name': 'test', 'ldap_base_dn': 'ou=test,ou=fachschaften,dc=test,dc=de'})
self.assertContains(response, 'Bereich test', status_code=201)
self.client.logout()
self.realm, _ = Realm.objects.get_or_create(name="test", ldap_base_dn="ou=test,ou=fachschaften,dc=test,dc=de")
def test_with_super_user_login_add_extisting_realm(self):
self.client.login(username=self.django_superuser.username, password='test')
response = self.client.post(reverse('realm-add'),
{'name': 'test', 'ldap_base_dn': 'ou=test,ou=fachschaften,dc=test,dc=de'})
self.assertContains(response, 'Das hinzufügen des Bereichs ist fehlgeschlagen.', status_code=409)
self.client.logout()
def test_with_super_user_login_add_extisting_realm_with_different_name(self):
self.client.login(username=self.django_superuser.username, password='test')
response = self.client.post(reverse('realm-add'),
{'name': 'test_new', 'ldap_base_dn': 'ou=test,ou=fachschaften,dc=test,dc=de'})
self.assertContains(response, 'Das hinzufügen des Bereichs ist fehlgeschlagen.', status_code=409)
self.client.logout()
def test_with_super_user_login_add_realm_with_not_existing_ldap_base_dn(self):
self.client.login(username=self.django_superuser.username, password='test')
response = self.client.post(reverse('realm-add'),
{'name': 'test_not_extisting_ldap_dn',
'ldap_base_dn': 'ou=not_exists,ou=fachschaften,dc=test,dc=de'})
self.assertContains(response, 'Das hinzufügen des Bereichs ist fehlgeschlagen.', status_code=409)
self.client.logout()
class RealmDetailViewTest(TestCase):
@classmethod
def setUpTestData(cls):
# User.objects.get_or_create(username="test", email="test@test.de")
User.objects.create_superuser(
username='test_superuser',
password=RealmDetailViewTest.get_password(),
email='test@test.de',
is_staff=True,
is_superuser=True,
is_active=True,
)
def create_ldap_objects(self):
self.realm_1, _ = Realm.objects.get_or_create(name="test_realm_1",
ldap_base_dn="ou=test,ou=fachschaften,dc=test,dc=de")
LdapUser.set_root_dn(self.realm_1)
self.ldap_user_admin, _ = LdapUser.objects.get_or_create(username="test_admin", email="test@test.de",
password=RealmDetailViewTest.get_password(),
first_name="max",
last_name="musterstudent")
self.ldap_user, _ = LdapUser.objects.get_or_create(username="test", email="test@test.de",
password=RealmDetailViewTest.get_password(),
first_name="max",
last_name="musterstudent")
LdapGroup.set_root_dn(self.realm_1)
self.realm_1_ldap_group = LdapGroup.objects.create(name="test_realm_1_admin_group",
members=[self.ldap_user_admin.dn])
logging.disable(logging.DEBUG)
self.realm_1.admin_group = self.realm_1_ldap_group.get_django_group()
self.realm_1.save()
@classmethod
def get_password(cls):
return "12345678"
def setUp(self):
self.create_ldap_objects()
self.django_superuser = User.objects.get(username="test_superuser")
def tearDown(self):
self.clear_ldap_objects()
self.django_superuser.delete()
logging.disable(logging.NOTSET)
def clear_ldap_objects(self):
self.realm_1.delete()
self.ldap_user_admin.delete()
self.ldap_user.delete()
self.realm_1_ldap_group.delete()
def test_without_login(self):
response = self.client.get(reverse('realm-detail', args=[self.realm_1.id]))
self.assertEqual(response.status_code, 302)
def test_with_user_login(self):
self.client.login(username=self.ldap_user.username, password=RealmDetailViewTest.get_password())
response = self.client.get(reverse('realm-detail', args=[self.realm_1.id]))
self.assertContains(response, 'Leider hast du keine Rechte', status_code=403)
self.client.logout()
def test_with_admin_login(self):
self.client.login(username=self.ldap_user_admin.username, password=RealmDetailViewTest.get_password())
response = self.client.get(reverse('realm-detail', args=[self.realm_1.id]))
self.assertContains(response, 'Bereich ', status_code=200)
self.client.logout()
def test_with_superuser_login(self):
self.client.login(username=self.django_superuser.username, password=RealmDetailViewTest.get_password())
response = self.client.get(reverse('realm-detail', args=[self.realm_1.id]))
self.assertContains(response, 'Bereich', status_code=200)
self.client.logout()
class RealmUpdateViewTest(TestCase):
@classmethod
def setUpTestData(cls):
# User.objects.get_or_create(username="test", email="test@test.de")
User.objects.create_superuser(
username='test_superuser',
password=RealmUpdateViewTest.get_password(),
email='test@test.de',
is_staff=True,
is_superuser=True,
is_active=True,
)
def create_ldap_objects(self):
self.realm_1, _ = Realm.objects.get_or_create(name="test_realm_1",
ldap_base_dn="ou=test,ou=fachschaften,dc=test,dc=de",
email="test.realm@test.de")
self.realm_2, _ = Realm.objects.get_or_create(name="test_realm_2",
ldap_base_dn="ou=test2,ou=fachschaften,dc=test,dc=de")
LdapUser.set_root_dn(self.realm_1)
self.ldap_user_multiple_admin, _ = LdapUser.objects.get_or_create(username="test_multi_admin",
email="test@test.de",
password=RealmUpdateViewTest.get_password(),
first_name="max",
last_name="musterstudent")
self.ldap_user_admin, _ = LdapUser.objects.get_or_create(username="test_admin", email="test@test.de",
password=RealmUpdateViewTest.get_password(),
first_name="max",
last_name="musterstudent")
self.ldap_user, _ = LdapUser.objects.get_or_create(username="test", email="test@test.de",
password=RealmUpdateViewTest.get_password(),
first_name="max",
last_name="musterstudent")
LdapGroup.set_root_dn(self.realm_1)
self.realm_1_ldap_group = LdapGroup.objects.create(name="test_realm_1_admin_group",
members=[self.ldap_user_multiple_admin.dn,
self.ldap_user_admin.dn])
LdapGroup.set_root_dn(self.realm_1)
self.realm_2_ldap_group = LdapGroup.objects.create(name="test_realm_2_admin_group",
members=[self.ldap_user_multiple_admin.dn])
LdapGroup.set_root_dn(self.realm_1)
self.realm_3_ldap_group = LdapGroup.objects.create(name="test_realm_3_admin_group",
members=[self.ldap_user_admin.dn])
logging.disable(logging.DEBUG)
self.realm_1.admin_group = self.realm_1_ldap_group.get_django_group()
self.realm_1.save()
self.realm_2.admin_group = self.realm_2_ldap_group.get_django_group()
self.realm_2.save()
@classmethod
def get_password(cls):
return "12345678"
def setUp(self):
self.create_ldap_objects()
self.django_superuser = User.objects.get(username="test_superuser")
def tearDown(self):
self.clear_ldap_objects()
self.django_superuser.delete()
logging.disable(logging.NOTSET)
def clear_ldap_objects(self):
self.realm_1.delete()
self.realm_2.delete()
self.ldap_user_multiple_admin.delete()
self.ldap_user_admin.delete()
self.ldap_user.delete()
self.realm_1_ldap_group.delete()
self.realm_2_ldap_group.delete()
self.realm_3_ldap_group.delete()
def test_without_login(self):
response = self.client.get(reverse('realm-update', args=[self.realm_1.id]))
self.assertEqual(response.status_code, 302)
def test_with_user_login(self):
self.client.login(username=self.ldap_user.username, password=RealmUpdateViewTest.get_password())
response = self.client.get(reverse('realm-update', args=[self.realm_1.id]))
self.assertContains(response, 'Leider hast du keine Rechte', status_code=403)
self.client.logout()
def test_with_admin_login(self):
self.client.login(username=self.ldap_user_admin.username, password=RealmUpdateViewTest.get_password())
response = self.client.get(reverse('realm-update', args=[self.realm_1.id]))
self.assertContains(response, 'Leider hast du keine Rechte', status_code=403)
self.client.logout()
def test_with_superuser_login(self):
self.client.login(username=self.django_superuser.username, password=RealmUpdateViewTest.get_password())
response = self.client.get(reverse('realm-update', args=[self.realm_1.id]))
self.assertContains(response, '<label for="id_name">Bereichsname</label>', status_code=200)
self.client.logout()
def test_with_superuser_login_post_single_changes(self):
self.client.login(username=self.django_superuser.username, password=RealmUpdateViewTest.get_password())
new_name = "new test realm"
new_email = "newtest@test.de"
new_admin_group = self.realm_1_ldap_group
new_default_group = self.realm_3_ldap_group
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name, 'email': new_email,
'ldap_base_dn': self.realm_1.ldap_base_dn})
self.assertContains(response, 'Nutzeranzahl', status_code=200)
self.realm_1.refresh_from_db()
self.assertEqual(self.realm_1.name, new_name)
self.assertEqual(self.realm_1.email, new_email)
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name, 'email': new_email,
'ldap_base_dn': self.realm_1.ldap_base_dn, 'admin_group': new_admin_group.name})
self.assertContains(response, 'Nutzeranzahl', status_code=200)
self.realm_1.refresh_from_db()
django_group = Group.objects.get(name=new_admin_group.name)
self.assertEqual(self.realm_1.admin_group, django_group)
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name, 'email': new_email,
'ldap_base_dn': self.realm_1.ldap_base_dn,
'default_group': new_default_group.name})
self.assertContains(response, 'Nutzeranzahl', status_code=200)
self.realm_1.refresh_from_db()
django_group = Group.objects.get(name=new_default_group.name)
self.assertEqual(self.realm_1.default_group, django_group)
self.client.logout()
def test_with_superuser_login_post_with_missing_data(self):
self.client.login(username=self.django_superuser.username, password=RealmUpdateViewTest.get_password())
new_name = "new test realm"
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name,
'ldap_base_dn': self.realm_1.ldap_base_dn})
self.assertContains(response, '<label for="id_name">Bereichsname</label>', status_code=422)
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'email': "test@test.de",
'ldap_base_dn': self.realm_1.ldap_base_dn})
self.assertContains(response, '<label for="id_name">Bereichsname</label>', status_code=422)
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name, 'email': "test@test.de"})
self.assertContains(response, '<label for="id_name">Bereichsname</label>', status_code=422)
response = self.client.post(reverse('realm-update', args=[self.realm_1.id]),
{'name': new_name, 'email': "abc",
'ldap_base_dn': self.realm_1.ldap_base_dn})
self.assertContains(response, '<label for="id_name">Bereichsname</label>', status_code=422)
self.client.logout()

View File

@ -53,6 +53,8 @@ urlpatterns = [
name='realm-multiple-user-delete'),
path('realm/<int:realm_id>/user/delete/multiple/inactive/', user_views.realm_multiple_user_delete_inactive,
name='realm-multiple-user-delete-inactive'),
path('realm/<int:realm_id>/user/delete/<str:user_dn>/cancel/', user_views.realm_user_delete_cancel,
name='realm-user-delete-cancel'),
# Realm Group
path('realm/<int:realm_id>/groups/', group_views.realm_groups, name='realm-group-list'),
@ -78,6 +80,8 @@ urlpatterns = [
name='user-delete'),
path('accounts/reset/<uidb64>/<token>/', user_views.LdapPasswordResetConfirmView.as_view(),
name='ldap_password_reset_confirm'),
path('accounts/password_change/secure/', user_views.password_change_controller,
name='password_change_controller'),
path('accounts/password_change/', user_views.LdapPasswordChangeView.as_view(),
name='password_change'),

View File

@ -0,0 +1,7 @@
from django.contrib.auth.models import User
def update_dajngo_user(ldap_user):
user, _ = User.objects.get_or_create(username=ldap_user.username)
user.email = ldap_user.email
user.save()

View File

@ -8,6 +8,7 @@ from django.contrib.auth.tokens import default_token_generator
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.conf import settings
from core.settings import EMAIL_HOST, EMAIL_PORT, EMAIL_USE_SSL, EMAIL_USE_TLS
@ -44,3 +45,14 @@ def send_welcome_mail(domain, email, protocol, realm, user):
# TODO failure handling
p1 = Process(target=realm_send_mail, args=(realm, user.email, mail_subject, message))
p1.start()
def send_deletion_mail(realm, user):
mail_subject = 'Aktiviere deinen StuVe Account'
message = render_to_string('registration/deletion_information_email.jinja2', {
'user': user,
'deletion_wait_days': settings.DELETION_WAIT_DAYS,
})
# TODO failure handling
p1 = Process(target=realm_send_mail, args=(realm, user.email, mail_subject, message))
p1.start()

View File

@ -0,0 +1,62 @@
from django.shortcuts import render
from account_helper.models import Realm
from account_manager.models import LdapUser, LdapGroup
from account_manager.utils.user_views import render_user_detail_view
def render_realm_detail_view(request, realm_id, success_headline=None, success_text=None, error_headline=None,
error_text=None, status_code=200):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = realm.ldap_base_dn
inactive_users = LdapUser.get_inactive_users().count()
ldap_admin_group, ldap_default_group = get_default_admin_group(realm)
return render(request, 'realm/realm_detailed.jinja2',
{'realm': realm,
'ldap_admin_group': ldap_admin_group,
'ldap_default_group': ldap_default_group,
'inactive_user_count': inactive_users,
'users_count': LdapUser.objects.all().count(),
'success_headline': success_headline,
'success_text': success_text,
'error_headline': error_headline,
'error_text': error_text}, status=status_code)
def get_default_admin_group(realm):
ldap_admin_group = None
ldap_default_group = None
if realm.admin_group:
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
ldap_admin_group = LdapGroup.objects.get(name=realm.admin_group.name)
if realm.default_group:
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
ldap_default_group = LdapGroup.objects.get(name=realm.default_group.name)
return ldap_admin_group, ldap_default_group
def render_permission_denied_view(request):
return render(request, 'permission_denied.jinja2', {}, status=403)
def get_group_user_count_wrapper(realm):
LdapUser.base_dn = f'ou=people,{realm.ldap_base_dn}'
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
return {'realm': realm, 'group_count': LdapGroup.objects.count(), 'user_count': LdapUser.objects.count()}
def get_users_home_view(request, django_user, realms):
show_user = request.GET.get('show_user', False)
if show_user or (len(realms) == 0 and not django_user.is_superuser):
LdapUser.base_dn = LdapUser.ROOT_DN
ldap_user = LdapUser.objects.get(username=django_user.username)
realm = Realm.objects.get(ldap_base_dn=ldap_user.get_users_realm_base_dn())
return render_user_detail_view(request, realm, ldap_user)
elif len(realms) == 1:
return render_realm_detail_view(request, realms[0].id)
else:
realm_wrappers = []
for realm in realms:
realm_wrappers.append(get_group_user_count_wrapper(realm))
return render(request, 'realm/realm_home.jinja2', {'realms': realms, 'realm_wrappers': realm_wrappers})

View File

@ -0,0 +1,10 @@
from django.shortcuts import render
from account_manager.models import LdapUser, LdapGroup
def render_user_detail_view(request, realm, ldap_user):
user_wrapper = LdapUser.get_extended_user(ldap_user)
LdapGroup.base_dn = LdapGroup.ROOT_DN
groups = LdapGroup.objects.filter(members=ldap_user.dn)
return render(request, 'user/user_detail.jinja2', {'user': user_wrapper, 'groups': groups, 'realm': realm})

View File

@ -1,5 +1,4 @@
import logging
import os
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordResetForm
@ -7,17 +6,26 @@ from django.contrib.auth.models import User
from django.contrib.auth.views import PasswordResetConfirmView, PasswordChangeView
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError
from django.http import HttpRequest
from django.shortcuts import render, redirect
from django.utils.translation import gettext as _
from ldap import ALREADY_EXISTS, OBJECT_CLASS_VIOLATION
from django.urls import reverse
from urllib.parse import urlencode
from account_helper.models import Realm
from account_helper.models import Realm, DeletedUser
from account_manager.forms import AddLDAPUserForm, UserDeleteListForm, UpdateLDAPUserForm, AdminUpdateLDAPUserForm, \
UserGroupListForm
UserGroupListForm, LdapPasswordChangeForm
from account_manager.main_views import is_realm_admin
from account_manager.models import LdapUser, LdapGroup
from account_manager.utils.mail_utils import send_welcome_mail
from account_manager.utils.django_user import update_dajngo_user
from account_manager.utils.mail_utils import send_welcome_mail, send_deletion_mail
from django.contrib.auth import logout
from django.conf import settings
from account_manager.utils.user_views import render_user_detail_view
logger = logging.getLogger(__name__)
@ -46,14 +54,7 @@ def realm_user(request, realm_id):
realm_users = LdapUser.objects.all()
user_wrappers = []
for user in realm_users:
try:
django_user = User.objects.get(username=user.username)
if django_user.last_login:
user_wrappers.append({'user': user, 'active': True})
else:
user_wrappers.append({'user': user, 'active': False})
except ObjectDoesNotExist:
user_wrappers.append({'user': user, 'active': False})
user_wrappers.append(LdapUser.get_extended_user(user))
return render(request, 'realm/realm_user.jinja2', {'realm': realm, 'realm_user': user_wrappers})
@ -61,24 +62,28 @@ def realm_user(request, realm_id):
@is_realm_admin
@protect_cross_realm_user_access
def realm_user_detail(request, realm_id, user_dn):
return get_rendered_user_details(request, realm_id, user_dn)
def get_rendered_user_details(request, realm_id, user_dn, success_headline=None, success_text=None):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = realm.ldap_base_dn
LdapGroup.base_dn = LdapGroup.ROOT_DN
user = LdapUser.objects.get(dn=user_dn)
user_wrapper = LdapUser.get_extended_user(user)
groups = LdapGroup.objects.filter(members=user.dn)
return render(request, 'user/realm_user_detail.jinja2', {'user': user, 'groups': groups, 'realm': realm})
return render(request, 'user/realm_user_detail.jinja2',
{'user': user_wrapper, 'groups': groups, 'realm': realm, 'success_headline': success_headline,
'success_text': success_text})
@login_required
def user_detail(request, realm_id, user_dn):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = realm.ldap_base_dn
LdapGroup.base_dn = LdapGroup.ROOT_DN
ldap_user = LdapUser.objects.get(dn=user_dn)
user = LdapUser.objects.get(dn=user_dn)
groups = LdapGroup.objects.filter(members=user.dn)
return render(request, 'user/user_detail.jinja2', {'user': user, 'groups': groups, 'realm': realm})
return render_user_detail_view(request, realm, ldap_user)
@login_required
@ -149,7 +154,7 @@ def realm_user_resend_password_reset(request, realm_id, user_dn):
ldap_user = LdapUser.objects.get(dn=user_dn)
try:
if ldap_user.email:
logger.info("Sending email for to this email:", ldap_user.email)
logger.info(f"Sending email to {ldap_user.email}")
form = PasswordResetForm({'email': ldap_user.email})
if form.is_valid():
logger.info('CREATE REQUEST')
@ -162,11 +167,11 @@ def realm_user_resend_password_reset(request, realm_id, user_dn):
form.save(
request=pw_reset_request,
use_https=True,
from_email=os.environ.get('DEFAULT_FROM_EMAIL', 'vergesslich@test.de'),
from_email=settings.DEFAULT_FROM_EMAIL,
email_template_name='registration/password_reset_email.html')
except Exception as e:
logger.info('Error')
logger.error('Error')
return redirect('realm-user-detail', realm_id, user_dn)
@ -177,13 +182,15 @@ def realm_user_resend_welcome_mail(request, realm_id, user_dn):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = f'ou=people,{realm.ldap_base_dn}'
ldap_user = LdapUser.objects.get(dn=user_dn)
update_dajngo_user(ldap_user)
current_site = get_current_site(request)
protocol = 'http'
if request.is_secure():
protocol = 'https'
send_welcome_mail(domain=current_site.domain, email=ldap_user.email, protocol=protocol, realm=realm,
user=User.objects.get(username=ldap_user.username))
return redirect('realm-user-detail', realm_id, user_dn)
return get_rendered_user_details(request, realm_id, user_dn, success_headline="Willkommensmail",
success_text="Willkommensmail erfolgreich versendet.")
@login_required
@ -220,7 +227,24 @@ def realm_user_delete_confirm(request, realm_id, user_dn):
deletion_link = {'name': 'realm-user-delete', 'args': [realm.id, ldap_user.dn]}
cancel_link = {'name': 'realm-user-detail', 'args': [realm.id, ldap_user.dn]}
return render(request, 'user/user_confirm_delete.jinja2',
{'realm': realm, 'user': ldap_user, 'deletion_link': deletion_link, 'cancel_link': cancel_link})
{'realm': realm, 'user': ldap_user, 'deletion_link': deletion_link, 'cancel_link': cancel_link,
'deletion_wait_days': settings.DELETION_WAIT_DAYS})
@login_required
@is_realm_admin
@protect_cross_realm_user_access
def realm_user_delete_cancel(request, realm_id, user_dn):
realm = Realm.objects.get(id=realm_id)
LdapUser.base_dn = f'ou=people,{realm.ldap_base_dn}'
ldap_user = LdapUser.objects.get(dn=user_dn)
try:
deleted_user = DeletedUser.objects.get(ldap_dn=ldap_user.dn)
deleted_user.delete()
except ObjectDoesNotExist as err:
pass
return redirect('realm-user-detail', realm_id, user_dn)
@login_required
@ -417,7 +441,7 @@ def realm_user_group_update_delete(request, realm_id, user_dn):
for group_name in group_names:
groups.append(LdapGroup.objects.get(name=group_name))
try:
ldap_remove_user_from_groups(user_dn, groups)
LdapGroup.remove_user_from_groups(user_dn, groups)
except OBJECT_CLASS_VIOLATION as err:
ldap_user, realm_groups_available, user_groups = get_available_given_groups(realm, user_dn)
return render(request, 'user/realm_user_update_groups.jinja2',
@ -454,45 +478,56 @@ def user_update_controller(request, realm, ldap_user, redirect_name, update_view
def user_delete_controller(ldap_user, realm):
LdapGroup.base_dn = f'ou=groups,{realm.ldap_base_dn}'
user_groups = LdapGroup.objects.filter(members__contains=ldap_user.dn)
ldap_remove_user_from_groups(ldap_user.dn, user_groups)
ldap_user.delete()
try:
django_user = User.objects.get(username=ldap_user.username)
django_user.delete()
# TODO user deletion cron
# DeletedUser.objects.create(user=django_user)
try:
DeletedUser.objects.create(user=django_user, ldap_dn=ldap_user.dn)
send_deletion_mail(realm=realm, user=ldap_user)
except IntegrityError as err:
pass
except ObjectDoesNotExist:
pass
return
def ldap_remove_user_from_groups(ldap_user, user_groups):
for group in user_groups:
group.members.remove(ldap_user)
group.save()
def ldap_add_user_to_groups(ldap_user, user_groups):
for group in user_groups:
group.members.append(ldap_user)
group.save()
@login_required
def password_change_controller(request):
logout(request)
base_url = reverse('login')
next_param = reverse('password_change')
query_string = urlencode({'next': next_param})
url = '{}?{}'.format(base_url, query_string)
return redirect(url)
class LdapPasswordResetConfirmView(PasswordResetConfirmView):
def form_valid(self, form):
user = form.save()
password = form.cleaned_data['new_password1']
LdapUser.base_dn = LdapUser.ROOT_DN
LdapUser.password_reset(user, password)
return super().form_valid(form)
cached_redirect = super().form_valid(form)
user.set_unusable_password()
user.save()
return cached_redirect
class LdapPasswordChangeView(PasswordChangeView):
form_class = LdapPasswordChangeForm
def form_valid(self, form):
user = form.save()
password = form.cleaned_data['new_password1']
LdapUser.base_dn = LdapUser.ROOT_DN
LdapUser.password_reset(user, password)
return super().form_valid(form)
cached_request = super().form_valid(form)
user.set_unusable_password()
user.save()
return cached_request

View File

@ -18,8 +18,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DOMAIN = os.environ['DOMAIN']
SITE_NAME = os.environ['SITE_NAME']
SECRET_KEY = os.environ['SECRET_KEY']
DEBUG = os.environ.get('DEBUG', 'False') =='True'
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.environ['ALLOWED_HOSTS'].split()
DELETION_WAIT_DAYS = int(os.environ.get('DELETION_WAIT_DAYS', "14"))
# Application definition
INSTALLED_APPS = [
@ -179,6 +180,8 @@ else:
EMAIL_TIMEOUT = 15
EMAIL_HOST = os.environ['EMAIL_HOST']
EMAIL_PORT = int(os.environ['EMAIL_PORT'])
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER','')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD','')
EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'False') == 'True'
EMAIL_USE_SSL = os.environ.get('EMAIL_USE_SSL', 'False') == 'True'
@ -217,10 +220,14 @@ LOGGING = {
'level': 'DEBUG',
},
'django_auth_ldap': {
'level': 'WARNING',
'level': 'DEBUG',
'handlers': ['console'],
},
'django': {
'django_ldapdb': {
'level': 'DEBUG',
'handlers': ['console'],
},
'*': {
'handlers': ['console'],
'level': 'DEBUG',
}

View File

@ -18,6 +18,7 @@ from django.urls import path, include
from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import user_passes_test
from account_manager.forms import LdapPasswordResetForm
from account_manager.views.user_views import LdapPasswordChangeView
from .views import about
login_forbidden = user_passes_test(lambda u: u.is_anonymous(), '/')
@ -31,5 +32,6 @@ urlpatterns = [
auth_views.PasswordResetView.as_view(html_email_template_name='registration/password_reset_email.html',
form_class=LdapPasswordResetForm),
name='password_reset'),
path('accounts/', include('django.contrib.auth.urls')),
]

View File

@ -215,6 +215,21 @@
color: #6c757d;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* -- Toast -- */
/* ------------------------------------------------------------------------------------------------------------------ */
.toast {
position: absolute;
top: 5px;
right: -250px;
width: 250px;
min-height: 50px;
z-index: 1000;
transform: translateX(-280px);
transition: transform 2s;
}
/* ------------------------------------------------------------------------------------------------------------------ */
/* -- Footer -- */
/* ------------------------------------------------------------------------------------------------------------------ */

View File

@ -3,6 +3,10 @@ const TABLE_CLASS = '.data-table';
const TABLE_CLASS_NO_PAGING = '.data-table-npaging';
$(document).ready(function () {
// Bottstrap Toast
$('.toast').toast('show');
//Datatables
const data_table = $(TABLE_CLASS).DataTable({
"paging": true,
"pageLength": 10,

View File

@ -65,6 +65,7 @@
<script src="{{ static('libs/DataTables/DataTables-1.10.18/js/jquery.dataTables.js') }}"></script>
<script src="{{ static('libs/DataTables/RowReorder-1.2.4/js/dataTables.rowReorder.min.js') }}"></script>
<script src="{{ static('libs/DataTables/Responsive-2.2.2/js/dataTables.responsive.min.js') }}"></script>
<script src="{{ static('libs/bootstrap-4.3.1-dist/js/bootstrap.min.js') }}"></script>
<script src="{{ static('js/main.js') }}"></script>
</body>

View File

@ -36,11 +36,12 @@
<th scope="col">Nachname</th>
<th scope="col">Aktiv</th>
<th scope="col">Letzer Login</th>
<th scope="col">Löschdatum</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<tr class="{% if user.deleted_user %}bg-warning{% endif %}">
<td>
<a href="{{ url('realm-user-detail', args=[realm.id, user.user.dn]) }}">{{ user.user.username }}</a>
</td>
@ -57,6 +58,11 @@
<i class="far fa-times-circle text-danger"></i><span class="d-none">+</span>
{% endif %}
</td>
<td class="text-center">
{% if user.deleted_user %}
{{ user.deleted_user.deletion_date.strftime('%Y-%m-%d') }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
@ -125,6 +131,29 @@
{% endif %}
{% endmacro %}
{% macro get_success_toast(success_head, success_text, error_headline, error_text) -%}
{% if success_text and success_head %}
<div class="toast" role="alert" aria-live="polite" aria-atomic="true" data-delay="5000">
<div role="alert" aria-live="assertive" aria-atomic="true">
<div role="alert" aria-live="assertive" aria-atomic="true" class="" data-autohide="false">
<div class="toast-header text-success"><strong class="mr-auto">{{ success_head }}</strong></div>
<div class="toast-body">{{ success_text }}</div>
</div>
</div>
</div>
{% endif %}
{% if error_text and error_headline %}
<div class="toast" role="alert" aria-live="polite" aria-atomic="true" data-delay="5000">
<div role="alert" aria-live="assertive" aria-atomic="true">
<div role="alert" aria-live="assertive" aria-atomic="true" class="" data-autohide="false">
<div class="toast-header text-error"><strong class="mr-auto">{{ error_headline }}</strong></div>
<div class="toast-body">{{ error_text }}</div>
</div>
</div>
</div>
{% endif %}
{% endmacro %}
{% macro get_data_table_search_field(input_id="data-table-search-input") -%}
<div class="form-group w-25 float-right">
<input type="text"

View File

@ -1,4 +1,6 @@
{% extends 'base_admin.jinja2' %}
{% import 'macros/utils_macros.jinja2' as mutils %}
{% block admin_content %}
<div class="row ">
<div class="col-12 p-3">
@ -19,13 +21,17 @@
<p style="color: darkred">{{ error }}</p>
{% endif %}
{% block detail_content %}
{{ mutils.get_success_toast(success_headline, success_text, error_headline, error_text) }}
<ul class="list-group list-group-flush w-100">
<li class="list-group-item">LDAP Organisationseinheit: {{ realm.ldap_base_dn }}</li>
<li class="list-group-item">Nutzeranzahl: {{ users_count }}</li>
<li class="list-group-item">Inaktive Nutzer: {{ inactive_user_count }} <a
href="{{ url('realm-multiple-user-delete-inactive', args=[realm.id]) }}"
class="float-right">
Inaktive Nutzer löschen</a></li>
<li class="list-group-item">Nutzeranzahl (Aktive/Inaktive): {{ users_count }}
({{ users_count-inactive_user_count }}/{{ inactive_user_count }})
<a
href="{{ url('realm-multiple-user-delete-inactive', args=[realm.id]) }}"
class="float-right">
Inaktive Nutzer löschen
</a>
</li>
{% if realm.email %}
<li class="list-group-item">Email: {{ realm.email }}</li>
{% else %}

View File

@ -0,0 +1,9 @@
<h1>StuVe Accountlöschung</h1>
<p>Dein Account mit dem Nutzernamen <strong>{{ user.username }}</strong> wurde als gelöscht markiert. Deine Nutzerdaten
werden in {{ deletion_wait_days }} Tagen gelöscht. Falls du noch wichtige Daten in den StuVe Services gespeichert
hast, bitte kopiere diese noch vor der Löschung. Danach werden diese nicht mehr zugänglich sein. </p>
<p>Möchtest du weiter Teil der StuVe Services sein, bitte kontaktiere zuvor deinen Administrator.</p>
<p>Wir wünschen dir noch einen schöne Studienzeit</p>
<p>Das Fachschaft WIAI Admin Team</p>
Kontakt: <a href="mailto:fachschaft-wiai.stuve@uni-bamberg.de?subject=LAMa (Dein Betreff)">fachschaft-wiai.stuve@uni-bamberg.de</a>

View File

@ -1,22 +1,33 @@
{% extends 'base.jinja2' %}
{% import 'macros/form_macros.jinja2' as mform %}
{% block content %}
<div class="col-12 ">
<div class="row justify-content-center justify-content-sm-center">
<div class="col-12 col-sm-8 col-md-7 col-lg-5 col-xl-4 bg-white text-dark p-3 mt-5 border">
<h1 class="mb-4">Passwort ändern</h1>
<form method="post" class="floating-label-form">
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
{{ mform.password_input(form.old_password) }}
{{ mform.password_input(form.new_password1) }}
{{ mform.password_input(form.new_password2) }}
<div class="d-flex mt-4">
<button type="submit" class="btn btn-primary mr-auto p-2">Speichern</button>
<a href="{{ url('realm-home')}}"
class="btn btn-secondary p-2">Abbrechen</a>
</div>
</form>
</div>
<div class="col-12 ">
<div class="row justify-content-center justify-content-sm-center">
<div class="col-12 col-sm-8 col-md-7 col-lg-5 col-xl-4 bg-white text-dark p-3 mt-5 border">
<h1 class="mb-4">Passwort ändern</h1>
<form method="post" class="floating-label-form">
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
<!-- {{form.errors}}-->
<input type="password"
class="form-control"
placeholder="Old password"
aria-describedby="id_old_password_help"
name="old_password"
id="id_old_password"
maxlength="None"
value="ralf"
hidden>
<!-- {{ mform.password_input(form.old_password) }}-->
{{ mform.password_input(form.new_password1) }}
{{ mform.password_input(form.new_password2) }}
<div class="d-flex mt-4">
<button type="submit" class="btn btn-primary mr-auto p-2">Speichern</button>
<a href="{{ url('realm-home')}}"
class="btn btn-secondary p-2">Abbrechen</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,26 +1,49 @@
{% extends 'realm/realm_detailed.jinja2' %}
{% import 'macros/form_macros.jinja2' as mform %}
{% import 'macros/utils_macros.jinja2' as mutils %}
{% block detail_content %}
<h3>{{ user.username }}</h3>
{{ mutils.get_success_toast(success_headline, success_text) }}
{% if user.user %}
{% if user.deleted_user.deletion_date %}
<h3 class="text-danger">{{ user.user.username }}
<small>Nutzer wird vorraussichtlich am {{ user.deleted_user.deletion_date.strftime('%d.%m.%Y') }}
gelöscht
</small>
</h3>
{% else %}
<h3>{{ user.user.username }}</h3>
{% endif %}
{% else %}
<h3>{{ user.username }}</h3>
{% endif %}
{% if not form %}
<ul class="list-group list-group-flush w-100">
<li class="list-group-item">Ldap Domain: {{ user.dn }}</li>
<li class="list-group-item">Ldap Domain: {{ user.user.dn }}</li>
<li class="list-group-item"> Anzeigename:
{% if user.display_name %}
{{ user.display_name }}
{% if user.user.display_name %}
{{ user.user.display_name }}
{% else %}
<span class="text-warning"> Noch nicht generiert </span>
{% endif %}
</li>
<li class="list-group-item">Vorname: {{ user.first_name }}</li>
<li class="list-group-item">Nachname: {{ user.last_name }}</li>
<li class="list-group-item">Email: {{ user.email }}</li>
{% if user.user.phone %}
<li class="list-group-item">Vorname: {{ user.user.first_name }}</li>
{% endif %}
{% if user.user.phone %}
<li class="list-group-item">Nachname: {{ user.user.last_name }}</li>
{% endif %}
<li class="list-group-item">Email: {{ user.user.email }}</li>
<li class="list-group-item">Passwort: <a
href="{{ url('realm-user-password-reset', args = [realm.id, user.dn]) }}" class="float-right">Nutzerpasswort
href="{{ url('realm-user-password-reset', args = [realm.id, user.user.dn]) }}" class="float-right">Nutzerpasswort
zurücksetzen</a></li>
<li class="list-group-item">Telefon: {{ user.phone }}</li>
<li class="list-group-item">Mobiltelefon: {{ user.mobile_phone }}</li>
{% if user.user.phone %}
<li class="list-group-item">Telefon: {{ user.user.phone }}</li>
{% endif %}
{% if user.user.mobile_phone %}
<li class="list-group-item">Mobiltelefon: {{ user.user.mobile_phone }}</li>
{% endif %}
<li class="list-group-item">Gruppen:
{% if groups %}
{% for group in groups %}
@ -33,29 +56,45 @@
{% else %}
<span class="text-warning">Keine zugewiesen</span>
{% endif %}
<a href="{{ url('realm-user-group-update', args=[realm.id, user.dn]) }}" class="float-right">
Gruppen zuweisen</a>
{% if not user.deleted_user.deletion_date %}
<a href="{{ url('realm-user-group-update', args=[realm.id, user.user.dn]) }}" class="float-right">
Gruppen zuweisen</a>
{% endif %}
</li>
<li class="list-group-item">Zuletzt eingeloggt:
{% if user.last_login %}
{{ user.last_login.strftime('%d.%m.%Y') }}
{% if user.user.last_login %}
{{ user.user.last_login.strftime('%d.%m.%Y') }}
{% else %}
<i class="far fa-times-circle text-danger"></i><span class="d-none">+</span>
{% endif %}</li>
{% if user.deleted_user.deletion_date %}
<li class="list-group-item text-danger">
Löschvorgang: {{ user.deleted_user.deletion_date.strftime('%d.%m.%Y') }}
<a href="{{ url('realm-user-delete-cancel', args=[realm.id, user.user.dn]) }}"
class="float-right">
Löschvorgang abbrechen</a>
</li>
{% endif %}
</ul>
<div class="d-flex mt-3">
<a href="{{ url('realm-user-update', args = [realm.id, user.dn]) }}" class="btn btn-primary mr-auto p-2">
<i class="fas fa-user-cog"></i> Nutzer bearbeiten
</a>
{% if not user.last_login %}
<a href="{{ url('realm-user-resend-welcome-mail', args = [realm.id, user.dn]) }}"
class="btn btn-secondary p-2 mr-2">
<i class="fas fa-paper-plane"></i> Wilkommensmail erneut senden
{% if not user.deleted_user.deletion_date %}
<a href="{{ url('realm-user-update', args = [realm.id, user.user.dn]) }}"
class="btn btn-primary mr-auto p-2">
<i class="fas fa-user-cog"></i> Nutzer bearbeiten
</a>
{% if not user.user.last_login %}
<a href="{{ url('realm-user-resend-welcome-mail', args = [realm.id, user.user.dn]) }}"
class="btn btn-secondary p-2 mr-2">
<i class="fas fa-paper-plane"></i> Wilkommensmail erneut senden
</a>
{% endif %}
<a href="{{ url('realm-user-delete-confirm', args = [realm.id, user.user.dn]) }}"
class="btn btn-danger p-2">
<i class="fas fa-trash"></i> Nutzer löschen
</a>
{% endif %}
<a href="{{ url('realm-user-delete-confirm', args = [realm.id, user.dn]) }}" class="btn btn-danger p-2">
<i class="fas fa-trash"></i> Nutzer löschen
</a>
</div>
{% else %}
<form method="post">

View File

@ -7,7 +7,7 @@
<div class="row justify-content-center justify-content-sm-center">
<div class="col-12 col-sm-8 col-md-7 col-lg-5 col-xl-4 bg-white text-dark p-3 mt-5">
<div class="alert alert-warning" role="alert">
<p>Achtung! Sie sind gerade dabei den Account von <strong>{{ user.username }}</strong> zu schließen.
<p><strong>Achtung!</strong> Sie sind gerade dabei den Account von <strong>{{ user.username }}</strong> zu schließen.
</p>
<p>Falls Sie sich sicher sind, dass Sie diesen Nutzer löschen wollen, klicken Sie bitte auf "Nutzer
löschen".
@ -15,6 +15,9 @@
Diensten hochgeladen wurden, weiterhin bestehen bleiben.</p>
<p>Um auch diese zu löschen müssen Sie zuvor Ihre Daten entsprechend löschen. </p>
<p>Möchten Sie das Löschen der Accountdaten verhindern, klicken Sie auf "Abbrechen"</p>
<p>Der Nutzer wird automatisch über die Löschung informiert.</p>
<p><strong>Der Account bleibt noch {{ deletion_wait_days }} Tage (vom heutigen Tag aus) bis zur geplanten Löschung
bestehen.</strong></p>
</div>
<div class="d-flex">
<a href="{{ url(cancel_link.name, args = cancel_link.args) }}"

View File

@ -8,8 +8,14 @@
<div class="col-12 col-sm-8 col-md-7 col-lg-5 col-xl-4 bg-white text-dark p-3 mt-5">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ user.username }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ user.last_name }}, {{ user.first_name }}</h6>
{% if user.deleted_user.deletion_date %}
<h5 class="card-title text-danger">{{ user.user.username }}
<small>Als gelöscht markiert</small>
</h5>
{% else %}
<h5 class="card-title">{{ user.user.username }}</h5>
{% endif %}
<h6 class="card-subtitle mb-2 text-muted">{{ user.user.last_name }}, {{ user.user.first_name }}</h6>
<ul class="list-group list-group-flush">
{% if groups %}
<li class="list-group-item">
@ -23,21 +29,26 @@
</li>
{% endif %}
<li class="list-group-item"><span
class="font-weight-bold">Email:</span> {{ user.email }}</li>
class="font-weight-bold">Email:</span> {{ user.user.email }}</li>
<li class="list-group-item"><span
class="font-weight-bold">Passwort:</span> <a
href="{{ url('password_change') }}">Passwort ändern</a>
href="{{ url('password_change_controller') }}">Passwort ändern</a>
</li>
<li class="list-group-item"><span
class="font-weight-bold">Telefon:</span> {{ user.phone }}</li>
class="font-weight-bold">Telefon:</span> {{ user.user.phone }}</li>
<li class="list-group-item"><span
class="font-weight-bold">Mobiltelefon:</span> {{ user.mobile_phone }}</li>
class="font-weight-bold">Mobiltelefon:</span> {{ user.user.mobile_phone }}</li>
{% if user.deleted_user.deletion_date %}
<li class="list-group-item text-danger">
Löschvorgang: {{ user.deleted_user.deletion_date.strftime('%d.%m.%Y') }}
</li>
{% endif %}
</ul>
<div class="card-footer d-flex bg-white">
<a href="{{ url('user-update', args = [realm.id, user.dn]) }}"
<a href="{{ url('user-update', args = [realm.id, user.user.dn]) }}"
class="btn btn-primary mr-auto p-2"><i class="fas fa-user-edit"></i> Profil
bearbeiten</a>
<a href="{{ url('user-delete-confirm', args = [realm.id, user.dn]) }}"
<a href="{{ url('user-delete-confirm', args = [realm.id, user.user.dn]) }}"
class="btn btn-danger p-2"><i class="fas fa-trash"></i> Profil löschen</a>
</div>
</div>