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:
parent
808a5e186d
commit
054ef03687
@ -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'):
|
||||||
|
@ -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'))
|
||||||
|
@ -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)
|
||||||
|
@ -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'
|
||||||
|
@ -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 %}
|
||||||
|
@ -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}
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
25
tests/seahub/auth/test_login.py
Normal file
25
tests/seahub/auth/test_login.py
Normal 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)
|
26
tests/seahub/auth/test_password_change.py
Normal file
26
tests/seahub/auth/test_password_change.py
Normal 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'))
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user