Received: from mail.netlandish.com (unknown [10.138.202.29]) by code.netlandish.com (Postfix) with ESMTP id 8892A8115A for <~petersanchez/public-inbox@lists.code.netlandish.com>; Tue, 24 Nov 2020 10:25:44 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.221.52; helo=mail-wr1-f52.google.com; envelope-from=leo@jacobs-alumni.de; receiver= Authentication-Results: mail.netlandish.com; dkim=pass (2048-bit key; unprotected) header.d=jacobs-alumni.de header.i=@jacobs-alumni.de header.b=VRXSfHsj Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) by mail.netlandish.com (Postfix) with ESMTP id 5DB6B4A40F for <~petersanchez/public-inbox@lists.code.netlandish.com>; Tue, 24 Nov 2020 02:25:42 -0800 (PST) Received: by mail-wr1-f52.google.com with SMTP id s8so21699259wrw.10 for <~petersanchez/public-inbox@lists.code.netlandish.com>; Tue, 24 Nov 2020 02:25:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jacobs-alumni.de; s=google; h=mime-version:content-transfer-encoding:subject:message-id :user-agent:date:from:to; bh=NIvK7XPrWele4ifyp90EpfKNdadXt4K2UJc8eu48fJo=; b=VRXSfHsj7bn9pik+YyKbKLbogGM5nO6BewFwfJqzswzg4Pj8ab72vrysp7bdejbntn rYdkpA1/7TfpErZVnucr9bhjT4e3D9GcDZiuPDicM1hpqB3GEt+79FWwwEdf0r131LEl zs9oeCq2GHLqNjR08v2KRzhHH4X7Vw/ljGZHxyrAMG99PnY2b4Q1k14JYT13Ko+McCeD gJF9uKVGs45orcvh9qy0WZRmFheNRA/SnNnT0kUUltYc4qDVSAxYPvK+1U3LpRbj+Pxz KkwLoqmscUIfPJcxa6R1/dKW/7J7XcEcu4Tv2A8sJDg3z75h1tHUVyaIEtNPnoSHl7qT f6kg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:content-transfer-encoding:subject :message-id:user-agent:date:from:to; bh=NIvK7XPrWele4ifyp90EpfKNdadXt4K2UJc8eu48fJo=; b=fE7o1x2xJROaa6Se5gtTkw9W/r4aQdrbRQ0Htom5qYKuRy00B26ajX7LvminMzWx1/ LN0yW3MmmeUYUHMJIULVZCYa/J3D0Gr6U51DWLYdeHT+Q+xbo7GWKKDX0mFva55cSPvN sFwtS9QajQOkLPWJYuGhBIiqD1v7QcdtYpYI3xrGRc3sqfPz/ND8vWfpy3SEUsFg5fmB ndT00+BOW2OYuyCbtOQ/m2J6kiToTsOlabQFFaQiGxMz+s7jULG4HkRkae+wVe49hi23 nj+P15B5O7EM+CmQ7wNkRL73vMvsdOtlAnxReMqGoymQXbTr6AW/OKCt+xQ9mIHHH86k 6TLA== X-Gm-Message-State: AOAM531hO8BK88JY02WZsmXOHHiEpxupCAziI9823iWfh8GD3Ft2gMTd cRpNZPjwPB8S+9xLTbqyoauZYI9w0BVdsRPOgZY= X-Google-Smtp-Source: ABdhPJzmXoMDz9AZvWKwye24CRlVr6UyXH1u36wZ7/2ohvVdE8hZ5e8n1WruTK1AQR+Sz3wEhBJGSg== X-Received: by 2002:a5d:4701:: with SMTP id y1mr4353403wrq.33.1606213541568; Tue, 24 Nov 2020 02:25:41 -0800 (PST) Received: from [127.0.1.1] (p200300c1071e7c05ba8bb26b00b9eddd.dip0.t-ipconnect.de. [2003:c1:71e:7c05:ba8b:b26b:b9:eddd]) by smtp.gmail.com with ESMTPSA id d13sm27944495wrb.39.2020.11.24.02.25.40 for <~petersanchez/public-inbox@lists.code.netlandish.com> (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 24 Nov 2020 02:25:40 -0800 (PST) MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Subject: [PATCH django-impersonate] add option for auto-expiring impersonate sesions, implements #45 X-Mercurial-Node: c574577f8c34d7f3d4575c98f14ee807c3b6d5e2 X-Mercurial-Series-Index: 1 X-Mercurial-Series-Total: 1 Message-Id: X-Mercurial-Series-Id: User-Agent: Mercurial-patchbomb/5.6 Date: Tue, 24 Nov 2020 11:25:39 +0100 From: =?iso-8859-1?q?Leonhard_Kuboschek?= To: ~petersanchez/public-inbox@lists.code.netlandish.com # HG changeset patch # User Leonhard Kuboschek # 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[