Подтвердить что ты не робот

Возможно ли реализовать функцию "изменить пароль при следующем входе в систему" ​​в администраторе django?

Я хочу иметь возможность установить параметр в пользовательских настройках, который заставляет их менять свой пароль при следующем входе в интерфейс администратора. Это возможно? Как это будет реализовано? Я использую модель auth по умолчанию прямо сейчас, но не против изменения или изменения. Спасибо за любую помощь.

4b9b3361

Ответ 1

Я на самом деле сам делаю это. Вам нужны три компонента: профиль пользователя (если он еще не используется на вашем сайте), компонент промежуточного программного обеспечения и сигнал pre_save.

Мой код для этого находится в приложении с именем "учетные записи".

# myproject/accounts/models.py

from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    force_password_change = models.BooleanField(default=False)

def create_user_profile_signal(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

def password_change_signal(sender, instance, **kwargs):
    try:
        user = User.objects.get(username=instance.username)
        if not user.password == instance.password:
          profile = user.get_profile()
          profile.force_password_change = False
          profile.save()
    except User.DoesNotExist:
        pass

signals.pre_save.connect(password_change_signal, sender=User, dispatch_uid='accounts.models')

signals.post_save.connect(create_user_profile_signal, sender=User, dispatch_uid='accounts.models')

Сначала мы создаем UserProfile с внешним ключом для пользователя. Логический force_password_change boolean будет, по мере того, как его имя описывает, будет настроен на true для пользователя, когда вы хотите заставить их изменить свой пароль. Вы могли бы сделать все здесь. В моей организации мы также выбрали обязательное изменение каждые 90 дней, поэтому у меня также есть DateTimeField, который сохраняет последний раз, когда пользователь изменил свой пароль. Затем вы устанавливаете это в сигнале pre_save, password_changed_signal.

Во-вторых, мы имеем create_user_profile_signal. Это в основном добавляется только для полноты. Если вы просто добавляете профили пользователей в свой проект, вам понадобится сигнал post_save, который будет создавать UserProfile каждый раз, когда пользователь будет создан. Это выполняет эту задачу.

В-третьих, мы имеем password_changed_signal. Это сигнал pre_save, потому что в этот момент процесса фактическая строка в таблице User не обновляется. Поэтому мы можем получить доступ как к предыдущему паролю, так и к новому паролю, который будет сохранен. Если эти два не совпадают, это означает, что пользователь изменил свой пароль, и мы сможем затем reset force_password_change boolean. Это был бы смысл, если бы вы позаботились о каких-либо других вещах, которые вы добавили, например, о назначении ранее указанного DateTimeField.

Последние две строки присоединяют две функции к соответствующим сигналам.

Если вы еще этого не сделали, вам также потребуется добавить следующую строку в свой проект settings.py (изменение метки приложения и имени модели в соответствии с вашей настройкой):

AUTH_PROFILE_MODULE = 'accounts.UserProfile'

Это охватывает основы. Теперь нам нужен компонент промежуточного программного обеспечения для проверки состояния нашего флага force_password_change (и любых других необходимых проверок).

# myproject/accounts/middleware.py

from django.http import HttpResponseRedirect

import re

class PasswordChangeMiddleware:
    def process_request(self, request):
        if request.user.is_authenticated() and \
            re.match(r'^/admin/?', request.path) and \
            not re.match(r'^/admin/password_change/?', request.path):

            profile = request.user.get_profile()
            if profile.force_password_change:
                return HttpResponseRedirect('/admin/password_change/')

Это очень простое промежуточное соединение перехватывает этап process_request процесса загрузки страницы. Он проверяет, что 1) пользователь уже вошел в систему, 2) они пытаются получить доступ к какой-либо странице в админе, и 3) страница, к которой они обращаются, не является самой страницей смены пароля (в противном случае вы получите бесконечный цикл перенаправления). Если все они верны, а флаг force_password_change установлен на True, тогда пользователь перенаправляется на страницу смены пароля. Они не смогут перемещаться в другом месте до тех пор, пока они не изменят свой пароль (обжигающий сигнал pre_save, обсуждавшийся ранее).

Наконец, вам просто нужно добавить это промежуточное программное обеспечение в свой проект settings.py (опять же, изменив путь импорта по мере необходимости):

MIDDLEWARE_CLASSES = (
    # Other middleware here
    'myproject.accounts.middleware.PasswordChangeMiddleware',
)

Ответ 2

Я использовал решение Chris Pratt с небольшим изменением: вместо использования промежуточного программного обеспечения, которое будет выполняться для каждой страницы с последующим использованием ресурсов, я решил, что просто перехватил вид входа.

В моем urls.py я добавил это в мои urlpatterns:

url(r'^accounts/login/$', 'userbase.views.force_pwd_login'),

то я добавил следующее к userbase.views:

def force_pwd_login(request, *args, **kwargs):
    response = auth_views.login(request, *args, **kwargs)
    if response.status_code == 302:
        #We have a user
        try:
            if request.user.get_profile().force_password_change:
                return redirect('django.contrib.auth.views.password_change')
        except AttributeError: #No profile?
            pass
    return response

Кажется, что он работает безупречно на Django 1.2, но у меня нет оснований полагать, что у 1.3+ должны быть проблемы с ним.

Ответ 3

Из потока в список рассылки Django Users:

Это не идеально, но он должен работать (или попросите кого-нибудь предложить что-то лучше).

Добавьте индивидуальную таблицу для пользователя, с полем, содержащим начальную пароль (зашифрованный, конечно, так он выглядит как пароль в auth_user).

Когда пользователь входит в систему, введите логин проверьте ли пароли совпадение. Если они это сделают, перенаправите страницы смены пароля вместо нормальная страница переадресации.

Ответ 4

Это промежуточное программное обеспечение, которое я использую с Django 1.11:

# myproject/accounts/middleware.py

from django.http import HttpResponseRedirect
from django.urls import reverse


class PasswordChangeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        next = reverse('client:password-update')
        if request.user.is_authenticated() and request.path != next:
            if request.user.account.force_password_change:
                return HttpResponseRedirect(next)

        return response

Все еще добавляя его в список промежуточных программ:

MIDDLEWARE_CLASSES = (
    # Other middleware here
    'myproject.accounts.middleware.PasswordChangeMiddleware',
)

Ответ 5

Оформить этот простой пакет на основе сеанса (протестирован с помощью django 1.8). https://github.com/abdullatheef/django_force_reset_password

Создайте собственное представление в myapp.views.py

class PassWordReset(admin.AdminSite):

    def login(self, request, extra_context=None):
        if request.method == 'POST':
            response = super(PassWordReset, self).login(request, extra_context=extra_context)
            if response.status_code == 302 and request.user.is_authenticated():
                if not "fpr" in request.session or request.session['fpr']:
                    request.session['fpr'] = True
                    return HttpResponseRedirect("/admin/password_change/")
            return response
        return super(PassWordReset, self).login(request, extra_context=extra_context)

    def password_change(self, request, extra_context=None):
        if request.method == 'POST':
            response = super(PassWordReset, self).password_change(request, extra_context=extra_context)
            if response.status_code == 302 and request.user.is_authenticated():
                request.session['fpr'] = False
            return response
        return super(PassWordReset, self).password_change(request, extra_context=extra_context)


pfr_login = PassWordReset().login
pfr_password_change = PassWordReset().admin_view(PassWordReset().password_change, cacheable=True)

Затем в project/urls.py

from myapp.views import pfr_password_change, pfr_login
urlpatterns = [
    ......
    url(r'^admin/login/$', pfr_login),
    url(r'^admin/password_change/$', pfr_password_change),
    url(r'^admin/', admin.site.urls),
    ....
]

Затем добавьте это промежуточное ПО myapp/middleware.py

class FPRCheck(object):
    def process_request(self, request):
        if request.user.is_authenticated() \
                and re.match(r'^/admin/?', request.path) \
                and (not "fpr" in request.session or ("fpr" in request.session and request.session['fpr'])) \
                and not re.match(r"/admin/password_change|/admin/logout", request.path):
            return HttpResponseRedirect("/admin/password_change/")

Заказ промежуточного программного обеспечения

MIDDLEWARE_CLASSES = [
    ....

    'myapp.middleware.FPRCheck'
    ]

Примечание

  • Это не потребует дополнительной модели.
  • Также работайте с любым механизмом сеанса.
  • Нет запроса db внутри промежуточного программного обеспечения.

Ответ 6

Недавно я потратил 2 дня на эту проблему, и появилось новое решение. Надеюсь, это полезно.

Как было сказано выше, создана новая модель пользователя.

новый_пользователь/models.py

class Users(AbstractUser):
    default_pwd_updated = models.NullBooleanField(default=None, editable=False)
    pwd_update_time = models.DateTimeField(editable=False, null=True, default=None)  # reserved column to support further interval password (such as 60 days) update policy

     def set_password(self, raw_password):
        if self.default_pwd_updated is None:
            self.default_pwd_updated = False
        elif not self.default_pwd_updated:
            self.default_pwd_updated = True
            self.pwd_update_time = timezone.now()
        else:
            self.pwd_update_time = timezone.now()
        super().set_password(raw_password)

Установите эту модель как AUTH_USER_MODEL.

[проект]/settings.py

AUTH_USER_MODEL = 'newuser.Users'

Теперь вам просто нужно настроить LoginView и некоторые методы в AdminSite.

[проект]/admin.py

from django.contrib.admin import AdminSite
from django.contrib.auth.views import LoginView
from django.utils.translation import gettext as _, gettext_lazy
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect


class NewLoginView(LoginView):
    def get_redirect_url(self):
        if self.request.method == "POST" and self.request.user.get_username()\
                and not self.request.user.default_pwd_updated:
            redirect_to = reverse("admin:password_change")
        else:
            redirect_to = self.request.POST.get(
                self.redirect_field_name,
                self.request.GET.get(self.redirect_field_name, '')
            )
        return redirect_to


class NewAdminSite(AdminSite):
    site_header = site_title = gettext_lazy("Customized Admin Site")

    def __init__(self, name="admin"):
        super().__init__(name)

    @never_cache
    def login(self, request, extra_context=None):
        """
        Display the login form for the given HttpRequest.
        """
        if request.method == 'GET' and self.has_permission(request):
            # Already logged-in, redirect to admin index
            if request.user.get_username() and not request.user.default_pwd_updated:
                # default password not changed, force to password_change view
                path = reverse('admin:password_change', current_app=self.name)
            else:
                path = reverse('admin:index', current_app=self.name)
            return HttpResponseRedirect(path)

        from django.contrib.auth.views import LoginView
        from django.contrib.admin.forms import AdminAuthenticationForm
        context = {
            **self.each_context(request),
            'title': _('Log in'),
            'app_path': request.get_full_path(),
            'username': request.user.get_username(),
        }
        if (REDIRECT_FIELD_NAME not in request.GET and
                REDIRECT_FIELD_NAME not in request.POST):
            context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
        context.update(extra_context or {})

        defaults = {
            'extra_context': context,
            'authentication_form': self.login_form or AdminAuthenticationForm,
            'template_name': self.login_template or 'admin/login.html',
        }
        request.current_app = self.name
        return NewLoginView.as_view(**defaults)(request) # use NewLoginView

    @never_cache
    def index(self, request, extra_context=None):
        if request.user.get_username() and not request.user.default_pwd_updated:
            # if default password not updated, force to password_change page
            context = self.each_context(request)
            context.update(extra_context or {})
            return self.password_change(request, context)
        return super().index(request, extra_context)


admin_site = NewAdminSite(name="admin")

ПРИМЕЧАНИЕ: если вы намереваетесь использовать пользовательский шаблон для изменения пароля по умолчанию, вы можете переопределить метод each_context и затем определить, какой шаблон следует использовать, до флага force_pwd_change.

[проект]/admin.py

def using_default_password(self, request):
    if self.has_permission(request) and request.user.get_username() and not request.user.default_pwd_updated:
            return True
    return False

def each_context(self, request):
    context = super().each_context(request)
    context["force_pwd_change"] = self.using_default_password(request)
    return context