diff --git a/idhub/urls.py b/idhub/urls.py index 785f4d1..f7df70d 100644 --- a/idhub/urls.py +++ b/idhub/urls.py @@ -17,7 +17,7 @@ Including another URLconf from django.contrib.auth import views as auth_views from django.views.generic import RedirectView from django.urls import path, reverse_lazy -from .views import LoginView +from .views import LoginView, PasswordResetConfirmView from .admin import views as views_admin from .user import views as views_user @@ -44,13 +44,16 @@ urlpatterns = [ ), name='password_reset_done' ), - path('auth/reset///', - auth_views.PasswordResetConfirmView.as_view( - template_name='auth/password_reset_confirm.html', - success_url=reverse_lazy('idhub:password_reset_complete') - ), + path('auth/reset///', PasswordResetConfirmView.as_view(), name='password_reset_confirm' ), + # path('auth/reset///', + # auth_views.PasswordResetConfirmView.as_view( + # template_name='auth/password_reset_confirm.html', + # success_url=reverse_lazy('idhub:password_reset_complete') + # ), + # name='password_reset_confirm' + # ), path('auth/reset/done/', auth_views.PasswordResetCompleteView.as_view( template_name='auth/password_reset_complete.html' diff --git a/idhub/views.py b/idhub/views.py index 3db164f..8e7f542 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -4,7 +4,6 @@ from django.utils.translation import gettext_lazy as _ from django.contrib.auth import views as auth_views from django.contrib.auth import login as auth_login from django.http import HttpResponseRedirect -from nacl import secret class LoginView(auth_views.LoginView): @@ -23,7 +22,7 @@ class LoginView(auth_views.LoginView): user = form.get_user() # Decrypt the user's sensitive data encryption key and store it in the session. password = form.cleaned_data.get("password") - sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) + sensitive_data_encryption_key = user.decrypt_sensitive_data(password) key_dids = cache.get("KEY_DIDS", {}) if not user.is_anonymous and user.is_admin: user_dashboard = reverse_lazy('idhub:user_dashboard') @@ -41,3 +40,14 @@ class LoginView(auth_views.LoginView): return HttpResponseRedirect(self.extra_context['success_url']) + +class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): + template_name = 'auth/password_reset_confirm.html' + success_url = reverse_lazy('idhub:password_reset_complete') + + def form_valid(self, form): + password = form.cleaned_data.get("password") + user = form.get_user() + user.set_encrypted_sensitive_data(password) + user.save() + return HttpResponseRedirect(self.success_url) diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py index f292182..c9ab1f6 100644 --- a/idhub_auth/forms.py +++ b/idhub_auth/forms.py @@ -2,7 +2,7 @@ import re from django import forms from django.utils.translation import gettext_lazy as _ -from idhub_auth.models import User +from idhub_auth.models import User, gen_salt class ProfileForm(forms.ModelForm): @@ -31,4 +31,3 @@ class ProfileForm(forms.ModelForm): return last_name - diff --git a/idhub_auth/models.py b/idhub_auth/models.py index cf1ac69..2fcf669 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -1,6 +1,9 @@ +import nacl +import base64 + from django.db import models +from django.core.cache import cache from django.contrib.auth.models import BaseUserManager, AbstractBaseUser -from nacl import secret, pwhash class UserManager(BaseUserManager): @@ -44,10 +47,8 @@ class User(AbstractBaseUser): is_admin = models.BooleanField(default=False) first_name = models.CharField(max_length=255, blank=True, null=True) last_name = models.CharField(max_length=255, blank=True, null=True) - # TODO: Hay que generar una clave aleatoria para cada usuario cuando se le da de alta en el sistema. - encrypted_sensitive_data_encryption_key = models.BinaryField(max_length=255) - # TODO: Hay que generar un salt aleatorio para cada usuario cuando se le da de alta en el sistema. - salt_of_sensitive_data_encryption_key = models.BinaryField(max_length=255) + encrypted_sensitive_data = models.CharField(max_length=255) + salt = models.CharField(max_length=255) objects = UserManager() @@ -92,14 +93,54 @@ class User(AbstractBaseUser): return ", ".join(set(roles)) def derive_key_from_password(self, password): - kdf = pwhash.argon2i.kdf # TODO: Move the KDF choice to SETTINGS.PY - ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY - mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY - salt = self.salt_of_sensitive_data_encryption_key - return kdf(secret.SecretBox.KEY_SIZE, password, salt, opslimit=ops, memlimit=mem) + kdf = nacl.pwhash.argon2i.kdf + ops = nacl.pwhash.argon2i.OPSLIMIT_INTERACTIVE + mem = nacl.pwhash.argon2i.MEMLIMIT_INTERACTIVE + return kdf( + nacl.secret.SecretBox.KEY_SIZE, + password, + self.get_salt(), + opslimit=ops, + memlimit=mem + ) + + def decrypt_sensitive_data(self, password, data=None): + sb_key = self.derive_key_from_password(password.encode('utf-8')) + sb = nacl.secret.SecretBox(sb_key) + if not data: + data = self.get_encrypted_sensitive_data() + if not isinstance(data, bytes): + data = data.encode('utf-8') + + return sb.decrypt(data).decode('utf-8') + + def encrypt_sensitive_data(self, password, data): + sb_key = self.derive_key_from_password(password.encode('utf-8')) + sb = nacl.secret.SecretBox(sb_key) + if not isinstance(data, bytes): + data = data.encode('utf-8') + + return sb.encrypt(data).decode('utf-8') + + def get_salt(self): + return base64.b64decode(self.salt.encode('utf-8')) + + def set_salt(self): + self.salt = base64.b64encode(nacl.utils.random(16)).decode('utf-8') + + def get_encrypted_sensitive_data(self): + return base64.b64decode(self.encrypted_sensitive_data.encode('utf-8')) + + def set_encrypted_sensitive_data(self, password): + key = base64.b64encode(nacl.utils.random(64)) + key_dids = cache.get("KEY_DIDS", {}) + + if key_dids.get(user.id): + key = key_dids[user.id] + else: + self.set_salt() + + key_crypted = self.encrypt_sensitive_data(password, key) + self.encrypted_sensitive_data = base64.b64encode(key_crypted).decode('utf-8') - def decrypt_sensitive_data_encryption_key(self, password): - sb_key = self.derive_key_from_password(password) - sb = secret.SecretBox(sb_key) - return sb.decrypt(self.encrypted_sensitive_data_encryption_key)