1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-09 19:07:40 +00:00

[sysadmin] Force passwd change when add/reset user

This commit is contained in:
zhengxie 2015-11-23 17:55:42 +08:00
parent 808a5e186d
commit 054ef03687
11 changed files with 165 additions and 7 deletions

View File

@ -23,6 +23,7 @@ from seahub.auth.forms import AuthenticationForm, CaptchaAuthenticationForm
from seahub.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm from seahub.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm
from seahub.auth.tokens import default_token_generator from seahub.auth.tokens import default_token_generator
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.options.models import UserOptions
from seahub.utils import is_ldap_user from seahub.utils import is_ldap_user
from seahub.utils.http import is_safe_url from seahub.utils.http import is_safe_url
from seahub.utils.ip import get_remote_ip from seahub.utils.ip import get_remote_ip
@ -134,6 +135,11 @@ def login(request, template_name='registration/login.html',
# have captcha # have captcha
form = CaptchaAuthenticationForm(data=request.POST) form = CaptchaAuthenticationForm(data=request.POST)
if form.is_valid(): if form.is_valid():
if UserOptions.objects.passwd_change_required(
form.get_user().username):
redirect_to = reverse('auth_password_change')
request.session['force_passwd_change'] = True
# captcha & passwod is valid, log user in # captcha & passwod is valid, log user in
request.session['remember_me'] = remember_me request.session['remember_me'] = remember_me
return log_user_in(request, form.get_user(), redirect_to) return log_user_in(request, form.get_user(), redirect_to)
@ -143,6 +149,11 @@ def login(request, template_name='registration/login.html',
else: else:
form = authentication_form(data=request.POST) form = authentication_form(data=request.POST)
if form.is_valid(): if form.is_valid():
if UserOptions.objects.passwd_change_required(
form.get_user().username):
redirect_to = reverse('auth_password_change')
request.session['force_passwd_change'] = True
# password is valid, log user in # password is valid, log user in
request.session['remember_me'] = remember_me request.session['remember_me'] = remember_me
return log_user_in(request, form.get_user(), redirect_to) return log_user_in(request, form.get_user(), redirect_to)
@ -365,6 +376,12 @@ def password_change(request, template_name='registration/password_change_form.ht
form = password_change_form(user=request.user, data=request.POST) form = password_change_form(user=request.user, data=request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
if request.session.get('force_passwd_change', False):
del request.session['force_passwd_change']
UserOptions.objects.unset_force_passwd_change(
request.user.username)
update_session_auth_hash(request, request.user) update_session_auth_hash(request, request.user)
return HttpResponseRedirect(post_change_redirect) return HttpResponseRedirect(post_change_redirect)
else: else:
@ -375,6 +392,7 @@ def password_change(request, template_name='registration/password_change_form.ht
'min_len': config.USER_PASSWORD_MIN_LENGTH, 'min_len': config.USER_PASSWORD_MIN_LENGTH,
'strong_pwd_required': config.USER_STRONG_PASSWORD_REQUIRED, 'strong_pwd_required': config.USER_STRONG_PASSWORD_REQUIRED,
'level': config.USER_PASSWORD_STRENGTH_LEVEL, 'level': config.USER_PASSWORD_STRENGTH_LEVEL,
'force_passwd_change': request.session.get('force_passwd_change', False),
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
def password_change_done(request, template_name='registration/password_change_done.html'): def password_change_done(request, template_name='registration/password_change_done.html'):

View File

@ -1,4 +1,8 @@
import re
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
import seaserv import seaserv
@ -12,6 +16,7 @@ try:
from seahub.settings import MULTI_TENANCY from seahub.settings import MULTI_TENANCY
except ImportError: except ImportError:
MULTI_TENANCY = False MULTI_TENANCY = False
from seahub.settings import SITE_ROOT
class BaseMiddleware(object): class BaseMiddleware(object):
""" """
@ -70,3 +75,21 @@ class InfobarMiddleware(object):
def process_response(self, request, response): def process_response(self, request, response):
return response return response
class ForcePasswdChangeMiddleware(object):
def _request_in_black_list(self, request):
path = request.path
black_list = (r'^%s$' % SITE_ROOT, r'home/.+', r'repo/.+',
r'[f|d]/[a-f][0-9]{10}', r'group/\d+', r'groups/',
r'share/', r'profile/', r'notification/list/')
for patt in black_list:
if re.search(patt, path) is not None:
return True
return False
def process_request(self, request):
if request.session.get('force_passwd_change', False):
if self._request_in_black_list(request):
return HttpResponseRedirect(reverse('auth_password_change'))

View File

@ -18,6 +18,9 @@ KEY_SUB_LIB = "sub_lib"
VAL_SUB_LIB_ENABLED = "1" VAL_SUB_LIB_ENABLED = "1"
VAL_SUB_LIB_DISABLED = "0" VAL_SUB_LIB_DISABLED = "0"
KEY_FORCE_PASSWD_CHANGE = "force_passwd_change"
VAL_FORCE_PASSWD_CHANGE = "1"
KEY_DEFAULT_REPO = "default_repo" KEY_DEFAULT_REPO = "default_repo"
class CryptoOptionNotSetError(Exception): class CryptoOptionNotSetError(Exception):
@ -43,6 +46,11 @@ class UserOptionsManager(models.Manager):
return user_option return user_option
def unset_user_option(self, username, k):
"""Remove user's option.
"""
super(UserOptionsManager, self).filter(email=username, option_key=k).delete()
def enable_server_crypto(self, username): def enable_server_crypto(self, username):
""" """
@ -185,7 +193,24 @@ class UserOptionsManager(models.Manager):
return user_option.option_val return user_option.option_val
except UserOptions.DoesNotExist: except UserOptions.DoesNotExist:
return None return None
def passwd_change_required(self, username):
"""Check whether user need to change password.
"""
try:
r = super(UserOptionsManager, self).get(
email=username, option_key=KEY_FORCE_PASSWD_CHANGE)
return r.option_val == VAL_FORCE_PASSWD_CHANGE
except UserOptions.DoesNotExist:
return False
def set_force_passwd_change(self, username):
return self.set_user_option(username, KEY_FORCE_PASSWD_CHANGE,
VAL_FORCE_PASSWD_CHANGE)
def unset_force_passwd_change(self, username):
return self.unset_user_option(username, KEY_FORCE_PASSWD_CHANGE)
class UserOptions(models.Model): class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True) email = LowerCaseCharField(max_length=255, db_index=True)
option_key = models.CharField(max_length=50) option_key = models.CharField(max_length=50)

View File

@ -117,7 +117,8 @@ MIDDLEWARE_CLASSES = (
'seahub.auth.middleware.AuthenticationMiddleware', 'seahub.auth.middleware.AuthenticationMiddleware',
'seahub.base.middleware.BaseMiddleware', 'seahub.base.middleware.BaseMiddleware',
'seahub.base.middleware.InfobarMiddleware', 'seahub.base.middleware.InfobarMiddleware',
'seahub.password_session.middleware.CheckPasswordHash' 'seahub.password_session.middleware.CheckPasswordHash',
'seahub.base.middleware.ForcePasswdChangeMiddleware',
) )
SITE_ROOT_URLCONF = 'seahub.urls' SITE_ROOT_URLCONF = 'seahub.urls'

View File

@ -7,6 +7,10 @@
{% endblock %} {% endblock %}
{% block main_panel %} {% block main_panel %}
{% if force_passwd_change %}
<p>{% trans "Please update your password before continue." %} </p>
{% endif %}
<div class="new-narrow-panel"> <div class="new-narrow-panel">
<h2 class="hd">{% trans "Password Modification" %}</h2> <h2 class="hd">{% trans "Password Modification" %}</h2>
<form action="" method="post" class="con">{% csrf_token %} <form action="" method="post" class="con">{% csrf_token %}

View File

@ -104,7 +104,7 @@ class Fixtures(Exam):
class BaseTestCase(TestCase, Fixtures): class BaseTestCase(TestCase, Fixtures):
def login_as(self, user): def login_as(self, user):
self.client.post( return self.client.post(
reverse('auth_login'), {'login': user.username, reverse('auth_login'), {'login': user.username,
'password': 'secret'} 'password': self.user_password}
) )

View File

@ -1,5 +1,7 @@
"""Copied from latest django/utils/http.py::is_safe_url """Copied from latest django/utils/http.py::is_safe_url
""" """
from __future__ import unicode_literals
import unicodedata import unicodedata
import urlparse import urlparse
import json import json

View File

@ -46,6 +46,7 @@ from seahub.views.ajax import (get_related_users_by_org_repo,
get_related_users_by_repo) get_related_users_by_repo)
from seahub.views import get_system_default_repo_id, gen_path_link from seahub.views import get_system_default_repo_id, gen_path_link
from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm
from seahub.options.models import UserOptions
from seahub.profile.models import Profile, DetailedProfile from seahub.profile.models import Profile, DetailedProfile
from seahub.signals import repo_deleted from seahub.signals import repo_deleted
from seahub.share.models import FileShare, UploadLinkShare from seahub.share.models import FileShare, UploadLinkShare
@ -1236,7 +1237,9 @@ def user_reset(request, email):
new_password = INIT_PASSWD new_password = INIT_PASSWD
user.set_password(new_password) user.set_password(new_password)
user.save() user.save()
clear_token(user.username) clear_token(user.username)
UserOptions.objects.set_force_passwd_change(user.username)
if IS_EMAIL_CONFIGURED: if IS_EMAIL_CONFIGURED:
if SEND_EMAIL_ON_RESETTING_USER_PASSWD: if SEND_EMAIL_ON_RESETTING_USER_PASSWD:
@ -1309,6 +1312,7 @@ def user_add(request):
if user: if user:
User.objects.update_role(email, role) User.objects.update_role(email, role)
UserOptions.objects.set_force_passwd_change(email)
if request.user.org: if request.user.org:
org_id = request.user.org.org_id org_id = request.user.org.org_id

View File

@ -0,0 +1,25 @@
from django.conf import settings
from django.core.urlresolvers import reverse
from seahub.options.models import UserOptions
from seahub.test_utils import BaseTestCase
class LoginTest(BaseTestCase):
def test_can_login(self):
resp = self.login_as(self.user)
self.assertEqual(302, resp.status_code)
self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL)
def test_force_passwd_change_when_login(self):
UserOptions.objects.set_force_passwd_change(self.user.username)
resp = self.login_as(self.user)
self.assertEqual(302, resp.status_code)
self.assertRedirects(resp, '/accounts/password/change/')
resp = self.client.get(reverse('auth_password_change'))
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.context['force_passwd_change'], True)

View File

@ -0,0 +1,26 @@
from django.core.urlresolvers import reverse
from seahub.test_utils import BaseTestCase
class PasswordChangeTest(BaseTestCase):
def test_can_render(self):
self.login_as(self.user)
resp = self.client.get(reverse('auth_password_change'))
self.assertEqual(200, resp.status_code)
self.assertContains(resp, 'Password Modification')
def test_can_change(self):
self.login_as(self.user)
resp = self.client.post(
reverse('auth_password_change'), {
'old_password': self.user_password,
'new_password1': '123',
'new_password2': '123',
}
)
self.assertEqual(302, resp.status_code)
self.assertRedirects(resp, reverse('auth_password_change_done'))

View File

@ -6,9 +6,11 @@ from django.http.cookie import parse_cookie
from tests.common.utils import randstring from tests.common.utils import randstring
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.utils.ms_excel import write_xls as real_write_xls from seahub.options.models import (UserOptions, KEY_FORCE_PASSWD_CHANGE,
from seahub.test_utils import BaseTestCase VAL_FORCE_PASSWD_CHANGE)
from seahub.share.models import FileShare from seahub.share.models import FileShare
from seahub.test_utils import BaseTestCase
from seahub.utils.ms_excel import write_xls as real_write_xls
from seaserv import ccnet_threaded_rpc, seafile_api from seaserv import ccnet_threaded_rpc, seafile_api
@ -50,6 +52,9 @@ class UserResetTest(BaseTestCase):
self.login_as(self.admin) self.login_as(self.admin)
def test_can_reset(self): def test_can_reset(self):
assert len(UserOptions.objects.filter(
email=self.user.username, option_key=KEY_FORCE_PASSWD_CHANGE)) == 0
old_passwd = self.user.enc_password old_passwd = self.user.enc_password
resp = self.client.post( resp = self.client.post(
reverse('user_reset', args=[self.user.email]) reverse('user_reset', args=[self.user.email])
@ -58,7 +63,9 @@ class UserResetTest(BaseTestCase):
u = User.objects.get(email=self.user.username) u = User.objects.get(email=self.user.username)
assert u.enc_password != old_passwd assert u.enc_password != old_passwd
assert UserOptions.objects.get(
email=self.user.username,
option_key=KEY_FORCE_PASSWD_CHANGE).option_val == VAL_FORCE_PASSWD_CHANGE
class BatchUserMakeAdminTest(BaseTestCase): class BatchUserMakeAdminTest(BaseTestCase):
def setUp(self): def setUp(self):
@ -98,6 +105,29 @@ class BatchUserMakeAdminTest(BaseTestCase):
# assert u.enc_password == old_passwd # assert u.enc_password == old_passwd
class UserAddTest(BaseTestCase):
def setUp(self):
self.new_user = 'new_user@test.com'
self.login_as(self.admin)
self.remove_user(self.new_user)
def test_can_add(self):
assert len(UserOptions.objects.filter(
email=self.new_user, option_key=KEY_FORCE_PASSWD_CHANGE)) == 0
resp = self.client.post(
reverse('user_add',), {
'email': self.new_user,
'password1': '123',
'password2': '123',
}, HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
self.assertEqual(200, resp.status_code)
assert UserOptions.objects.get(
email=self.new_user,
option_key=KEY_FORCE_PASSWD_CHANGE).option_val == VAL_FORCE_PASSWD_CHANGE
class UserRemoveTest(BaseTestCase): class UserRemoveTest(BaseTestCase):
def setUp(self): def setUp(self):
self.login_as(self.admin) self.login_as(self.admin)