summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2023-04-04 12:19:52 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2023-04-04 12:19:52 +0200
commitef2b079d276a2e485383ce0e1a187d882ae5c1dc (patch)
tree86520f0d7c926f256aab6504900c6256409f9f5f /ishtar_common
parent0dac9c341be56b382fb6d5b918997aa2f41995ad (diff)
downloadIshtar-ef2b079d276a2e485383ce0e1a187d882ae5c1dc.tar.bz2
Ishtar-ef2b079d276a2e485383ce0e1a187d882ae5c1dc.zip
Manage expiration of passwords
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/context_processors.py36
-rw-r--r--ishtar_common/migrations/0227_auto_20230404_1112.py29
-rw-r--r--ishtar_common/models.py5
-rw-r--r--ishtar_common/templates/base.html2
-rw-r--r--ishtar_common/urls_registration.py6
-rw-r--r--ishtar_common/views.py42
-rw-r--r--ishtar_common/wizards.py1
7 files changed, 109 insertions, 12 deletions
diff --git a/ishtar_common/context_processors.py b/ishtar_common/context_processors.py
index e1754e935..c1d1224ea 100644
--- a/ishtar_common/context_processors.py
+++ b/ishtar_common/context_processors.py
@@ -17,8 +17,13 @@
# See the file COPYING for details.
+import datetime
+
from django.conf import settings
+from django.core.cache import cache
from django.contrib.sites.models import Site
+from django.urls import reverse
+from django.utils.translation import ugettext_lazy as _
from ishtar_common.version import __version__
from ishtar_common.models import get_current_profile
@@ -41,13 +46,6 @@ def get_base_context(request):
except Site.DoesNotExist:
dct["APP_NAME"] = settings.APP_NAME
dct["COUNTRY"] = settings.COUNTRY
- """
- if 'MENU' not in request.session or \
- request.session['MENU'].user != request.user:
- menu = Menu(request.user)
- menu.init()
- request.session['MENU'] = menu
- """ # menu is now in cache - put it back in session later?
current_action = None
if "CURRENT_ACTION" in request.session:
@@ -71,6 +69,30 @@ def get_base_context(request):
menu.init()
if hasattr(request.user, "ishtaruser") and request.user.ishtaruser:
+
+ # check password expiration date
+ if settings.ISHTAR_PASSWORD_EXPIRATION_DAYS and \
+ isinstance(settings.ISHTAR_PASSWORD_EXPIRATION_DAYS, int):
+ key = f"{settings.PROJECT_SLUG}-password_expired-{request.user.pk}"
+ password_expired = cache.get(key)
+ if password_expired is None:
+ password_expired = True
+ d = datetime.date.today() - request.user.ishtaruser.password_last_update
+ if d.days < settings.ISHTAR_PASSWORD_EXPIRATION_DAYS:
+ password_expired = False
+ cache.set(key, password_expired, settings.CACHE_TIMEOUT)
+ if password_expired and not request.path.endswith("password_change/"):
+ msg = str(_("Your password has expired. Please update it using this "
+ "form."))
+ msg = msg.replace(
+ str(_("form")),
+ f'<a href="{reverse("password_change")}">'
+ f'<i class="fa fa-external-link" aria-hidden="true"></i> '
+ f'{_("form")}</a>'
+ )
+ dct["MESSAGES"].append((msg, "warning"))
+
+ # external sources
if (
request.user.ishtaruser.current_profile
and "EXTERNAL_SOURCES" not in request.session
diff --git a/ishtar_common/migrations/0227_auto_20230404_1112.py b/ishtar_common/migrations/0227_auto_20230404_1112.py
new file mode 100644
index 000000000..1ba74f292
--- /dev/null
+++ b/ishtar_common/migrations/0227_auto_20230404_1112.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.24 on 2023-04-04 11:12
+
+import datetime
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ishtar_common', '0226_auto_20230316_1115'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='ishtaruser',
+ name='password_last_update',
+ field=models.DateField(default=datetime.date.today, verbose_name='Password last update'),
+ ),
+ migrations.AlterField(
+ model_name='customform',
+ name='header',
+ field=models.TextField(blank=True, default='', help_text='You can use markdown syntax.', verbose_name='Header text'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='footer',
+ field=models.TextField(blank=True, default='', help_text='You can use markdown syntax.', verbose_name='Footer text'),
+ ),
+ ]
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index c053f7c10..efa061431 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -3410,6 +3410,9 @@ class IshtarUser(FullSearch):
related_name="ishtaruser",
on_delete=models.CASCADE,
)
+ password_last_update = models.DateField(
+ _("Password last update"), default=datetime.date.today
+ )
advanced_shortcut_menu = models.BooleanField(
_("Advanced shortcut menu"), default=False
)
@@ -3491,6 +3494,8 @@ class IshtarUser(FullSearch):
def import_set_password(self, context, value):
self.user_ptr.set_password(value)
self.user_ptr.save()
+ self.password_last_update = datetime.date.today()
+ self.save()
@post_importer_action
def import_create_profile(self, context, value):
diff --git a/ishtar_common/templates/base.html b/ishtar_common/templates/base.html
index 53ef4f35a..be7187ef1 100644
--- a/ishtar_common/templates/base.html
+++ b/ishtar_common/templates/base.html
@@ -209,7 +209,7 @@
{% if MESSAGES %}{% for message, message_type in MESSAGES %}
<div class="alert alert-{{message_type}} alert-dismissible fade show"
role="alert">
- {{message}}
+ {{message|safe}}
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">&times;</span>
diff --git a/ishtar_common/urls_registration.py b/ishtar_common/urls_registration.py
index 0a18ff060..29d0055d7 100644
--- a/ishtar_common/urls_registration.py
+++ b/ishtar_common/urls_registration.py
@@ -39,9 +39,9 @@ urlpatterns = [
name='registration_disallowed'),
# url("^accounts/", include('django.contrib.auth.urls')),
path('accounts/login/', views.LoginView.as_view(), name='login'),
- path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
+ path('accounts/logout/', views.LogoutView.as_view(), name='logout'),
- path('accounts/password_change/', auth_views.PasswordChangeView.as_view(),
+ path('accounts/password_change/', views.PasswordChangeView.as_view(),
name='password_change'),
path('accounts/password_change/done/', auth_views.PasswordChangeDoneView.as_view(),
name='password_change_done'),
@@ -49,7 +49,7 @@ urlpatterns = [
path('accounts/password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('accounts/password_reset/done/', auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done'),
- path('accounts/reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(),
+ path('accounts/reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
path('accounts/reset/done/', auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index b6b58fe05..f5a58afad 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -31,9 +31,13 @@ from django.apps import apps
from django.conf import settings
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
-from django.contrib.auth.views import redirect_to_login, LoginView as AuthLoginView
+from django.contrib.auth.views import redirect_to_login, LoginView as AuthLoginView, \
+ PasswordChangeView as AuthPasswordChangeView, \
+ PasswordResetConfirmView as AuthPasswordResetConfirmView, \
+ LogoutView as AuthLogoutView
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
+from django.core.cache import cache
from django.db.models import Q
from django.template import loader
from django.forms.models import modelformset_factory
@@ -171,6 +175,42 @@ class LoginView(AuthLoginView):
form_class = forms.AuthenticationForm
+class LogoutView(AuthLogoutView):
+ def get(self, request, *args, **kwargs):
+ # clear cache
+ keys = []
+ if request.user and hasattr(request.user, "pk") and request.user.pk:
+ keys.append(f"{settings.PROJECT_SLUG}-password_expired-{request.user.pk}")
+ for key in keys:
+ cache.delete(key)
+ return super().get(request, *args, **kwargs)
+
+
+def update_password_last_update(user):
+ try:
+ ishtar_user = models.IshtarUser.objects.get(pk=user.pk)
+ except models.IshtarUser.DoesNotExist:
+ return
+ ishtar_user.password_last_update = datetime.date.today()
+ ishtar_user.save()
+ key = f"{settings.PROJECT_SLUG}-password_expired-{ishtar_user.pk}"
+ cache.set(key, False, settings.CACHE_TIMEOUT)
+
+
+class PasswordChangeView(AuthPasswordChangeView):
+ def form_valid(self, form):
+ returned = super().form_valid(form)
+ update_password_last_update(form.user)
+ return returned
+
+
+class PasswordResetConfirmView(AuthPasswordResetConfirmView):
+ def form_valid(self, form):
+ returned = super().form_valid(form)
+ update_password_last_update(form.user)
+ return returned
+
+
person_search_wizard = wizards.PersonSearch.as_view(
[("general-person_search", forms.PersonFormSelection)],
label=_("Person search"),
diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py
index f6b2894ab..72cd4db45 100644
--- a/ishtar_common/wizards.py
+++ b/ishtar_common/wizards.py
@@ -2040,6 +2040,7 @@ class AccountWizard(Wizard):
if dct["password"]:
account.set_password(dct["password"])
+ account.password_last_update = datetime.date.today()
account.save()
profile_form = form_dict["profile-account_management"]