#37200: Signer.unsign() doesn't accept max_age, breaking
HttpRequest.get_signed_cookie() when SIGNING_BACKEND is not a
TimestampSigner
-------------------------------------+-------------------------------------
Reporter: Stefan Lilov | Owner: (none)
Type: Bug | Status: new
Component: HTTP handling | Version: 5.2
Severity: Normal | Resolution:
Keywords: signing, cookies, | Triage Stage:
SIGNING_BACKEND, | Unreviewed
get_signed_cookie |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Stefan Lilov):
Until this is fixed in core, projects hitting this can work around it
entirely
at the settings level, without touching any call sites, by pointing
`SIGNING_BACKEND` at a thin `Signer` subclass that accepts and ignores
`max_age`:
{{{#!python
# myproject/signing.py
from django.core import signing
class MaxAgeCompatSigner(signing.Signer):
"""
Signer that tolerates the max_age kwarg
HttpRequest.get_signed_cookie()
(via signing._unsign_cookie()) always passes, regardless of
SIGNING_BACKEND. Signer.unsign() doesn't accept max_age at all -- only
TimestampSigner.unsign() does -- so with SIGNING_BACKEND set to a
plain
Signer, request.get_signed_cookie() raises TypeError unconditionally.
This subclass makes SIGNING_BACKEND-selected signers interchangeable
with
TimestampSigner for that purpose, without adopting its timestamp-
prefixed
signing format (i.e. without gaining real expiry enforcement).
"""
def unsign(self, signed_value, max_age=None):
return super().unsign(signed_value)
}}}
{{{#!python
# settings.py
SIGNING_BACKEND = "myproject.signing.MaxAgeCompatSigner"
}}}
This is a strict superset of `Signer`'s existing interface -- `sign()`,
`signature()`, `sign_object()`, and `__init__` are all inherited
unchanged, so
signature output is byte-for-byte identical to what a plain `Signer`
produces
(HMAC computation only depends on `key`/`salt`/`algorithm`/`sep`, none of
which the subclass touches). Existing signed cookies remain valid after
switching -- no forced re-signing or logout. `contrib.messages`'
`CookieStorage` (which also goes through `get_cookie_signer()`) is
unaffected
too, since `unsign_object()` never passes `max_age` in the first place.
The one thing to flag for anyone using this: it's a compatibility shim,
not
real expiry support. `max_age` is silently ignored, exactly as it already
was
whenever `SIGNING_BACKEND` pointed at a plain `Signer` before this ticket
was
filed -- if a project actually needs signature-age enforcement on cookies,
it
should use `TimestampSigner` (the default) rather than this workaround.
Confirmed against 5.2.4, 5.2.14, and 5.2.15 -- the underlying
incompatibility
(`Signer.unsign()` never having a `max_age` param) is identical across all
three, so the workaround applies regardless of patch version.
--
Ticket URL: <
https://code.djangoproject.com/ticket/37200#comment:1>