diff --git a/digitaleagentur/__pycache__/settings.cpython-38.pyc b/digitaleagentur/__pycache__/settings.cpython-38.pyc index 48a02aa..b975009 100644 Binary files a/digitaleagentur/__pycache__/settings.cpython-38.pyc and b/digitaleagentur/__pycache__/settings.cpython-38.pyc differ diff --git a/digitaleagentur/settings.py b/digitaleagentur/settings.py index 22a4d84..a8ef592 100644 --- a/digitaleagentur/settings.py +++ b/digitaleagentur/settings.py @@ -156,9 +156,19 @@ AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, + #{ + # 'NAME' : "passwords.auth_password_validators.ComplexityValidator", + #} ] - +PASSWORD_COMPLEXITY = { # You can omit any or all of these for no limit for that particular set + "UPPER": 1, # Uppercase + "LOWER": 1, # Lowercase + "LETTERS": 1, # Either uppercase or lowercase letters + "DIGITS": 1, # Digits + "SPECIAL": 1, # Not alphanumeric, space or punctuation character + "WORDS": 0 # Words (alphanumeric sequences separated by a whitespace or punctuation character) +} # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ diff --git a/recoverdir/forms.py b/recoverdir/forms.py index f9ec44e..61f5681 100644 --- a/recoverdir/forms.py +++ b/recoverdir/forms.py @@ -2,6 +2,9 @@ from django import forms from django.forms import ModelForm from .models import * from bootstrap_datepicker_plus import DatePickerInput +from django.contrib.auth.password_validation import validate_password +from django.core import validators +from passwords.validators import * class PersLetterForm(forms.ModelForm): @@ -18,6 +21,22 @@ class PersLetterForm(forms.ModelForm): super(PersLetterForm, self).__init__(*args, **kwargs) self.fields['text'] = forms.CharField(label="Ihr persönlicher Text", widget=forms.Textarea(attrs={"rows":15, "cols":35})) +class LoginRDForm(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['pass'] = forms.CharField(widget=forms.PasswordInput, label="Passwort", required=True) + +class RecoverDirSettingForm(forms.ModelForm): + + class Meta: + model = RecoverDirSetting + fields = ['logpass'] + + def __init__(self, *args, **kwargs): + super(RecoverDirSettingForm, self).__init__(*args, **kwargs) + self.fields['logpass'] = forms.CharField(widget=forms.PasswordInput, label="Passwort", required=True, validators=[dictionary_words, complexity, validate_length]) + self.fields['logpass_check'] = forms.CharField(widget=forms.PasswordInput, label="Passwort wiederholen", required=True,validators=[dictionary_words, complexity, validate_length]) + # Notfallhilfe FORMS # 1 Handlungsleitfaden diff --git a/recoverdir/models.py b/recoverdir/models.py index 9ce0dcc..814e7ea 100644 --- a/recoverdir/models.py +++ b/recoverdir/models.py @@ -18,6 +18,12 @@ def rd_path_agency(instance, filename): return 'agencydata/agency_{0}/rd/{1}'.format(instance.agency.pk, filename) +# MAIN RECOVERDIR PASSWORD AND CONFIG +class RecoverDirSetting(models.Model): + agency = models.ForeignKey(Agency, on_delete=models.CASCADE) + logpass = encrypt(models.CharField(max_length=500, blank=True, default="", null=True)) + recoverkey = encrypt(models.CharField(max_length=500, blank=True, default="", null=True)) + # Create your models here. class PersLetter(models.Model): diff --git a/recoverdir/templates/recoverdir/rd_areas/rd_area_5.html b/recoverdir/templates/recoverdir/rd_areas/rd_area_5.html index 77f32bb..3de1714 100644 --- a/recoverdir/templates/recoverdir/rd_areas/rd_area_5.html +++ b/recoverdir/templates/recoverdir/rd_areas/rd_area_5.html @@ -1,7 +1,6 @@

Personal, interne und externe Partner inkl. Funktionen usw.  Personal

-Beim externen Aufrufen der Notfallhilfe werden alle aktuell angelegten Mitarbeiter inkl. Kontaktdaten angezeigt. Erstellen Sie daher hier Mitarbeiter, die nicht in der digitalen Agentur angelegt sind.
@@ -29,6 +28,17 @@ {% endfor %} + {% for us in users_of_agency %} + + + + + + + {% endfor %}
{{us.get_full_name}}{{us.profile.func|default:""}} + Intern + +

@@ -101,7 +111,8 @@ $(document).ready(function(){ "pageLength": 50, "buttons" : { "className" : "btn-danger" - } + }, + "order": [[ 1, "desc" ]] }); }); diff --git a/recoverdir/templates/recoverdir/rd_elements_forms/rd_mainlogin.html b/recoverdir/templates/recoverdir/rd_elements_forms/rd_mainlogin.html new file mode 100644 index 0000000..d1bac55 --- /dev/null +++ b/recoverdir/templates/recoverdir/rd_elements_forms/rd_mainlogin.html @@ -0,0 +1,26 @@ +{% extends "users/base.html" %} +{% load counter_tag %} +{% load crispy_forms_tags %} +{% block content %} +{% if request.user.profile.agency.module_recoverdir %} +
+

Notfallhilfe{% if request.user.profile.showtooltips %} {% endif %} + +

+
+
+ Bitte melden Sie sich mit dem Passwort für die Notfallhilfe an! +
+ {% csrf_token %} + {{form.media}} + {{form|crispy}} +
+   +
+
+
+ +{% else %} +Sie haben keinen Zugriff auf das Modul Notfallhilfe. Bitte wenden Sie sich an den Support! +{% endif %} +{% endblock content %} diff --git a/recoverdir/templates/recoverdir/rd_management.html b/recoverdir/templates/recoverdir/rd_management.html index 6bdcfa4..98e60c9 100644 --- a/recoverdir/templates/recoverdir/rd_management.html +++ b/recoverdir/templates/recoverdir/rd_management.html @@ -5,6 +5,7 @@

Notfallhilfe{% if request.user.profile.showtooltips %} {% endif %} +


@@ -63,7 +64,10 @@
- Einstellungen +
Einstellungen{% if request.user.profile.showtooltips %} {% endif %}
+ {% block settings_content %} + {% include "recoverdir/rd_viewsettings.html" %} + {% endblock %}
diff --git a/recoverdir/templates/recoverdir/rd_rd.html b/recoverdir/templates/recoverdir/rd_rd.html index c4259a4..9dc0398 100644 --- a/recoverdir/templates/recoverdir/rd_rd.html +++ b/recoverdir/templates/recoverdir/rd_rd.html @@ -125,7 +125,7 @@

#5

@@ -147,7 +147,7 @@

#6

@@ -169,7 +169,7 @@

#7

@@ -191,7 +191,7 @@

#8

diff --git a/recoverdir/templates/recoverdir/rd_settings.html b/recoverdir/templates/recoverdir/rd_settings.html new file mode 100644 index 0000000..6762b31 --- /dev/null +++ b/recoverdir/templates/recoverdir/rd_settings.html @@ -0,0 +1,21 @@ +{% extends "users/base.html" %} +{% load crispy_forms_tags %} +{% block content %} +{% if request.user.profile.agency.module_recoverdir %} +
+

Einstellungen verwalten

+
+
+ Beachten Sie bitte, dass dieses Passwort allen Personen sicher zugänglich gemacht werden muss, die auf die Notfallhilfe zugreifen können! Zusätzlich benötigen diese Personen den Sicherheitssschlüssel. Dieser wird automatisch generiert und kann nicht geändert werden. Sie finden den Sicherheitsschlüssel unter Notfallhilfe, Einstellungen. + {% csrf_token %} + {{normalForm.media}} + {{ form|crispy }} +
+ Abbrechen +   +
+
+{% else %} +

Das Modul Notfallhilfe wurden in ihrer Agentur deaktiviert.

+{% endif %} +{% endblock content %} diff --git a/recoverdir/templates/recoverdir/rd_updates.html b/recoverdir/templates/recoverdir/rd_updates.html index 6663774..ca5d678 100644 --- a/recoverdir/templates/recoverdir/rd_updates.html +++ b/recoverdir/templates/recoverdir/rd_updates.html @@ -61,6 +61,9 @@ {% elif hisotryelementinfo.1 == 11 %} {{hisotryelementinfo.0}} + {% elif hisotryelementinfo.1 == 20 %} + {{hisotryelementinfo.0}} + {% endif %} {{rdele.history_date|date:"d.m.Y H:i"}} diff --git a/recoverdir/templates/recoverdir/rd_viewsettings.html b/recoverdir/templates/recoverdir/rd_viewsettings.html new file mode 100644 index 0000000..2d6897b --- /dev/null +++ b/recoverdir/templates/recoverdir/rd_viewsettings.html @@ -0,0 +1,20 @@ +
+ + + + + + + + + +
Sicherheitsschlüssel (nicht veränderbar):{{rd_settings.recoverkey}}
Passwort für die Notfallhilfe:*************** +   +   +
+ \ No newline at end of file diff --git a/recoverdir/urls.py b/recoverdir/urls.py index 3cfe267..e447d1a 100644 --- a/recoverdir/urls.py +++ b/recoverdir/urls.py @@ -12,6 +12,13 @@ Permissions definiert in models.py bei USERS und dann hier vor die View geschrie urlpatterns = [ path('', permission_required('users.recoverdirmanager')(RecoverDirManagement.as_view(template_name="recoverdir/rd_management.html")), name='recoverdir'), + + # SETTINGS + path('rdsettings/', permission_required('users.recoverdirmanager')(RecoverDirAddSettings.as_view()), name='recoverdir-addsettings'), + path('rdsettings/update/', permission_required('users.recoverdirmanager')(RecoverDirUpdateSettings.as_view()), name='recoverdir-updatesettings'), + path('rdlogin/', permission_required('users.recoverdirmanager')(RecoverDirLog.as_view()), name='recoverdir-login'), + path('close/', permission_required('users.recoverdirmanager')(CloseRecoverDir), name='closerecoverdir'), + # Persönliches Schreiben path('addpl/', permission_required('users.recoverdirmanager')(RecoverDirAddPL.as_view(template_name="recoverdir/rd_pers_add.html")), name='recoverdir-addpl'), path('updatepl/', permission_required('users.recoverdirmanager')(RecoverDirUpdatePL.as_view(template_name="recoverdir/rd_pers_update.html")), name='recoverdir-updatepl'), diff --git a/recoverdir/views.py b/recoverdir/views.py index 55aff0d..f8a8dfe 100644 --- a/recoverdir/views.py +++ b/recoverdir/views.py @@ -1,9 +1,11 @@ from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin -from django.views.generic import CreateView, ListView, UpdateView, DetailView, DeleteView +from django.views.generic import CreateView, ListView, UpdateView, DetailView, DeleteView, FormView from .models import * from .forms import * from django.urls import reverse_lazy +from django.shortcuts import redirect +from django.contrib import messages from django.contrib.auth.decorators import login_required import os import sys @@ -12,11 +14,27 @@ from django.http import JsonResponse, HttpResponse, Http404 from django_encrypted_filefield.views import FetchView import string, random from itertools import chain +from django.contrib.auth.password_validation import * +from datetime import datetime +from django.contrib.auth.models import User # Create your views here. class RecoverDirManagement(LoginRequiredMixin, ListView): model = PersLetter + # First Method! + def dispatch(self, request, *args, **kwargs): + # Check, if initial config exist and user has rights + if(len(RecoverDirSetting.objects.filter(agency=self.request.user.profile.agency)) == 0 and self.request.user.has_perm('users.recoverdirmanager')): + # Settings not found + return redirect('recoverdir-addsettings') + # TASK: Hier noch einstellen, dass der Nutzer weniger als 30 Minuten nicht inaktiv war! + elif(self.request.user.profile.rd_login == None and self.request.user.has_perm('users.recoverdirmanager')): + return redirect('recoverdir-login') + else: + return super(RecoverDirManagement, self).get(request) + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update({'active_link' : 'recoverdir'}) @@ -60,7 +78,9 @@ class RecoverDirManagement(LoginRequiredMixin, ListView): finalupdatelist = chain(persletters, handlungsleitfaden, contactfc, contactstrust, handlungsleitfadenvf, depistvollmacht, ergodigi, onlinebank, streamingabo, digitalaccount, personal, onlinebank6, elseele) context.update({"history" : finalupdatelist}) - + + users_of_agency = User.objects.filter(profile__agency=self.request.user.profile.agency) + context.update({"users_of_agency" : users_of_agency}) # DOCUMENTS NOT WORKING Weil das "alte" nicht gespeichert wird sondern lediglich der Datensatz #documents = Documents.objects.filter(agency=self.request.user.profile.agency, area=1) @@ -117,8 +137,93 @@ class RecoverDirManagement(LoginRequiredMixin, ListView): # A9 context.update({'area_9_doc' : Documents.objects.filter(agency=self.request.user.profile.agency, area=9).order_by('-document_date')}) + + # Load Settings + context.update({'rd_settings' : RecoverDirSetting.objects.filter(agency=self.request.user.profile.agency)[0]}) + return context +def CloseRecoverDir(request): + request.user.profile.rd_login = None + request.user.profile.save() + return redirect('recoverdir') + + + +class RecoverDirLog(FormView): + template_name = "recoverdir/rd_elements_forms/rd_mainlogin.html" + form_class = LoginRDForm + success_url = reverse_lazy('recoverdir') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'active_link' : 'recoverdir', + 'form' : LoginRDForm() + }) + return context + + def form_valid(self, form): + # Load Settings-Data + settings = RecoverDirSetting.objects.filter(agency=self.request.user.profile.agency)[0] + + if form.cleaned_data.get("pass") == settings.logpass and self.request.user.has_perm("users.recoverdirmanager") and settings.agency == self.request.user.profile.agency: + self.request.user.profile.rd_login = datetime.now() + self.request.user.profile.save() + else: + messages.warning(self.request, f'Passwort nicht korrekt!') + return super().form_valid(form) + +class RecoverDirAddSettings(CreateView): + model = RecoverDirSetting + success_url = reverse_lazy('recoverdir') + form_class = RecoverDirSettingForm + template_name = "recoverdir/rd_settings.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'active_link' : 'recoverdir', + }) + return context + + def form_valid(self, form): + form.instance.agency = self.request.user.profile.agency + # Initial Recover-Key + if(len(RecoverDirSetting.objects.filter(agency=self.request.user.profile.agency)) == 0 and self.request.user.has_perm('users.recoverdirmanager')): + form.instance.recoverkey = randomStringRecoverKey() + + if(form.cleaned_data.get('logpass') == form.cleaned_data.get('logpass_check')): + messages.warning(self.request, f'Daten gespeichert!') + else: + messages.warning(self.request, f'Die Passwörter stimmen nicht überein. Bitte neu eingeben!') + return redirect('recoverdir-addsettings') + + return super().form_valid(form) + +class RecoverDirUpdateSettings(UpdateView): + model = RecoverDirSetting + success_url = reverse_lazy('recoverdir') + form_class = RecoverDirSettingForm + template_name = "recoverdir/rd_settings.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'active_link' : 'recoverdir', + }) + return context + + def form_valid(self, form): + # Initial Recover-Key + if(form.cleaned_data.get('logpass') == form.cleaned_data.get('logpass_check')): + messages.warning(self.request, f'Daten gespeichert!') + else: + messages.warning(self.request, f'Die Passwörter stimmen nicht überein. Bitte neu eingeben!') + return redirect('recoverdir-addsettings') + return super().form_valid(form) + + class RecoverDirAddPL(CreateView): model = PersLetter success_url = reverse_lazy('recoverdir') @@ -160,6 +265,18 @@ def randomString(stringLength=40): letters = string.ascii_lowercase return ''.join(random.choice(letters) for i in range(stringLength)) +def randomStringRecoverKey(): + letters = string.ascii_uppercase + + key = "" + + for run in range(10): + key += ''.join(random.choice(letters) for i in range(5)) + if(run < 9): + key += "-" + + return key + # Notfallhilfe ELEMENTE # ABNSCHNITT 1 @@ -1070,7 +1187,7 @@ class ElseSingleHistory(DetailView): shown_element = he context.update({ - 'ele' : shown_element, + 'else' : shown_element, 'history' : True }) return context diff --git a/requirements.txt b/requirements.txt index 810c5d6..359d481 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,3 +31,4 @@ python-dateutil==2.8.1 django-simple-history==2.11.0 django-encrypted-filefield==0.2.2 more-itertools==8.5.0 +django-passwords==0.3.12 \ No newline at end of file diff --git a/standards/templatetags/__pycache__/counter_tag.cpython-38.pyc b/standards/templatetags/__pycache__/counter_tag.cpython-38.pyc index b625b13..ff7ff43 100644 Binary files a/standards/templatetags/__pycache__/counter_tag.cpython-38.pyc and b/standards/templatetags/__pycache__/counter_tag.cpython-38.pyc differ diff --git a/standards/templatetags/counter_tag.py b/standards/templatetags/counter_tag.py index f743cc2..32ab410 100644 --- a/standards/templatetags/counter_tag.py +++ b/standards/templatetags/counter_tag.py @@ -799,7 +799,7 @@ def getHistoryClassOfObject(value): finalclass[0] = "Personal und Partner" finalclass[1] = 11 # ELSE - elif(objectClass == 'HistoricalElse'): + elif(objectClass == 'HistoricalRDElse'): finalclass[0] = "Sonstiges" finalclass[1] = 20 diff --git a/users/admin.py b/users/admin.py index cd584c2..9c80203 100644 --- a/users/admin.py +++ b/users/admin.py @@ -8,7 +8,7 @@ from cloud.models import DataFile from organizer.models import AGContacts from timemanagement.models import Workday, Breaks, AbsenceReason, FreeDays, Absence from chat.models import ChatRoom -from recoverdir.models import PersLetter, Documents, Handlungsleitfaden, RDContact +from recoverdir.models import PersLetter, Documents, Handlungsleitfaden, RDContact, RecoverDirSetting from simple_history.admin import SimpleHistoryAdmin @@ -39,4 +39,5 @@ admin.site.register(AgencyBills) admin.site.register(PersLetter, SimpleHistoryAdmin) admin.site.register(Documents, SimpleHistoryAdmin) admin.site.register(Handlungsleitfaden) -admin.site.register(RDContact) \ No newline at end of file +admin.site.register(RDContact) +admin.site.register(RecoverDirSetting) \ No newline at end of file diff --git a/users/models.py b/users/models.py index 1665607..0167946 100644 --- a/users/models.py +++ b/users/models.py @@ -262,6 +262,9 @@ class Profile(models.Model): ''' onlinestatus = models.IntegerField(default=0) + # RECOVER DIR LAST LOGIN FIELD - AFTER LOGIN SET TO NONE + rd_login = models.DateTimeField(null=True, blank=True, default=None) + def __str__(self): return f'{self.user.last_name}' diff --git a/users/signals.py b/users/signals.py index 81d8312..904b8dd 100644 --- a/users/signals.py +++ b/users/signals.py @@ -59,12 +59,14 @@ def loadingFreeDays(plz, year): @receiver(signal=user_logged_out, sender=User) def checkForFreeDays(sender, user, request, **kwargs): user.profile.onlinestatus = 3 + user.profile.rd_login = None user.save() # CHECK SOMETHING WHEN USER LOGGED IN @receiver(signal=user_logged_in, sender=User) def checkDefaultAbsenceReasons(sender, user, request, **kwargs): user.profile.onlinestatus = 0 + user.profile.rd_login = None user.save() ar = AbsenceReason.objects.filter(agency=user.profile.agency) if(len(ar) == 0): diff --git a/users/views.py b/users/views.py index bebd6bd..1f8828e 100644 --- a/users/views.py +++ b/users/views.py @@ -47,7 +47,7 @@ import filetype from django.db.models.signals import m2m_changed from django.contrib.auth.models import User, Group from users.signals import adjust_group_notifications_permission - +from django.core.exceptions import ObjectDoesNotExist def randomString(stringLength=10): """Generate a random string of fixed length """ @@ -1079,16 +1079,22 @@ def cronactionsdaily(request, code): today = date.today() for user in allusers: # REST URLAUB BERECHNUNG - try: + try: usertimedata = UserTime.objects.get(user=user) day_tocheck = usertimedata.loose_holidedate.split(".")[0] month_tocheck = usertimedata.loose_holidedate.split(".")[1] month = today.month + day = today.day if month < 10: month = "0" + str(month) day = today.day + else: + month = month + if day < 10: day = "0" + str(day) + else: + day = day # Restetag erreicht, Reste ins nächste Jahr übertragen if(str(day_tocheck) == str(day) and str(month_tocheck) == str(month)): sourceyear = today.year @@ -1144,10 +1150,10 @@ def cronactionsdaily(request, code): channel_layer = channels.layers.get_channel_layer() async_to_sync(channel_layer.group_send)("user_" + str(user.pk), {'type' : 'pushhandler', 'pushtext' : "pushnotification__Abwesenheit | In einer Woche startet Ihre Vertretung für " + r.user.first_name + " " + r.user.last_name + "!"}) - data.update({"status" : "ok"}) - except: - pass - + + data.update({"status " + str(user.pk) : "ok"}) + except ObjectDoesNotExist: + data.update({"status" + str(user.pk) : "no usertime found for " + user.get_full_name()}) else: print("API CODE FAILED") data.update({"status" : "failed"})