From 7f840d588c8cfc8c88cfdb1dd72eeacc2dda4d33 Mon Sep 17 00:00:00 2001 From: Dzerasha Date: Mon, 26 Jul 2021 13:37:04 +0300 Subject: [PATCH 1/2] Using signed email in django sessions --- cvat/apps/authentication/adapter.py | 9 ++----- cvat/apps/authentication/middleware.py | 25 ------------------- .../authentication/migrations/0001_initial.py | 3 ++- cvat/apps/authentication/models.py | 16 +++++++++--- cvat/apps/authentication/serializers.py | 1 + cvat/apps/authentication/utils.py | 19 ++++++++++++-- cvat/settings/base.py | 4 +-- 7 files changed, 37 insertions(+), 40 deletions(-) delete mode 100644 cvat/apps/authentication/middleware.py diff --git a/cvat/apps/authentication/adapter.py b/cvat/apps/authentication/adapter.py index 132705f3668c..92bd268ad07f 100644 --- a/cvat/apps/authentication/adapter.py +++ b/cvat/apps/authentication/adapter.py @@ -1,14 +1,8 @@ # Copyright (C) 2021 Intel Corporation # # SPDX-License-Identifier: MIT - -from cvat.apps.engine.log import slogger - -from django.conf import settings from allauth.account.adapter import DefaultAccountAdapter -from django.contrib.auth import SESSION_KEY, BACKEND_SESSION_KEY, user_logged_in - class UserAdapter(DefaultAccountAdapter): def save_user(self, request, user, form, commit=True): """ @@ -22,6 +16,8 @@ def save_user(self, request, user, form, commit=True): last_name = data.get("last_name") email = data.get("email") username = data.get("username") + signed_email = data.get("signed_email") + user.set_hashed_signed_email(signed_email) user_email(user, email) user_username(user, username) if first_name: @@ -30,7 +26,6 @@ def save_user(self, request, user, form, commit=True): user_field(user, "last_name", last_name) self.populate_username(request, user) - user.set_unusable_password() if commit: # Ability not to commit makes it easier to derive from # this adapter by adding diff --git a/cvat/apps/authentication/middleware.py b/cvat/apps/authentication/middleware.py deleted file mode 100644 index 7da7c82326ef..000000000000 --- a/cvat/apps/authentication/middleware.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (C) 2021 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.utils.deprecation import MiddlewareMixin -from django.utils.functional import SimpleLazyObject -from django.contrib import auth - - -def get_user(request): - print("GUM: " + request.session.session_key) - if not hasattr(request, '_cached_user'): - request._cached_user = auth.get_user(request) - return request._cached_user - - -class AuthenticationMiddleware(MiddlewareMixin): - def process_request(self, request): - assert hasattr(request, 'session'), ( - "The Django authentication middleware requires session middleware " - "to be installed. Edit your MIDDLEWARE setting to insert " - "'django.contrib.sessions.middleware.SessionMiddleware' before " - "'django.contrib.auth.middleware.AuthenticationMiddleware'." - ) - request.user = SimpleLazyObject(lambda: get_user(request)) \ No newline at end of file diff --git a/cvat/apps/authentication/migrations/0001_initial.py b/cvat/apps/authentication/migrations/0001_initial.py index ea5a3d1e2757..5a1b1f4f9ab3 100644 --- a/cvat/apps/authentication/migrations/0001_initial.py +++ b/cvat/apps/authentication/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.10 on 2021-07-08 15:19 +# Generated by Django 3.1.10 on 2021-07-26 10:31 from django.conf import settings import django.contrib.auth.models @@ -30,6 +30,7 @@ class Migration(migrations.Migration): ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('hashed_signed_email', models.CharField(default='', max_length=128, verbose_name='hashed_signed_email')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], diff --git a/cvat/apps/authentication/models.py b/cvat/apps/authentication/models.py index 40f8a7a1fc6e..43e5f8070e58 100644 --- a/cvat/apps/authentication/models.py +++ b/cvat/apps/authentication/models.py @@ -8,19 +8,29 @@ from django.db import models from django.utils.crypto import salted_hmac +from django.utils.translation import gettext_lazy as _ + +from cvat.apps.authentication.utils import hash_signed_email class User(AbstractUser): password = None + hashed_signed_email = models.CharField(_('hashed_signed_email'), max_length=128, default='') + + _hashed_signed_email = None + + def set_hashed_signed_email(self, raw_signed_email): + self.hashed_signed_email = hash_signed_email(raw_signed_email) + self._hashed_signed_email = raw_signed_email def get_session_auth_hash(self): # TODO: rework this temporary solution """ - Return an HMAC of the email field. + Return an HMAC of the hashed and signed email field. """ - key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" + key_salt = "cvat.apps.authentication.models.User.get_session_auth_hash" return salted_hmac( key_salt, - self.email, + self.hashed_signed_email, # RemovedInDjango40Warning: when the deprecation ends, replace # with: # algorithm='sha256', diff --git a/cvat/apps/authentication/serializers.py b/cvat/apps/authentication/serializers.py index 36f48b705955..952caad4aace 100644 --- a/cvat/apps/authentication/serializers.py +++ b/cvat/apps/authentication/serializers.py @@ -39,6 +39,7 @@ def get_cleaned_data(self): 'email': self.validated_data.get('email', ''), 'first_name': self.validated_data.get('first_name', ''), 'last_name': self.validated_data.get('last_name', ''), + 'signed_email': self.validated_data.get('signed_email', '') } def save(self, request): diff --git a/cvat/apps/authentication/utils.py b/cvat/apps/authentication/utils.py index 8cc4951c777c..61b219896101 100644 --- a/cvat/apps/authentication/utils.py +++ b/cvat/apps/authentication/utils.py @@ -4,6 +4,8 @@ from web3.auto import w3 from eth_account.messages import encode_defunct +from django.contrib.auth.hashers import get_hasher + def validate_user_wallet_address(wallet_address, email, signed_email): message_hash = encode_defunct(text=email) @@ -20,9 +22,22 @@ def setup_user_wallet_address(request, user): wallet_address = request.data.get('wallet_address') - #assert not WalletToUser.objects.filter(wallet_address=wallet_address).exists() + assert not WalletToUser.objects.filter(wallet_address=wallet_address).exists() walletToUser = WalletToUser(user=user, wallet_address=wallet_address) walletToUser.save() - return wallet_address \ No newline at end of file + return wallet_address + +def hash_signed_email(signed_email, salt=None, hasher='default'): + """ + Turn a signed email into a hash for database storage + """ + if not isinstance(signed_email, (bytes, str)): + raise TypeError( + 'Signed email must be a string or bytes, got %s.' + % type(signed_email).__qualname__ + ) + hasher = get_hasher(hasher) + salt = salt or hasher.salt() + return hasher.encode(signed_email, salt) \ No newline at end of file diff --git a/cvat/settings/base.py b/cvat/settings/base.py index e1a73435cea5..099b4648330e 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -172,8 +172,6 @@ def add_ssh_keys(): AUTH_USER_MODEL = 'authentication.User' -ADAPTER = 'authentication.adapter.UserAdapter' - REST_AUTH_REGISTER_SERIALIZERS = { 'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer', } @@ -249,6 +247,8 @@ def add_ssh_keys(): ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_AUTHENTICATION_METHOD = 'email' +ACCOUNT_ADAPTER = 'cvat.apps.authentication.adapter.UserAdapter' + #OLD_PASSWORD_FIELD_ENABLED = True From 98e7ef48fbca81f92051859d45813b2e80c5203c Mon Sep 17 00:00:00 2001 From: Dzerasha Date: Mon, 26 Jul 2021 15:46:20 +0300 Subject: [PATCH 2/2] Unused model backend removed --- cvat/apps/authentication/backends.py | 16 ---------------- cvat/settings/base.py | 3 +-- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 cvat/apps/authentication/backends.py diff --git a/cvat/apps/authentication/backends.py b/cvat/apps/authentication/backends.py deleted file mode 100644 index f7c80896b9a4..000000000000 --- a/cvat/apps/authentication/backends.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (C) 2021 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend as _ModelBackend - -UserModel = get_user_model() - -class ModelBackend(_ModelBackend): - def get_user(self, user_id): - try: - user = UserModel._default_manager.get(pk=user_id) - except UserModel.DoesNotExist: - return None - return user if self.user_can_authenticate(user) else None \ No newline at end of file diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 099b4648330e..a89e6c2d4210 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -234,8 +234,7 @@ def add_ssh_keys(): AUTHENTICATION_BACKENDS = [ 'rules.permissions.ObjectPermissionBackend', - 'cvat.apps.authentication.backends.ModelBackend', - #'django.contrib.auth.backends.ModelBackend', + 'django.contrib.auth.backends.ModelBackend', #'allauth.account.auth_backends.AuthenticationBackend', 'cvat.apps.authentication.authentication_backends.AuthenticationBackend' ]