Я хочу иметь возможность установить параметр в пользовательских настройках, который заставляет их менять свой пароль при следующем входе в интерфейс администратора. Это возможно? Как это будет реализовано? Я использую модель auth по умолчанию прямо сейчас, но не против изменения или изменения. Спасибо за любую помощь.
Возможно ли реализовать функцию "изменить пароль при следующем входе в систему" в администраторе django?
Ответ 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