mirror of
https://github.com/pretix/pretix.git
synced 2026-05-10 16:04:02 +00:00
Authentication: Support for fallback secret keys in get_session_auth_hash (#4481)
* Authentication: Support for fallback secret keys in get_session_auth_hash * Update src/pretix/presale/utils.py Co-authored-by: Richard Schreiber <schreiber@rami.io> --------- Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -571,13 +571,23 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
|
||||
def get_session_auth_hash(self):
|
||||
"""
|
||||
Return an HMAC that needs to
|
||||
Return an HMAC that needs to be the same throughout the session, used e.g. for forced
|
||||
logout after every password change.
|
||||
"""
|
||||
return self._get_session_auth_hash(secret=settings.SECRET_KEY)
|
||||
|
||||
def get_session_auth_fallback_hash(self):
|
||||
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
|
||||
yield self._get_session_auth_hash(secret=fallback_secret)
|
||||
|
||||
def _get_session_auth_hash(self, secret):
|
||||
"""
|
||||
"""
|
||||
key_salt = "pretix.base.models.User.get_session_auth_hash"
|
||||
payload = self.password
|
||||
payload += self.email
|
||||
payload += self.session_token
|
||||
return salted_hmac(key_salt, payload).hexdigest()
|
||||
return salted_hmac(key_salt, payload, secret=secret).hexdigest()
|
||||
|
||||
def update_session_token(self):
|
||||
self.session_token = generate_session_token()
|
||||
|
||||
@@ -219,13 +219,24 @@ class Customer(LoggedModel):
|
||||
return is_password_usable(self.password)
|
||||
|
||||
def get_session_auth_hash(self):
|
||||
"""
|
||||
Return an HMAC that needs to be the same throughout the session, used e.g. for forced
|
||||
logout after every password change.
|
||||
"""
|
||||
return self._get_session_auth_hash(secret=settings.SECRET_KEY)
|
||||
|
||||
def get_session_auth_fallback_hash(self):
|
||||
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
|
||||
yield self._get_session_auth_hash(secret=fallback_secret)
|
||||
|
||||
def _get_session_auth_hash(self, secret):
|
||||
"""
|
||||
Return an HMAC of the password field.
|
||||
"""
|
||||
key_salt = "pretix.base.models.customers.Customer.get_session_auth_hash"
|
||||
payload = self.password
|
||||
payload += self.email
|
||||
return salted_hmac(key_salt, payload).hexdigest()
|
||||
return salted_hmac(key_salt, payload, secret=secret).hexdigest()
|
||||
|
||||
def get_email_context(self):
|
||||
from pretix.base.settings import get_name_parts_localized
|
||||
|
||||
@@ -100,10 +100,23 @@ def get_customer(request):
|
||||
request._cached_customer = None
|
||||
else:
|
||||
session_hash = session.get(hash_session_key)
|
||||
session_auth_hash = customer.get_session_auth_hash()
|
||||
session_hash_verified = session_hash and constant_time_compare(
|
||||
session_hash,
|
||||
customer.get_session_auth_hash()
|
||||
session_auth_hash,
|
||||
)
|
||||
if not session_hash_verified:
|
||||
# If the current secret does not verify the session, try
|
||||
# with the fallback secrets and stop when a matching one is
|
||||
# found.
|
||||
if session_hash and any(
|
||||
constant_time_compare(session_hash, fallback_auth_hash)
|
||||
for fallback_auth_hash in customer.get_session_auth_fallback_hash()
|
||||
):
|
||||
request.session.cycle_key()
|
||||
request.session[hash_session_key] = session_auth_hash
|
||||
session_hash_verified = True
|
||||
|
||||
if session_hash_verified:
|
||||
request._cached_customer = customer
|
||||
else:
|
||||
|
||||
@@ -628,7 +628,7 @@ def test_change_email(env, client):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_change_pw(env, client):
|
||||
def test_change_pw(env, client, client2):
|
||||
with scopes_disabled():
|
||||
customer = env[0].customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password('foo')
|
||||
@@ -640,6 +640,12 @@ def test_change_pw(env, client):
|
||||
})
|
||||
assert r.status_code == 302
|
||||
|
||||
r = client2.post('/bigevents/account/login', {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
})
|
||||
assert r.status_code == 302
|
||||
|
||||
r = client.post('/bigevents/account/password', {
|
||||
'password_current': 'invalid',
|
||||
'password': 'aYLBRNg4',
|
||||
@@ -658,6 +664,13 @@ def test_change_pw(env, client):
|
||||
customer.refresh_from_db()
|
||||
assert customer.check_password('aYLBRNg4')
|
||||
|
||||
r = client.get('/bigevents/account/password')
|
||||
assert r.status_code == 200
|
||||
|
||||
# Client 2 got logged out
|
||||
r = client2.post('/bigevents/account/password')
|
||||
assert r.status_code == 302
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_login_per_org(env, client):
|
||||
|
||||
Reference in New Issue
Block a user