mirror of
https://github.com/jazzband/django-ddp.git
synced 2026-04-27 02:04:52 +00:00
Update hash method in dddp.accounts to bind hash tokens to specified purposes.
This commit is contained in:
parent
a0cef34ecf
commit
92b42e365b
1 changed files with 47 additions and 15 deletions
|
|
@ -7,9 +7,11 @@ See http://docs.meteor.com/#/full/accounts_api for details of each method.
|
|||
"""
|
||||
from binascii import Error
|
||||
import collections
|
||||
import hashlib
|
||||
|
||||
from ejson import loads, dumps
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
from django.contrib.auth.signals import user_login_failed
|
||||
|
|
@ -28,6 +30,16 @@ forgot_password = Signal(providing_args=['request', 'user', 'token', 'expiry'])
|
|||
password_reset = Signal(providing_args=['request', 'user'])
|
||||
|
||||
|
||||
class HashPurpose(object):
|
||||
|
||||
"""HashPurpose enumeration."""
|
||||
|
||||
PASSWORD_RESET = 'password_reset'
|
||||
RESUME_LOGIN = 'resume_login'
|
||||
CHANGE_EMAIL = 'change_email'
|
||||
CREATE_USER = 'create_user'
|
||||
|
||||
|
||||
class Users(Collection):
|
||||
|
||||
"""Mimic `users` collection of Meteor's `accounts-password` package."""
|
||||
|
|
@ -201,36 +213,48 @@ class Auth(APIMixin):
|
|||
)
|
||||
raise MeteorError(403, 'Authentication failed.')
|
||||
|
||||
def validated_user_and_session(self, token):
|
||||
@staticmethod
|
||||
def get_auth_hash(user, purpose):
|
||||
"""Generate a user hash for a particular purpose."""
|
||||
return hashlib.sha1(
|
||||
':'.join([
|
||||
settings.SECRET_KEY,
|
||||
user.get_session_auth_hash(),
|
||||
purpose,
|
||||
])
|
||||
).hexdigest()
|
||||
|
||||
@classmethod
|
||||
def validated_user_and_session(cls, token, purpose):
|
||||
"""Resolve and validate auth token, returns user and session objects."""
|
||||
try:
|
||||
username, session_key, auth_hash = loads(token.decode('base64'))
|
||||
except (ValueError, Error):
|
||||
self.auth_failed(token=token)
|
||||
cls.auth_failed(token=token)
|
||||
try:
|
||||
user = self.user_model.objects.get(**{
|
||||
self.user_model.USERNAME_FIELD: username,
|
||||
user = cls.user_model.objects.get(**{
|
||||
cls.user_model.USERNAME_FIELD: username,
|
||||
})
|
||||
user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
except self.user_model.DoesNotExist:
|
||||
self.auth_failed(username=username, token=token)
|
||||
if user.get_session_auth_hash() != auth_hash:
|
||||
self.auth_failed(username=username, token=token)
|
||||
except cls.user_model.DoesNotExist:
|
||||
cls.auth_failed(username=username, token=token)
|
||||
if cls.get_auth_hash(user, purpose) != auth_hash:
|
||||
cls.auth_failed(username=username, token=token)
|
||||
session = SessionStore(
|
||||
session_key=session_key,
|
||||
)
|
||||
if session.get_expiry_date() <= timezone.now():
|
||||
self.auth_failed(username=username, token=token)
|
||||
cls.auth_failed(username=username, token=token)
|
||||
return (user, session)
|
||||
|
||||
@staticmethod
|
||||
def get_user_token(user, session_key, expiry_date):
|
||||
@classmethod
|
||||
def get_user_token(cls, user, session_key, expiry_date, purpose):
|
||||
"""Return login token info for given user."""
|
||||
token = ''.join(
|
||||
dumps([
|
||||
user.get_username(),
|
||||
session_key,
|
||||
user.get_session_auth_hash(),
|
||||
cls.get_auth_hash(user, purpose),
|
||||
]).encode('base64').split('\n')
|
||||
)
|
||||
return {
|
||||
|
|
@ -330,6 +354,7 @@ class Auth(APIMixin):
|
|||
user=user,
|
||||
session_key=this.request.session.session_key,
|
||||
expiry_date=this.request.session.get_expiry_date(),
|
||||
purpose=HashPurpose.CREATE_USER,
|
||||
)
|
||||
|
||||
@api_endpoint
|
||||
|
|
@ -367,6 +392,7 @@ class Auth(APIMixin):
|
|||
user=user,
|
||||
session_key=this.request.session.session_key,
|
||||
expiry_date=this.request.session.get_expiry_date(),
|
||||
purpose=HashPurpose.RESUME_LOGIN,
|
||||
)
|
||||
|
||||
# Call to `authenticate` was unable to verify the username and password.
|
||||
|
|
@ -386,8 +412,10 @@ class Auth(APIMixin):
|
|||
# never allow insecure login
|
||||
self.check_secure()
|
||||
|
||||
# pull the username, session_key and session_auth_hash from the token
|
||||
user, session = self.validated_user_and_session(params['resume'])
|
||||
# pull the username, session_key and auth_hash from the token
|
||||
user, session = self.validated_user_and_session(
|
||||
params['resume'], purpose=HashPurpose.RESUME_LOGIN,
|
||||
)
|
||||
|
||||
auth.login(this.request, user)
|
||||
self.update_subs(user.pk)
|
||||
|
|
@ -396,6 +424,7 @@ class Auth(APIMixin):
|
|||
user=user,
|
||||
session_key=session.session_key,
|
||||
expiry_date=session.get_expiry_date(),
|
||||
purpose=HashPurpose.RESUME_LOGIN,
|
||||
)
|
||||
|
||||
@api_endpoint('changePassword')
|
||||
|
|
@ -431,6 +460,7 @@ class Auth(APIMixin):
|
|||
token = self.get_user_token(
|
||||
user=user, session_key=this.request.session.session_key,
|
||||
expiry_date=expiry_date,
|
||||
purpose=HashPurpose.PASSWORD_RESET,
|
||||
)
|
||||
|
||||
forgot_password.send(
|
||||
|
|
@ -444,7 +474,9 @@ class Auth(APIMixin):
|
|||
@api_endpoint('resetPassword')
|
||||
def reset_password(self, token, new_password):
|
||||
"""Reset password using a token received in email then logs user in."""
|
||||
user, _ = self.validated_user_and_session(token)
|
||||
user, _ = self.validated_user_and_session(
|
||||
token, purpose=HashPurpose.PASSWORD_RESET,
|
||||
)
|
||||
user.set_password(new_password)
|
||||
user.save()
|
||||
auth.login(this.request, user)
|
||||
|
|
|
|||
Loading…
Reference in a new issue