Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cvat/apps/authentication/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ 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)
if "signed_email" in data:
user.set_hashed_signed_email(data["signed_email"])
user_email(user, email)
user_username(user, username)
if first_name:
user_field(user, "first_name", first_name)
if last_name:
user_field(user, "last_name", last_name)
if "password1" in data:
user.set_password(data["password1"])
else:
user.set_unusable_password()

self.populate_username(request, user)
if commit:
Expand Down
65 changes: 6 additions & 59 deletions cvat/apps/authentication/auth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
import base64
import binascii
from django.conf import settings
from django.db.models import Q
import rules
Expand All @@ -12,9 +10,8 @@
from django.core import signing
from rest_framework import authentication, exceptions
from rest_framework.authentication import TokenAuthentication as _TokenAuthentication
from rest_framework.authentication import BasicAuthentication as _BasicAuthentication
from rest_framework.authentication import get_authorization_header, _, HTTP_HEADER_ENCODING
from django.contrib.auth import login, authenticate, get_user_model

from django.contrib.auth import login

# Even with token authorization it is very important to have a valid session id
# in cookies because in some cases we cannot use token authorization (e.g. when
Expand Down Expand Up @@ -75,52 +72,6 @@ def authenticate(self, request):

return (user, None)

class BasicAuthentication(_BasicAuthentication):
def authenticate(self, request):
"""
Returns a `User` if a correct username and wallet address have been supplied
using HTTP Basic authentication. Otherwise returns `None`.
"""
auth = get_authorization_header(request).split()

if not auth or auth[0].lower() != b'basic':
return None

if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)

try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)

userid, wallet_address = auth_parts[0], auth_parts[2]
return self.authenticate_credentials(userid, wallet_address, request)

def authenticate_credentials(self, userid, wallet_address, request=None):
"""
Authenticate the userid and wallet address against username and wallet_address
with optional request for context.
"""
credentials = {
get_user_model().USERNAME_FIELD: userid,
'wallet_address': wallet_address
}
user = authenticate(request=request, **credentials)

if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))

if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

return (user, None)

# AUTH PREDICATES
has_admin_role = rules.is_group_member(str(AUTH_ROLE.ADMIN))
has_user_role = rules.is_group_member(str(AUTH_ROLE.USER))
Expand Down Expand Up @@ -291,8 +242,7 @@ def has_object_permission(self, request, view, obj):
class TaskCreatePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return True
#return request.user.has_perm('engine.task.create')
return request.user.has_perm('engine.task.create')

class TaskAccessPermission(BasePermission):
# pylint: disable=no-self-use
Expand Down Expand Up @@ -338,8 +288,7 @@ def get_queryset(self):
class TaskChangePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return True
#return request.user.has_perm('engine.task.change', obj)
return request.user.has_perm('engine.task.change', obj)

class TaskDeletePermission(BasePermission):
# pylint: disable=no-self-use
Expand All @@ -349,14 +298,12 @@ def has_object_permission(self, request, view, obj):
class JobAccessPermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return True
#return request.user.has_perm('engine.job.access', obj)
return request.user.has_perm('engine.job.access', obj)

class JobChangePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return True
#return request.user.has_perm('engine.job.change', obj)
return request.user.has_perm('engine.job.change', obj)

class JobReviewPermission(BasePermission):
# pylint: disable=no-self-use
Expand Down
3 changes: 3 additions & 0 deletions cvat/apps/authentication/authentication_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from allauth.account.utils import filter_users_by_email, filter_users_by_username
from django.contrib.auth import get_user_model

from cvat.apps.authentication.utils import validate_user_wallet_address

UserModel = get_user_model()

class AuthenticationBackend(_AuthenticationBackend):
Expand All @@ -19,6 +21,7 @@ def _authenticate_by_email(self, **credentials):
# and use username as fallback
email = credentials.get("email", credentials.get("username"))
if email:
validate_user_wallet_address(credentials["wallet_address"], email, credentials["signed_email"])
for user in filter_users_by_email(email):
if self._check_wallet_address(user, credentials["wallet_address"]):
return user
Expand Down
3 changes: 2 additions & 1 deletion cvat/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.1.10 on 2021-07-26 10:31
# Generated by Django 3.1.10 on 2021-07-26 14:04

from django.conf import settings
import django.contrib.auth.models
Expand All @@ -21,6 +21,7 @@ class Migration(migrations.Migration):
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
Expand Down
1 change: 0 additions & 1 deletion cvat/apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
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
Expand Down
12 changes: 7 additions & 5 deletions cvat/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,18 @@ def get_email_options(self):
class LoginSerializer(_LoginSerializer):
password = None
wallet_address = CharField(required=True)
signed_email = CharField(required=True)

def authenticate(self, **kwargs):
return authenticate(self.context['request'], **kwargs)

def _validate_email(self, email, wallet_address):
def _validate_email(self, email, wallet_address, signed_email):
user = None

if email and wallet_address:
user = self.authenticate(email=email, wallet_address=wallet_address)
if email and wallet_address and signed_email:
user = self.authenticate(email=email, wallet_address=wallet_address, signed_email=signed_email)
else:
msg = _('Must include "email" and "wallet_address".')
msg = _('Must include "email" and "wallet_address" and "signed_email".')
raise exceptions.ValidationError(msg)

return user
Expand Down Expand Up @@ -109,6 +110,7 @@ def validate(self, attrs):
username = attrs.get('username')
email = attrs.get('email')
wallet_address = attrs.get('wallet_address')
signed_email = attrs.get('signed_email')

user = None

Expand All @@ -117,7 +119,7 @@ def validate(self, attrs):

# Authentication through email
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
user = self._validate_email(email, wallet_address)
user = self._validate_email(email, wallet_address, signed_email)

# Authentication through username
elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
Expand Down
2 changes: 1 addition & 1 deletion cvat/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def add_ssh_keys():
'cvat.apps.authentication.auth.TokenAuthentication',
'cvat.apps.authentication.auth.SignatureAuthentication',
'rest_framework.authentication.SessionAuthentication',
'cvat.apps.authentication.auth.BasicAuthentication'
'rest_framework.authentication.BasicAuthentication'
],
'DEFAULT_VERSIONING_CLASS':
# Don't try to use URLPathVersioning. It will give you /api/{version}
Expand Down