1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-19 18:29:23 +00:00

[passwd strength] add STRONG password check(default not enabled) when user sign-up or change-password

This commit is contained in:
imwhatiam
2014-07-29 16:42:08 +08:00
parent c8cf757191
commit 90ba936514
11 changed files with 330 additions and 215 deletions

View File

@@ -7,10 +7,13 @@ from seahub.base.accounts import User
from seahub.auth import authenticate from seahub.auth import authenticate
from seahub.auth.tokens import default_token_generator from seahub.auth.tokens import default_token_generator
from seahub.utils import IS_EMAIL_CONFIGURED, send_html_email, \ 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 captcha.fields import CaptchaField
from seahub.settings import USER_STRONG_PASSWORD_REQUIRED, \
USER_PASSWORD_STRENGTH_LEVEL, USER_PASSWORD_MIN_LENGTH
class AuthenticationForm(forms.Form): class AuthenticationForm(forms.Form):
""" """
Base class for authenticating users. Extend this to get a form that accepts 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) 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): def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1') password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2') password2 = self.cleaned_data.get('new_password2')

View File

@@ -25,6 +25,8 @@ from seahub.auth.tokens import default_token_generator
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.utils import is_ldap_user from seahub.utils import is_ldap_user
from seahub.utils.ip import get_remote_ip 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 # Get an instance of a logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -352,8 +354,12 @@ def password_change(request, template_name='registration/password_change_form.ht
return HttpResponseRedirect(post_change_redirect) return HttpResponseRedirect(post_change_redirect)
else: else:
form = password_change_form(user=request.user) form = password_change_form(user=request.user)
return render_to_response(template_name, { return render_to_response(template_name, {
'form': form, 'form': form,
'min_len': USER_PASSWORD_MIN_LENGTH,
'strong_pwd_required': USER_STRONG_PASSWORD_REQUIRED,
'level': USER_PASSWORD_STRENGTH_LEVEL,
}, 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

@@ -13,12 +13,14 @@ from registration import signals
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, is_passwd_set from seaserv import ccnet_threaded_rpc, unset_repo_passwd, is_passwd_set
from seahub.profile.models import Profile, DetailedProfile 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: try:
from seahub.settings import CLOUD_MODE from seahub.settings import CLOUD_MODE
except ImportError: except ImportError:
CLOUD_MODE = False 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 UNUSABLE_PASSWORD = '!' # This will never be a valid hash
@@ -463,40 +465,14 @@ class RegistrationForm(forms.Form):
def clean_password1(self): def clean_password1(self):
if 'password1' in self.cleaned_data: if 'password1' in self.cleaned_data:
pwd = self.cleaned_data['password1'] pwd = self.cleaned_data['password1']
if len(pwd) < 6:
raise forms.ValidationError( if USER_STRONG_PASSWORD_REQUIRED is True:
_("Passwords must have at least 6 characters.")) if is_user_password_strong(pwd) is True:
return pwd
else: else:
num = 0 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))
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: else:
return 8; return pwd
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
def clean_password2(self): def clean_password2(self):
""" """

View File

@@ -207,6 +207,18 @@ SHOW_REPO_DOWNLOAD_BUTTON = False
# mininum length for password of encrypted library # mininum length for password of encrypted library
REPO_PASSWORD_MIN_LENGTH = 8 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. # Using server side crypto by default, otherwise, let user choose crypto method.
FORCE_SERVER_CRYPTO = True FORCE_SERVER_CRYPTO = True

View File

@@ -9,7 +9,11 @@
<label for="id_old_password">{% trans "Current Password: " %}</label> <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> <label for="id_new_password1">{% trans "New 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.new_password1 }} {{ form.new_password1.errors }} {{ form.new_password1 }} {{ form.new_password1.errors }}
<div id="pwd_strength"></div>
<label for="id_new_password2">{% trans "Confirm Password: " %}</label> <label for="id_new_password2">{% trans "Confirm Password: " %}</label>
{{ form.new_password2 }} {{ form.new_password2.errors }} {{ form.new_password2 }} {{ form.new_password2.errors }}
@@ -23,5 +27,46 @@
{% block extra_script %} {% block extra_script %}
<script type="text/javascript"> <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> </script>
{% endblock %} {% endblock %}

View File

@@ -16,7 +16,10 @@
<label for="id_email">{% trans "Email" %}</label> <label for="id_email">{% trans "Email" %}</label>
{{ form.email }} {{ form.email.errors }} {{ 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 }} {{ form.password1 }} {{ form.password1.errors }}
<div id="pwd_strength"></div> <div id="pwd_strength"></div>
<label for="id_password2">{% trans "Confirm Password" %}</label> <label for="id_password2">{% trans "Confirm Password" %}</label>
@@ -40,74 +43,18 @@
{% block extra_script %} {% block extra_script %}
<script type="text/javascript"> <script type="text/javascript">
{% include "snippets/password_strength_js.html" %}
$("#id_password1").keyup(function() { $("#id_password1").keyup(function() {
var level = getStrengthLevel($(this).val()); var pwd = $(this).val(),
level = getStrengthLevel(pwd);
if (pwd.length > 0) {
showStrength(level); showStrength(level);
};
}); });
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);
};
}
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(){ $('form').submit(function(){
var email = $.trim($('input[name="email"]').val()), var email = $.trim($('input[name="email"]').val()),
pwd1 = $.trim($('input[name="password1"]').val()), pwd1 = $.trim($('input[name="password1"]').val()),
@@ -130,14 +77,12 @@ $('form').submit(function(){
$('.error').removeClass('hide').html("{% trans "Passwords don't match" %}"); $('.error').removeClass('hide').html("{% trans "Passwords don't match" %}");
return false; return false;
} }
if (pwd1.length < 6) { {% if strong_pwd_required %}
$('.error').removeClass('hide').html("{% trans "Passwords must have at least 6 characters" %}"); if (level < {{level}}) {
return false; $('.error').removeClass('hide').html("{% blocktrans %}{{min_len}} characters or more, include {{level}} types or more of these: letters(case sensitive), numbers, and symbols{% endblocktrans %}");
}
if (level <= 2) {
$('.error').removeClass('hide').html("{% trans "Passwords contain at least 2 types: uppercase letters, lowercase letters, numbers, and symbols" %}");
return false; return false;
} }
{% endif %}
}); });
</script> </script>
{% endblock %} {% endblock %}

View 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;
}
}

View File

@@ -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_personal_repos_by_owner, get_group_repos, \
list_inner_pub_repos, CCNET_CONF_PATH, SERVICE_URL list_inner_pub_repos, CCNET_CONF_PATH, SERVICE_URL
import seahub.settings 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: try:
from seahub.settings import EVENTS_CONFIG_FILE from seahub.settings import EVENTS_CONFIG_FILE
except ImportError: except ImportError:
@@ -945,3 +946,51 @@ def user_traffic_over_limit(username):
month_traffic = stat['file_view'] + stat['file_download'] + stat['dir_download'] month_traffic = stat['file_view'] + stat['file_download'] + stat['dir_download']
return True if month_traffic >= traffic_limit else False 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

View File

@@ -9,6 +9,8 @@ from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from registration.backends import get_backend 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, def activate(request, backend,
template_name='registration/activate.html', 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}) form = form_class(initial={'email': src})
return render_to_response(template_name, return render_to_response(template_name,
{ 'form': form }, { 'form': form,
context_instance=context) 'min_len': USER_PASSWORD_MIN_LENGTH,
'strong_pwd_required': USER_STRONG_PASSWORD_REQUIRED,
'level': USER_PASSWORD_STRENGTH_LEVEL,
}, context_instance=context)