Commit cd17a240 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Added optional kwargs to get_expiry_age/date.

This change allows for cleaner tests: we can test the exact output.

Refs #18194: this change makes it possible to compute session expiry
dates at times other than when the session is saved.

Fixed #18458: the existence of the `modification` kwarg implies that you
must pass it to get_expiry_age/date if you call these functions outside
of a short request - response cycle (the intended use case).
parent fc2681b2
Loading
Loading
Loading
Loading
+32 −8
Original line number Diff line number Diff line
@@ -170,28 +170,52 @@ class SessionBase(object):

    _session = property(_get_session)

    def get_expiry_age(self, expiry=None):
    def get_expiry_age(self, **kwargs):
        """Get the number of seconds until the session expires.

        expiry is an optional parameter specifying the datetime of expiry.
        Optionally, this function accepts `modification` and `expiry` keyword
        arguments specifying the modification and expiry of the session.
        """
        if expiry is None:
        try:
            modification = kwargs['modification']
        except KeyError:
            modification = timezone.now()
        # Make the difference between "expiry=None passed in kwargs" and
        # "expiry not passed in kwargs", in order to guarantee not to trigger
        # self.load() when expiry is provided.
        try:
            expiry = kwargs['expiry']
        except KeyError:
            expiry = self.get('_session_expiry')

        if not expiry:   # Checks both None and 0 cases
            return settings.SESSION_COOKIE_AGE
        if not isinstance(expiry, datetime):
            return expiry
        delta = expiry - timezone.now()
        delta = expiry - modification
        return delta.days * 86400 + delta.seconds

    def get_expiry_date(self):
        """Get session the expiry date (as a datetime object)."""
    def get_expiry_date(self, **kwargs):
        """Get session the expiry date (as a datetime object).

        Optionally, this function accepts `modification` and `expiry` keyword
        arguments specifying the modification and expiry of the session.
        """
        try:
            modification = kwargs['modification']
        except KeyError:
            modification = timezone.now()
        # Same comment as in get_expiry_age
        try:
            expiry = kwargs['expiry']
        except KeyError:
            expiry = self.get('_session_expiry')

        if isinstance(expiry, datetime):
            return expiry
        if not expiry:   # Checks both None and 0 cases
            expiry = settings.SESSION_COOKIE_AGE
        return timezone.now() + timedelta(seconds=expiry)
        return modification + timedelta(seconds=expiry)

    def set_expiry(self, value):
        """
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ class SessionStore(DBStore):
                )
                data = self.decode(s.session_data)
                cache.set(self.cache_key, data,
                    self.get_expiry_age(s.expire_date))
                    self.get_expiry_age(expiry=s.expire_date))
            except (Session.DoesNotExist, SuspiciousOperation):
                self.create()
                data = {}
+29 −17
Original line number Diff line number Diff line
@@ -197,31 +197,43 @@ class SessionTestsMixin(object):
        self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)

    def test_custom_expiry_seconds(self):
        # Using seconds
        modification = timezone.now()

        self.session.set_expiry(10)
        delta = self.session.get_expiry_date() - timezone.now()
        self.assertIn(delta.seconds, (9, 10))

        age = self.session.get_expiry_age()
        self.assertIn(age, (9, 10))
        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_timedelta(self):
        # Using timedelta
        modification = timezone.now()

        # Mock timezone.now, because set_expiry calls it on this code path.
        original_now = timezone.now
        try:
            timezone.now = lambda: modification
            self.session.set_expiry(timedelta(seconds=10))
        delta = self.session.get_expiry_date() - timezone.now()
        self.assertIn(delta.seconds, (9, 10))
        finally:
            timezone.now = original_now

        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age()
        self.assertIn(age, (9, 10))
        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_datetime(self):
        # Using fixed datetime
        self.session.set_expiry(timezone.now() + timedelta(seconds=10))
        delta = self.session.get_expiry_date() - timezone.now()
        self.assertIn(delta.seconds, (9, 10))
        modification = timezone.now()

        self.session.set_expiry(modification + timedelta(seconds=10))

        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age()
        self.assertIn(age, (9, 10))
        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_reset(self):
        self.session.set_expiry(None)
+11 −0
Original line number Diff line number Diff line
@@ -250,12 +250,23 @@ You can edit it multiple times.
      with no custom expiration (or those set to expire at browser close), this
      will equal :setting:`SESSION_COOKIE_AGE`.

      This function accepts two optional keyword arguments:

      - ``modification``: last modification of the session, as a
        :class:`~datetime.datetime` object. Defaults to the current time.
      - ``expiry``: expiry information for the session, as a
        :class:`~datetime.datetime` object, an :class:`int` (in seconds), or
        ``None``. Defaults to the value stored in the session by
        :meth:`set_expiry`, if there is one, or ``None``.

    .. method:: get_expiry_date

      Returns the date this session will expire. For sessions with no custom
      expiration (or those set to expire at browser close), this will equal the
      date :setting:`SESSION_COOKIE_AGE` seconds from now.

      This function accepts the same keyword argumets as :meth:`get_expiry_age`.

    .. method:: get_expire_at_browser_close

      Returns either ``True`` or ``False``, depending on whether the user's