From fb775de923969e1f5add47f2a77021e141c90b6b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 30 Jul 2020 12:50:48 +0200 Subject: [PATCH] Add backdoor to login as other users (in debug mode only) --- apps/member/hashers.py | 34 +++++++++++++++++++++++++++++++++- apps/member/views.py | 4 ++++ apps/permission/backends.py | 3 +-- apps/permission/decorators.py | 5 +++++ apps/permission/signals.py | 6 +++++- note_kfet/settings/__init__.py | 8 +++++--- 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/apps/member/hashers.py b/apps/member/hashers.py index 0c5d010b..9ebed95b 100644 --- a/apps/member/hashers.py +++ b/apps/member/hashers.py @@ -3,9 +3,12 @@ import hashlib -from django.contrib.auth.hashers import PBKDF2PasswordHasher +from django.conf import settings +from django.contrib.auth.hashers import PBKDF2PasswordHasher, BasePasswordHasher from django.utils.crypto import constant_time_compare +from note_kfet.middlewares import get_current_authenticated_user, get_current_session + class CustomNK15Hasher(PBKDF2PasswordHasher): """ @@ -20,8 +23,37 @@ class CustomNK15Hasher(PBKDF2PasswordHasher): """ algorithm = "custom_nk15" + def must_update(self, encoded): + if settings.DEBUG: + current_user = get_current_authenticated_user() + if current_user is not None and current_user.is_superuser: + return False + return True + def verify(self, password, encoded): + if settings.DEBUG: + current_user = get_current_authenticated_user() + if current_user is not None and current_user.is_superuser\ + and get_current_session().get("permission_mask", -1) >= 42: + return True + if '|' in encoded: salt, db_hashed_pass = encoded.split('$')[2].split('|') return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass) return super().verify(password, encoded) + + +class DebugSuperuserBackdoor(PBKDF2PasswordHasher): + """ + In debug mode and during the beta, superusers can login into other accounts for tests. + """ + def must_update(self, encoded): + return False + + def verify(self, password, encoded): + if settings.DEBUG: + current_user = get_current_authenticated_user() + if current_user is not None and current_user.is_superuser\ + and get_current_session().get("permission_mask", -1) >= 42: + return True + return super().verify(password, encoded) diff --git a/apps/member/views.py b/apps/member/views.py index 02cf69db..bc555acf 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta from PIL import Image from django.conf import settings +from django.contrib.auth import logout from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.contrib.auth.views import LoginView @@ -21,6 +22,7 @@ from note.forms import ImageForm from note.models import Alias, NoteUser from note.models.transactions import Transaction, SpecialTransaction from note.tables import HistoryTable, AliasTable +from note_kfet.middlewares import _set_current_user_and_ip from permission.backends import PermissionBackend from permission.models import Role from permission.views import ProtectQuerysetMixin @@ -38,6 +40,8 @@ class CustomLoginView(LoginView): form_class = CustomAuthenticationForm def form_valid(self, form): + logout(self.request) + _set_current_user_and_ip(form.get_user(), self.request.session, None) self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank return super().form_valid(form) diff --git a/apps/permission/backends.py b/apps/permission/backends.py index c1291a2f..c29419eb 100644 --- a/apps/permission/backends.py +++ b/apps/permission/backends.py @@ -1,7 +1,6 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from django.conf import settings from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import User, AnonymousUser from django.contrib.contenttypes.models import ContentType @@ -137,7 +136,7 @@ class PermissionBackend(ModelBackend): if sess is not None and sess.session_key is None: return False - if user_obj.is_superuser and get_current_session().get("permission_mask", -1) >= 42: + if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42: return True if obj is None: diff --git a/apps/permission/decorators.py b/apps/permission/decorators.py index f144935a..8963cb0b 100644 --- a/apps/permission/decorators.py +++ b/apps/permission/decorators.py @@ -4,6 +4,7 @@ from functools import lru_cache from time import time +from django.conf import settings from django.contrib.sessions.models import Session from note_kfet.middlewares import get_current_session @@ -32,6 +33,10 @@ def memoize(f): sess_funs = new_sess_funs def func(*args, **kwargs): + if settings.DEBUG: + # Don't memoize in DEBUG mode + return f(*args, **kwargs) + nonlocal last_collect if time() - last_collect > 60: diff --git a/apps/permission/signals.py b/apps/permission/signals.py index cac0a8a0..9b4bb161 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -1,6 +1,6 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - +from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.utils.translation import gettext_lazy as _ from note_kfet.middlewares import get_current_authenticated_user @@ -50,6 +50,10 @@ def pre_save_object(sender, instance, **kwargs): # In the other case, we check if he/she has the right to change one field previous = qs.get() + + if isinstance(instance, User) and instance.last_login != previous.last_login: + pass #return + for field in instance._meta.fields: field_name = field.name old_value = getattr(previous, field.name) diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py index 73aae469..ce691cc9 100644 --- a/note_kfet/settings/__init__.py +++ b/note_kfet/settings/__init__.py @@ -57,6 +57,8 @@ if "cas_server" in INSTALLED_APPS: if "logs" in INSTALLED_APPS: MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',) -if "debug_toolbar" in INSTALLED_APPS: - MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") - INTERNAL_IPS = ['127.0.0.1'] +if DEBUG: + PASSWORD_HASHERS += ['member.hashers.DebugSuperuserBackdoor'] + if "debug_toolbar" in INSTALLED_APPS: + MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") + INTERNAL_IPS = ['127.0.0.1']