mirror of
https://github.com/pretix/pretix.git
synced 2026-05-07 15:34: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):
|
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"
|
key_salt = "pretix.base.models.User.get_session_auth_hash"
|
||||||
payload = self.password
|
payload = self.password
|
||||||
payload += self.email
|
payload += self.email
|
||||||
payload += self.session_token
|
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):
|
def update_session_token(self):
|
||||||
self.session_token = generate_session_token()
|
self.session_token = generate_session_token()
|
||||||
|
|||||||
@@ -219,13 +219,24 @@ class Customer(LoggedModel):
|
|||||||
return is_password_usable(self.password)
|
return is_password_usable(self.password)
|
||||||
|
|
||||||
def get_session_auth_hash(self):
|
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.
|
Return an HMAC of the password field.
|
||||||
"""
|
"""
|
||||||
key_salt = "pretix.base.models.customers.Customer.get_session_auth_hash"
|
key_salt = "pretix.base.models.customers.Customer.get_session_auth_hash"
|
||||||
payload = self.password
|
payload = self.password
|
||||||
payload += self.email
|
payload += self.email
|
||||||
return salted_hmac(key_salt, payload).hexdigest()
|
return salted_hmac(key_salt, payload, secret=secret).hexdigest()
|
||||||
|
|
||||||
def get_email_context(self):
|
def get_email_context(self):
|
||||||
from pretix.base.settings import get_name_parts_localized
|
from pretix.base.settings import get_name_parts_localized
|
||||||
|
|||||||
@@ -100,10 +100,23 @@ def get_customer(request):
|
|||||||
request._cached_customer = None
|
request._cached_customer = None
|
||||||
else:
|
else:
|
||||||
session_hash = session.get(hash_session_key)
|
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_verified = session_hash and constant_time_compare(
|
||||||
session_hash,
|
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:
|
if session_hash_verified:
|
||||||
request._cached_customer = customer
|
request._cached_customer = customer
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -628,7 +628,7 @@ def test_change_email(env, client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_change_pw(env, client):
|
def test_change_pw(env, client, client2):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
customer = env[0].customers.create(email='john@example.org', is_verified=True)
|
customer = env[0].customers.create(email='john@example.org', is_verified=True)
|
||||||
customer.set_password('foo')
|
customer.set_password('foo')
|
||||||
@@ -640,6 +640,12 @@ def test_change_pw(env, client):
|
|||||||
})
|
})
|
||||||
assert r.status_code == 302
|
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', {
|
r = client.post('/bigevents/account/password', {
|
||||||
'password_current': 'invalid',
|
'password_current': 'invalid',
|
||||||
'password': 'aYLBRNg4',
|
'password': 'aYLBRNg4',
|
||||||
@@ -658,6 +664,13 @@ def test_change_pw(env, client):
|
|||||||
customer.refresh_from_db()
|
customer.refresh_from_db()
|
||||||
assert customer.check_password('aYLBRNg4')
|
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
|
@pytest.mark.django_db
|
||||||
def test_login_per_org(env, client):
|
def test_login_per_org(env, client):
|
||||||
|
|||||||
Reference in New Issue
Block a user