mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-20 02:48:51 +00:00
[passwd strength] add STRONG password check(default not enabled) when user sign-up or change-password
This commit is contained in:
@@ -7,10 +7,13 @@ from seahub.base.accounts import User
|
||||
from seahub.auth import authenticate
|
||||
from seahub.auth.tokens import default_token_generator
|
||||
from seahub.utils import IS_EMAIL_CONFIGURED, send_html_email, \
|
||||
is_valid_username, is_ldap_user
|
||||
is_valid_username, is_ldap_user, is_user_password_strong
|
||||
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
from seahub.settings import USER_STRONG_PASSWORD_REQUIRED, \
|
||||
USER_PASSWORD_STRENGTH_LEVEL, USER_PASSWORD_MIN_LENGTH
|
||||
|
||||
class AuthenticationForm(forms.Form):
|
||||
"""
|
||||
Base class for authenticating users. Extend this to get a form that accepts
|
||||
@@ -124,6 +127,18 @@ class SetPasswordForm(forms.Form):
|
||||
|
||||
super(SetPasswordForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_new_password1(self):
|
||||
if 'new_password1' in self.cleaned_data:
|
||||
pwd = self.cleaned_data['new_password1']
|
||||
|
||||
if USER_STRONG_PASSWORD_REQUIRED is True:
|
||||
if is_user_password_strong(pwd) is True:
|
||||
return pwd
|
||||
else:
|
||||
raise forms.ValidationError(_("%s characters or more, include %s types or more of these: letters(case sensitive), numbers, and symbols") % (USER_PASSWORD_MIN_LENGTH, USER_PASSWORD_STRENGTH_LEVEL))
|
||||
else:
|
||||
return pwd
|
||||
|
||||
def clean_new_password2(self):
|
||||
password1 = self.cleaned_data.get('new_password1')
|
||||
password2 = self.cleaned_data.get('new_password2')
|
||||
|
@@ -25,6 +25,8 @@ from seahub.auth.tokens import default_token_generator
|
||||
from seahub.base.accounts import User
|
||||
from seahub.utils import is_ldap_user
|
||||
from seahub.utils.ip import get_remote_ip
|
||||
from seahub.settings import USER_PASSWORD_MIN_LENGTH, \
|
||||
USER_STRONG_PASSWORD_REQUIRED, USER_PASSWORD_STRENGTH_LEVEL
|
||||
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -352,8 +354,12 @@ def password_change(request, template_name='registration/password_change_form.ht
|
||||
return HttpResponseRedirect(post_change_redirect)
|
||||
else:
|
||||
form = password_change_form(user=request.user)
|
||||
|
||||
return render_to_response(template_name, {
|
||||
'form': form,
|
||||
'min_len': USER_PASSWORD_MIN_LENGTH,
|
||||
'strong_pwd_required': USER_STRONG_PASSWORD_REQUIRED,
|
||||
'level': USER_PASSWORD_STRENGTH_LEVEL,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
def password_change_done(request, template_name='registration/password_change_done.html'):
|
||||
|
@@ -13,12 +13,14 @@ from registration import signals
|
||||
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, is_passwd_set
|
||||
|
||||
from seahub.profile.models import Profile, DetailedProfile
|
||||
from seahub.utils import is_valid_username
|
||||
from seahub.utils import is_valid_username, is_user_password_strong
|
||||
try:
|
||||
from seahub.settings import CLOUD_MODE
|
||||
except ImportError:
|
||||
CLOUD_MODE = False
|
||||
|
||||
from seahub.settings import USER_STRONG_PASSWORD_REQUIRED, \
|
||||
USER_PASSWORD_MIN_LENGTH, USER_PASSWORD_STRENGTH_LEVEL
|
||||
|
||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
||||
|
||||
@@ -463,40 +465,14 @@ class RegistrationForm(forms.Form):
|
||||
def clean_password1(self):
|
||||
if 'password1' in self.cleaned_data:
|
||||
pwd = self.cleaned_data['password1']
|
||||
if len(pwd) < 6:
|
||||
raise forms.ValidationError(
|
||||
_("Passwords must have at least 6 characters."))
|
||||
|
||||
if USER_STRONG_PASSWORD_REQUIRED is True:
|
||||
if is_user_password_strong(pwd) is True:
|
||||
return pwd
|
||||
else:
|
||||
raise forms.ValidationError(_("%s characters or more, include %s types or more of these: letters(case sensitive), numbers, and symbols") % (USER_PASSWORD_MIN_LENGTH, USER_PASSWORD_STRENGTH_LEVEL))
|
||||
else:
|
||||
num = 0
|
||||
for letter in pwd:
|
||||
# get ascii dec
|
||||
# bitwise OR
|
||||
num |= self.get_char_mode(ord(letter))
|
||||
level = self.caculate_bitwise(num)
|
||||
if level == 1:
|
||||
raise forms.ValidationError(_("Passwords must contain at least 2 types: uppercase letters, lowercase letters, numbers, and symbols"))
|
||||
|
||||
return self.cleaned_data['password1']
|
||||
|
||||
def get_char_mode(self, n):
|
||||
if (n >= 48 and n <= 57): #nums
|
||||
return 1;
|
||||
if (n >= 65 and n <= 90): #uppers
|
||||
return 2;
|
||||
if (n >= 97 and n <= 122): #lowers
|
||||
return 4;
|
||||
else:
|
||||
return 8;
|
||||
|
||||
def caculate_bitwise(self, num):
|
||||
level = 0
|
||||
for i in range(4):
|
||||
# bitwise AND
|
||||
if (num&1):
|
||||
level += 1
|
||||
# Right logical shift
|
||||
num = num >> 1
|
||||
return level
|
||||
return pwd
|
||||
|
||||
def clean_password2(self):
|
||||
"""
|
||||
|
@@ -207,6 +207,18 @@ SHOW_REPO_DOWNLOAD_BUTTON = False
|
||||
# mininum length for password of encrypted library
|
||||
REPO_PASSWORD_MIN_LENGTH = 8
|
||||
|
||||
# mininum length for user's password
|
||||
USER_PASSWORD_MIN_LENGTH = 6
|
||||
|
||||
# LEVEL based on four types of input:
|
||||
# num, upper letter, lower letter, other symbols
|
||||
# '3' means password must have at least 3 types of the above.
|
||||
USER_PASSWORD_STRENGTH_LEVEL = 3
|
||||
|
||||
# default False, only check USER_PASSWORD_MIN_LENGTH
|
||||
# when True, check password strength level, STRONG(or above) is allowed
|
||||
USER_STRONG_PASSWORD_REQUIRED = False
|
||||
|
||||
# Using server side crypto by default, otherwise, let user choose crypto method.
|
||||
FORCE_SERVER_CRYPTO = True
|
||||
|
||||
|
@@ -7,9 +7,13 @@
|
||||
<h2 class="hd">{% trans "Password Modification" %}</h2>
|
||||
<form action="" method="post" class="con">{% csrf_token %}
|
||||
<label for="id_old_password">{% trans "Current Password: " %}</label>
|
||||
{{ form.old_password }} {{ form.old_password.errors }}
|
||||
{{ form.old_password }} {{ form.old_password.errors }}
|
||||
<label for="id_new_password1">{% trans "New Password: " %}</label>
|
||||
{{ form.new_password1 }} {{ form.new_password1.errors }}
|
||||
{% if strong_pwd_required %}
|
||||
<span class="icon-question-sign" title="{% blocktrans %}{{min_len}} characters or more, include {{level}} types or more of these: letters(case sensitive), numbers, and symbols"{% endblocktrans%}></span>
|
||||
{% endif %}
|
||||
{{ form.new_password1 }} {{ form.new_password1.errors }}
|
||||
<div id="pwd_strength"></div>
|
||||
<label for="id_new_password2">{% trans "Confirm Password: " %}</label>
|
||||
{{ form.new_password2 }} {{ form.new_password2.errors }}
|
||||
|
||||
@@ -22,6 +26,47 @@
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
$('[type="password"]').addClass('input');
|
||||
$('[type="password"]').addClass('input');
|
||||
|
||||
{% include "snippets/password_strength_js.html" %}
|
||||
|
||||
$("#id_new_password1").keyup(function() {
|
||||
var pwd = $(this).val(),
|
||||
level = getStrengthLevel(pwd);
|
||||
|
||||
if (pwd.length > 0) {
|
||||
showStrength(level);
|
||||
};
|
||||
});
|
||||
|
||||
$('form').submit(function(){
|
||||
var old_pwd = $.trim($('input[name="old_password"]').val()),
|
||||
pwd1 = $.trim($('input[name="new_password1"]').val()),
|
||||
pwd2 = $.trim($('input[name="new_password2"]').val()),
|
||||
level = getStrengthLevel(pwd1);
|
||||
|
||||
if (!old_pwd) {
|
||||
$('.error').removeClass('hide').html("{% trans "Current password cannot be blank" %}");
|
||||
return false;
|
||||
}
|
||||
if (!pwd1) {
|
||||
$('.error').removeClass('hide').html("{% trans "Password cannot be blank" %}");
|
||||
return false;
|
||||
}
|
||||
if (!pwd2) {
|
||||
$('.error').removeClass('hide').html("{% trans "Please enter the password again" %}");
|
||||
return false;
|
||||
}
|
||||
if (pwd1 != pwd2) {
|
||||
$('.error').removeClass('hide').html("{% trans "Passwords don't match" %}");
|
||||
return false;
|
||||
}
|
||||
{% if strong_pwd_required %}
|
||||
if (level < {{level}}) {
|
||||
$('.error').removeClass('hide').html("{% blocktrans %}{{min_len}} characters or more, include {{level}} types or more of these: letters(case sensitive), numbers, and symbols{% endblocktrans %}");
|
||||
return false;
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -16,7 +16,10 @@
|
||||
|
||||
<label for="id_email">{% trans "Email" %}</label>
|
||||
{{ form.email }} {{ form.email.errors }}
|
||||
<label for="id_password1">{% trans "Password" %}</label> <span class="icon-question-sign" title="{% trans "6 characters or more, include 2 types or more of these: letters(case sensitive), numbers, and symbols" %}"></span>
|
||||
<label for="id_password1">{% trans "Password" %}</label>
|
||||
{% if strong_pwd_required %}
|
||||
<span class="icon-question-sign" title="{% blocktrans %}{{min_len}} characters or more, include {{level}} types or more of these: letters(case sensitive), numbers, and symbols"{% endblocktrans%}></span>
|
||||
{% endif %}
|
||||
{{ form.password1 }} {{ form.password1.errors }}
|
||||
<div id="pwd_strength"></div>
|
||||
<label for="id_password2">{% trans "Confirm Password" %}</label>
|
||||
@@ -40,73 +43,17 @@
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
|
||||
{% include "snippets/password_strength_js.html" %}
|
||||
|
||||
$("#id_password1").keyup(function() {
|
||||
var level = getStrengthLevel($(this).val());
|
||||
showStrength(level);
|
||||
});
|
||||
var pwd = $(this).val(),
|
||||
level = getStrengthLevel(pwd);
|
||||
|
||||
function getStrengthLevel(pwd) {
|
||||
var num = 0,
|
||||
min_passwd_len = 6;
|
||||
|
||||
if (pwd.length < min_passwd_len) {
|
||||
return 0;
|
||||
} else {
|
||||
for (var i = 0; i < pwd.length; i++) {
|
||||
// return the unicode
|
||||
// bitwise OR
|
||||
num |= getCharMode(pwd.charCodeAt(i));
|
||||
};
|
||||
return calculateBitwise(num);
|
||||
if (pwd.length > 0) {
|
||||
showStrength(level);
|
||||
};
|
||||
}
|
||||
|
||||
function getCharMode(n) {
|
||||
if (n >= 48 && n <= 57) // nums
|
||||
return 1;
|
||||
if (n >= 65 && n <= 90) // uppers
|
||||
return 2;
|
||||
if (n >= 97 && n <= 122) // lowers
|
||||
return 4;
|
||||
else
|
||||
return 8;
|
||||
}
|
||||
|
||||
function calculateBitwise(num) {
|
||||
var level = 0;
|
||||
for (var i = 0; i < 4; i++){
|
||||
// bitwise AND
|
||||
if (num&1) level++;
|
||||
// Right logical shift
|
||||
num>>>=1;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
function showStrength(level) {
|
||||
var strength_ct = $("#pwd_strength");
|
||||
var strength = [
|
||||
"{% trans "too weak" %}",
|
||||
"{% trans "weak" %}",
|
||||
"{% trans "medium" %}",
|
||||
"{% trans "strong" %}"
|
||||
];
|
||||
switch (level) {
|
||||
case 0:
|
||||
strength_ct.html(strength[0]).css("color", '#F00000');
|
||||
break;
|
||||
case 1:
|
||||
strength_ct.html('<span style="background:#db4747;">' + strength[1] + '</span><span>' + strength[2] + '</span><span>' + strength[3] + '</span>');
|
||||
break;
|
||||
case 2:
|
||||
strength_ct.html('<span>' + strength[1] + '</span><span style="background:#fdd64d;">' + strength[2] + '</span><span>' + strength[3] + '</span>');
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
strength_ct.html('<span>' + strength[1] + '</span><span>' + strength[2] + '</span><span style="background:#4aa323;">' + strength[3] + '</span>');
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('form').submit(function(){
|
||||
var email = $.trim($('input[name="email"]').val()),
|
||||
@@ -130,14 +77,12 @@ $('form').submit(function(){
|
||||
$('.error').removeClass('hide').html("{% trans "Passwords don't match" %}");
|
||||
return false;
|
||||
}
|
||||
if (pwd1.length < 6) {
|
||||
$('.error').removeClass('hide').html("{% trans "Passwords must have at least 6 characters" %}");
|
||||
return false;
|
||||
}
|
||||
if (level <= 2) {
|
||||
$('.error').removeClass('hide').html("{% trans "Passwords contain at least 2 types: uppercase letters, lowercase letters, numbers, and symbols" %}");
|
||||
return false;
|
||||
}
|
||||
{% if strong_pwd_required %}
|
||||
if (level < {{level}}) {
|
||||
$('.error').removeClass('hide').html("{% blocktrans %}{{min_len}} characters or more, include {{level}} types or more of these: letters(case sensitive), numbers, and symbols{% endblocktrans %}");
|
||||
return false;
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
62
seahub/templates/snippets/password_strength_js.html
Normal file
62
seahub/templates/snippets/password_strength_js.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{% load i18n %}
|
||||
function getStrengthLevel(pwd) {
|
||||
var num = 0;
|
||||
|
||||
if (pwd.length < {{min_len}}) {
|
||||
return 0;
|
||||
} else {
|
||||
for (var i = 0; i < pwd.length; i++) {
|
||||
// return the unicode
|
||||
// bitwise OR
|
||||
num |= getCharMode(pwd.charCodeAt(i));
|
||||
};
|
||||
return calculateBitwise(num);
|
||||
};
|
||||
}
|
||||
|
||||
function getCharMode(n) {
|
||||
if (n >= 48 && n <= 57) // nums
|
||||
return 1;
|
||||
if (n >= 65 && n <= 90) // uppers
|
||||
return 2;
|
||||
if (n >= 97 && n <= 122) // lowers
|
||||
return 4;
|
||||
else
|
||||
return 8;
|
||||
}
|
||||
|
||||
function calculateBitwise(num) {
|
||||
var level = 0;
|
||||
for (var i = 0; i < 4; i++){
|
||||
// bitwise AND
|
||||
if (num&1) level++;
|
||||
// Right logical shift
|
||||
num>>>=1;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
function showStrength(level) {
|
||||
var strength_ct = $("#pwd_strength");
|
||||
var strength = [
|
||||
"{% trans "too weak" %}",
|
||||
"{% trans "weak" %}",
|
||||
"{% trans "medium" %}",
|
||||
"{% trans "strong" %}"
|
||||
];
|
||||
switch (level) {
|
||||
case 0:
|
||||
strength_ct.html(strength[0]).css("color", '#F00000');
|
||||
break;
|
||||
case 1:
|
||||
strength_ct.html('<span style="background:#db4747;">' + strength[1] + '</span><span>' + strength[2] + '</span><span>' + strength[3] + '</span>');
|
||||
break;
|
||||
case 2:
|
||||
strength_ct.html('<span>' + strength[1] + '</span><span style="background:#fdd64d;">' + strength[2] + '</span><span>' + strength[3] + '</span>');
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
strength_ct.html('<span>' + strength[1] + '</span><span>' + strength[2] + '</span><span style="background:#4aa323;">' + strength[3] + '</span>');
|
||||
break;
|
||||
}
|
||||
}
|
@@ -29,7 +29,8 @@ from seaserv import seafserv_rpc, seafserv_threaded_rpc, get_repo, get_commits,\
|
||||
list_personal_repos_by_owner, get_group_repos, \
|
||||
list_inner_pub_repos, CCNET_CONF_PATH, SERVICE_URL
|
||||
import seahub.settings
|
||||
from seahub.settings import SITE_NAME, MEDIA_URL, LOGO_PATH
|
||||
from seahub.settings import SITE_NAME, MEDIA_URL, LOGO_PATH, \
|
||||
USER_PASSWORD_STRENGTH_LEVEL, USER_PASSWORD_MIN_LENGTH
|
||||
try:
|
||||
from seahub.settings import EVENTS_CONFIG_FILE
|
||||
except ImportError:
|
||||
@@ -945,3 +946,51 @@ def user_traffic_over_limit(username):
|
||||
|
||||
month_traffic = stat['file_view'] + stat['file_download'] + stat['dir_download']
|
||||
return True if month_traffic >= traffic_limit else False
|
||||
|
||||
def is_user_password_strong(password):
|
||||
"""Return ``True`` if user's password is STRONG, otherwise ``False``.
|
||||
STRONG means password has at least USER_PASSWORD_STRENGTH_LEVEL(3) types of the bellow:
|
||||
num, upper letter, lower letter, other symbols
|
||||
"""
|
||||
|
||||
if len(password) < USER_PASSWORD_MIN_LENGTH:
|
||||
return False
|
||||
else:
|
||||
num = 0
|
||||
for letter in password:
|
||||
# get ascii dec
|
||||
# bitwise OR
|
||||
num |= get_char_mode(ord(letter))
|
||||
|
||||
if calculate_bitwise(num) < USER_PASSWORD_STRENGTH_LEVEL:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_char_mode(n):
|
||||
"""Return different num according to the type of given letter:
|
||||
'1': num,
|
||||
'2': upper_letter,
|
||||
'4': lower_letter,
|
||||
'8': other symbols
|
||||
"""
|
||||
if (n >= 48 and n <= 57): #nums
|
||||
return 1;
|
||||
if (n >= 65 and n <= 90): #uppers
|
||||
return 2;
|
||||
if (n >= 97 and n <= 122): #lowers
|
||||
return 4;
|
||||
else:
|
||||
return 8;
|
||||
|
||||
def calculate_bitwise(num):
|
||||
"""Return different level according to the given num:
|
||||
"""
|
||||
level = 0
|
||||
for i in range(4):
|
||||
# bitwise AND
|
||||
if (num&1):
|
||||
level += 1
|
||||
# Right logical shift
|
||||
num = num >> 1
|
||||
return level
|
||||
|
@@ -9,6 +9,8 @@ from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
|
||||
from registration.backends import get_backend
|
||||
from seahub.settings import USER_PASSWORD_MIN_LENGTH, \
|
||||
USER_STRONG_PASSWORD_REQUIRED, USER_PASSWORD_STRENGTH_LEVEL
|
||||
|
||||
def activate(request, backend,
|
||||
template_name='registration/activate.html',
|
||||
@@ -204,5 +206,8 @@ def register(request, backend, success_url=None, form_class=None,
|
||||
form = form_class(initial={'email': src})
|
||||
|
||||
return render_to_response(template_name,
|
||||
{ 'form': form },
|
||||
context_instance=context)
|
||||
{ 'form': form,
|
||||
'min_len': USER_PASSWORD_MIN_LENGTH,
|
||||
'strong_pwd_required': USER_STRONG_PASSWORD_REQUIRED,
|
||||
'level': USER_PASSWORD_STRENGTH_LEVEL,
|
||||
}, context_instance=context)
|
||||
|
Reference in New Issue
Block a user