# HG changeset patch
# User Leonhard Kuboschek <leo@jacobs-alumni.de>
# Date 1606156336 -3600
# Mon Nov 23 19:32:16 2020 +0100
# Node ID c574577f8c34d7f3d4575c98f14ee807c3b6d5e2
# Parent 1e3192bbb6762d1756fd203d28714620ef43da15
add option for auto-expiring impersonate sesions, implements #45
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -395,6 +395,13 @@
Default is `True`
+ MAX_DURATION
+
+A number specifying the maximum allowed duration of impersonation
+sessions in **seconds**.
+
+Default is `None`
+
Admin
=====
diff --git a/README.rst b/README.rst
--- a/README.rst
+++ b/README.rst
@@ -464,6 +464,15 @@
Default is ``True``
+::
+
+ MAX_DURATION
+
+A number specifying the maximum allowed duration of impersonation
+sessions in **seconds**.
+
+Default is `None`
+
Admin
=====
diff --git a/impersonate/middleware.py b/impersonate/middleware.py
--- a/impersonate/middleware.py
+++ b/impersonate/middleware.py
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
+from datetime import datetime, timedelta, tzinfo
+
from django.http import HttpResponseNotAllowed
from django.utils.deprecation import MiddlewareMixin
+from django.utils import timezone
from .helpers import User, check_allow_for_uri, check_allow_for_user
from .settings import settings
@@ -12,6 +15,18 @@
request.impersonator = None
if request.user.is_authenticated and '_impersonate' in request.session:
+ if settings.MAX_DURATION:
+ if '_impersonate_start' not in request.session:
+ return
+
+ start_time = timezone.datetime.fromtimestamp(request.session['_impersonate_start'], timezone.utc)
+
+ if datetime.now(timezone.utc) - start_time > timedelta(seconds=settings.MAX_DURATION):
+ del request.session['_impersonate']
+ del request.session['_impersonate_start']
+
+ return
+
new_user_id = request.session['_impersonate']
if isinstance(new_user_id, User):
# Edge case for issue 15
diff --git a/impersonate/settings.py b/impersonate/settings.py
--- a/impersonate/settings.py
+++ b/impersonate/settings.py
@@ -23,6 +23,7 @@
'ADMIN_DELETE_PERMISSION': False,
'ADMIN_ADD_PERMISSION': False,
'ADMIN_READ_ONLY': True,
+ 'MAX_DURATION': None,
}
diff --git a/impersonate/tests.py b/impersonate/tests.py
--- a/impersonate/tests.py
+++ b/impersonate/tests.py
@@ -22,6 +22,7 @@
from distutils.version import LooseVersion
from unittest.mock import PropertyMock, patch
from urllib.parse import urlencode, urlsplit
+from datetime import datetime, timedelta, timezone
import django
from django.urls import include, path
@@ -110,11 +111,12 @@
return None
self.middleware = ImpersonateMiddleware(dummy_get_response)
- def _impersonated_request(self, use_id=True):
+ def _impersonated_request(self, use_id=True, _impersonate_start=None):
request = self.factory.get('/')
request.user = self.superuser
request.session = {
'_impersonate': self.user.pk if use_id else self.user,
+ '_impersonate_start': _impersonate_start
}
self.middleware.process_request(request)
@@ -133,6 +135,45 @@
'''
self._impersonated_request(use_id=False)
+ @override_settings(IMPERSONATE={'MAX_DURATION': 3600})
+ def test_impersonated_request_with_max_duration(self):
+ self._impersonated_request(_impersonate_start=datetime.now(timezone.utc).timestamp())
+
+ @override_settings(IMPERSONATE={'MAX_DURATION': 3600})
+ def test_reject_without_start_time(self):
+ ''' Test to ensure that requests without a start time
+ are rejected when MAX_DURATION is set
+ '''
+ request = self.factory.get('/')
+ request.user = self.superuser
+ request.session = {
+ '_impersonate': self.user.pk,
+ }
+ self.middleware.process_request(request)
+
+ self.assertEqual(request.user, self.superuser)
+ self.assertFalse(request.user.is_impersonate)
+
+
+ @override_settings(IMPERSONATE={'MAX_DURATION': 3600})
+ def test_reject_expired_impersonation(self):
+ ''' Test to ensure that requests with a start time before (now - MAX_DURATION)
+ are rejected
+ '''
+ request = self.factory.get('/')
+ request.user = self.superuser
+ request.session = {
+ '_impersonate': self.user.pk,
+ '_impersonate_start': (datetime.now(timezone.utc) - timedelta(seconds=3601)).timestamp()
+ }
+ self.middleware.process_request(request)
+
+ self.assertEqual(request.user, self.superuser)
+ self.assertFalse(request.user.is_impersonate)
+ self.assertNotIn('_impersonate', request.session)
+ self.assertNotIn('_impersonate_start', request.session)
+
+
def test_not_impersonated_request(self, use_id=True):
"""Check the real_user request attr is set correctly when **not** impersonating."""
request = self.factory.get('/')
diff --git a/impersonate/views.py b/impersonate/views.py
--- a/impersonate/views.py
+++ b/impersonate/views.py
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
+from datetime import datetime
import logging
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
+from django.utils import timezone
from .decorators import allowed_user_required
from .helpers import (
@@ -36,6 +38,7 @@
raise Http404('Invalid value given.')
if check_allow_for_user(request, new_user):
request.session['_impersonate'] = new_user.pk
+ request.session['_impersonate_start'] = datetime.now(tz=timezone.utc).timestamp()
prev_path = request.META.get('HTTP_REFERER')
if prev_path:
request.session[