diff --git a/seahub/auth/forms.py b/seahub/auth/forms.py index c9ee70c631..7890dd6ea9 100644 --- a/seahub/auth/forms.py +++ b/seahub/auth/forms.py @@ -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 @@ -64,7 +67,7 @@ class AuthenticationForm(forms.Form): class CaptchaAuthenticationForm(AuthenticationForm): captcha = CaptchaField() - + class PasswordResetForm(forms.Form): email = forms.EmailField(label=_("E-mail"), max_length=255) @@ -74,7 +77,7 @@ class PasswordResetForm(forms.Form): """ if not IS_EMAIL_CONFIGURED: raise forms.ValidationError(_(u'Failed to send email, email service is not properly configured, please contact administrator.')) - + email = self.cleaned_data["email"].lower().strip() # TODO: add filter method to UserManager @@ -121,9 +124,21 @@ class SetPasswordForm(forms.Form): def __init__(self, user, *args, **kwargs): self.user = user - + 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') diff --git a/seahub/auth/views.py b/seahub/auth/views.py index cb3695f12d..d0d6cb0cb4 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -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__) @@ -35,9 +37,9 @@ def log_user_in(request, user, redirect_to): # Light security check -- make sure redirect_to isn't garbage. if not redirect_to or ' ' in redirect_to: redirect_to = settings.LOGIN_REDIRECT_URL - - # Heavier security check -- redirects to http://example.com should - # not be allowed, but things like /view/?param=http://example.com + + # Heavier security check -- redirects to http://example.com should + # not be allowed, but things like /view/?param=http://example.com # should be allowed. This regex checks if there is a '//' *before* a # question mark. elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to): @@ -103,7 +105,7 @@ def _incr_login_faied_attempts(username=None, ip=None): def _clear_login_failed_attempts(request): """Clear login failed attempts records. - + Arguments: - `request`: """ @@ -167,9 +169,9 @@ def login(request, template_name='registration/login.html', form = CaptchaAuthenticationForm(request) else: form = authentication_form(request) - + request.session.set_test_cookie() - + if Site._meta.installed: current_site = Site.objects.get_current() else: @@ -213,7 +215,7 @@ def login_simple_check(request): user = User.objects.get(email=username) except User.DoesNotExist: raise Http404 - + for backend in get_backends(): user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) @@ -223,7 +225,7 @@ def login_simple_check(request): else: raise Http404 - + def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME): "Logs out the user and displays 'You are logged out' message." from seahub.auth import logout @@ -255,7 +257,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N # 4 views for password reset: # - password_reset sends the mail # - password_reset_done shows a success message for the above -# - password_reset_confirm checks the link the user clicked and +# - password_reset_confirm checks the link the user clicked and # prompts for a new password # - password_reset_complete shows a success message for the above @@ -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'): diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py index 65e1c4e5c2..1dba78c69c 100644 --- a/seahub/base/accounts.py +++ b/seahub/base/accounts.py @@ -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): """ diff --git a/seahub/settings.py b/seahub/settings.py index c098664a32..246e3804b0 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -104,10 +104,10 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', 'seahub.auth.middleware.AuthenticationMiddleware', - 'seahub.base.middleware.BaseMiddleware', + 'seahub.base.middleware.BaseMiddleware', 'seahub.base.middleware.InfobarMiddleware', ) @@ -161,7 +161,7 @@ LOCALE_PATHS = ( TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', + 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'djblets.util.context_processors.siteRoot', 'django.core.context_processors.request', @@ -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 @@ -219,7 +231,7 @@ OFFICE_PREVIEW_MAX_SIZE = 2 * 1024 * 1024 USE_PDFJS = True FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5'] FILE_ENCODING_TRY_LIST = ['utf-8', 'gbk'] -HIGHLIGHT_KEYWORD = False # If True, highlight the keywords in the file when the visit is via clicking a link in 'search result' page. +HIGHLIGHT_KEYWORD = False # If True, highlight the keywords in the file when the visit is via clicking a link in 'search result' page. # Common settings(file extension, storage) for avatar and group avatar. AVATAR_FILE_STORAGE = '' # Replace with 'seahub.base.database_storage.DatabaseStorage' if save avatar files to database @@ -346,7 +358,7 @@ LOGGING = { 'filename': os.path.join(LOG_DIR, 'seahub.log'), 'maxBytes': 1024*1024*10, # 10 MB 'formatter':'standard', - }, + }, 'request_handler': { 'level':'WARN', 'class':'logging.handlers.RotatingFileHandler', @@ -452,7 +464,7 @@ def load_local_settings(module): elif re.search('^[A-Z]', attr): globals()[attr] = getattr(module, attr) - + # Load seahub_extra_settings.py try: from seahub_extra import seahub_extra_settings @@ -478,7 +490,7 @@ try: except ImportError: pass else: - # In server release, sqlite3 db file is /seahub.db + # In server release, sqlite3 db file is /seahub.db DATABASES['default']['NAME'] = os.path.join(install_topdir, 'seahub.db') if 'win32' not in sys.platform: # In server release, gunicorn is used to deploy seahub diff --git a/seahub/templates/registration/password_change_form.html b/seahub/templates/registration/password_change_form.html index 4a6b49d315..3e348b3ad6 100644 --- a/seahub/templates/registration/password_change_form.html +++ b/seahub/templates/registration/password_change_form.html @@ -7,9 +7,13 @@

{% trans "Password Modification" %}

{% csrf_token %} - {{ form.old_password }} {{ form.old_password.errors }} + {{ form.old_password }} {{ form.old_password.errors }} - {{ form.new_password1 }} {{ form.new_password1.errors }} + {% if strong_pwd_required %} + + {% endif %} + {{ form.new_password1 }} {{ form.new_password1.errors }} +
{{ form.new_password2 }} {{ form.new_password2.errors }} @@ -22,6 +26,47 @@ {% block extra_script %} {% endblock %} diff --git a/seahub/templates/registration/registration_form.html b/seahub/templates/registration/registration_form.html index 442362c1fb..6aaff8b2e3 100644 --- a/seahub/templates/registration/registration_form.html +++ b/seahub/templates/registration/registration_form.html @@ -16,7 +16,10 @@ {{ form.email }} {{ form.email.errors }} - + + {% if strong_pwd_required %} + + {% endif %} {{ form.password1 }} {{ form.password1.errors }}
@@ -40,73 +43,17 @@ {% block extra_script %} {% endblock %} diff --git a/seahub/templates/snippets/password_strength_js.html b/seahub/templates/snippets/password_strength_js.html new file mode 100644 index 0000000000..fd69e68a7a --- /dev/null +++ b/seahub/templates/snippets/password_strength_js.html @@ -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('' + strength[1] + '' + strength[2] + '' + strength[3] + ''); + break; + case 2: + strength_ct.html('' + strength[1] + '' + strength[2] + '' + strength[3] + ''); + break; + case 3: + case 4: + strength_ct.html('' + strength[1] + '' + strength[2] + '' + strength[3] + ''); + break; + } +} diff --git a/seahub/templates/snippets/repo_create_js.html b/seahub/templates/snippets/repo_create_js.html index a852d34014..36bec4f66f 100644 --- a/seahub/templates/snippets/repo_create_js.html +++ b/seahub/templates/snippets/repo_create_js.html @@ -15,7 +15,7 @@ $(function() { $('#encrypt-switch').click(function () { var form = $('#repo-create-form'), pwd_input = $('input[type="password"]', form); - + if ($(this).attr('checked')) { pwd_input.attr('disabled', false).removeClass('input-disabled'); } else { @@ -23,7 +23,7 @@ $('#encrypt-switch').click(function () { } }); $('#repo-create-form').submit(function() { - var form = $(this), + var form = $(this), form_id = form.attr('id'), name = $('[name="repo_name"]', form).val(), desc = $('[name="repo_desc"]', form).val(), diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 5e1e63b4cf..c8f18e3321 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -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: @@ -78,7 +79,7 @@ PREVIEW_FILEEXT = { def gen_fileext_type_map(): """ Generate previewed file extension and file type relation map. - + """ d = {} for filetype in PREVIEW_FILEEXT.keys(): @@ -156,7 +157,7 @@ def gen_token(max_length=5): Generate a random token. """ - + return uuid.uuid4().hex[:max_length] def normalize_cache_key(value, prefix=None): @@ -165,7 +166,7 @@ def normalize_cache_key(value, prefix=None): """ key = value if prefix is None else prefix + value return urlquote(key) - + def get_repo_last_modify(repo): """ Get last modification time for a repo. @@ -332,7 +333,7 @@ def get_file_revision_id_size(repo_id, commit_id, path): def new_merge_with_no_conflict(commit): """Check whether a commit is a new merge, and no conflict. - + Arguments: - `commit`: """ @@ -358,7 +359,7 @@ def get_commit_before_new_merge(commit): commit = p1 if p1.ctime > p2.ctime else p2 assert new_merge_with_no_conflict(commit) is False - + return commit def gen_inner_file_get_url(token, filename): @@ -386,7 +387,7 @@ def get_max_upload_file_size(): Returns ``None`` if this value is not set. """ return seaserv.MAX_UPLOAD_FILE_SIZE - + def gen_block_get_url(token, blkid): """ Generate fileserver block url. @@ -425,7 +426,7 @@ def string2list(string): continue s.add(e) return [ x for x in s ] - + # def get_cur_ctx(request): # ctx_dict = request.session.get('current_context', { # 'base_template': 'myhome_base.html', @@ -439,12 +440,12 @@ def string2list(string): def is_org_context(request): """An organization context is a virtual private Seafile instance on cloud service. - + Arguments: - `request`: """ return request.cloud_mode and request.user.org is not None - + # def check_and_get_org_by_repo(repo_id, user): # """ # Check whether repo is org repo, get org info if it is, and set @@ -460,7 +461,7 @@ def is_org_context(request): # else: # org = None # base_template = 'myhome_base.html' - + # return org, base_template def check_and_get_org_by_group(group_id, user): @@ -478,9 +479,9 @@ def check_and_get_org_by_group(group_id, user): else: org = None base_template = 'myhome_base.html' - + return org, base_template - + # events related if EVENTS_CONFIG_FILE: import seafevents @@ -510,7 +511,7 @@ if EVENTS_CONFIG_FILE: events = _get_events_inner(ev_session, username, next_start, count) if not events: break - + for e1 in events: duplicate = False for e2 in valid_events: @@ -519,13 +520,13 @@ if EVENTS_CONFIG_FILE: new_merge = False if hasattr(e1, 'commit') and new_merge_with_no_conflict(e1.commit): new_merge = True - + if not duplicate and not new_merge: valid_events.append(e1) total_used = total_used + 1 if len(valid_events) == count: break - + if len(valid_events) == count: break next_start = next_start + len(events) @@ -569,14 +570,14 @@ if EVENTS_CONFIG_FILE: break if len(valid_events) == limit: - break + break next_start = next_start + len(valid_events) return valid_events def get_user_events(username, start, count): """Return user events list and a new start. - + For example: ``get_user_events('foo@example.com', 0, 10)`` returns the first 10 events. @@ -584,7 +585,7 @@ if EVENTS_CONFIG_FILE: 15th events. """ return _get_events(username, start, count) - + def get_org_user_events(org_id, username, start, count): return _get_events(username, start, count, org_id=org_id) @@ -600,7 +601,7 @@ def calc_file_path_hash(path, bits=12): path = path.encode('UTF-8') path_hash = hashlib.md5(urllib2.quote(path)).hexdigest()[:bits] - + return path_hash def get_service_url(): @@ -616,7 +617,7 @@ def get_server_id(): def get_site_scheme_and_netloc(): """Return a string contains site scheme and network location part from service url. - + For example: >>> get_site_scheme_and_netloc("https://example.com:8000/seafile/") https://example.com:8000 @@ -629,7 +630,7 @@ def send_html_email(subject, con_template, con_context, from_email, to_email): """Send HTML email """ base_context = { - 'url_base': get_site_scheme_and_netloc(), + 'url_base': get_site_scheme_and_netloc(), 'site_name': SITE_NAME, 'media_url': MEDIA_URL, 'logo_path': LOGO_PATH, @@ -639,7 +640,7 @@ def send_html_email(subject, con_template, con_context, from_email, to_email): msg = EmailMessage(subject, t.render(Context(con_context)), from_email, to_email) msg.content_subtype = "html" msg.send() - + def gen_dir_share_link(token): """Generate directory share link. """ @@ -727,7 +728,7 @@ def convert_cmmt_desc_link(commit): """ repo_id = commit.repo_id cmmt_id = commit.id - conv_link_url = reverse('convert_cmmt_desc_link') + conv_link_url = reverse('convert_cmmt_desc_link') def link_repl(matchobj): op = matchobj.group(1) @@ -776,7 +777,7 @@ def api_convert_desc_link(e): if file_or_dir not in d.name: # skip to next diff_result if file/folder user clicked does not # match the diff_result - continue + continue if d.status == 'add' or d.status == 'mod': e.link = "api://repo/%s/files/?p=/%s" % (repo_id, d.name) @@ -820,7 +821,7 @@ if EVENTS_CONFIG_FILE: return seafevents.get_office_converter_limit(config) HAS_OFFICE_CONVERTER = check_office_converter_enabled() - + if HAS_OFFICE_CONVERTER: OFFICE_HTML_DIR = get_office_converter_html_dir() @@ -849,7 +850,7 @@ if HAS_OFFICE_CONVERTER: rpc = _get_office_converter_rpc() return rpc.query_file_pages(file_id) - def get_converted_html_detail(file_id): + def get_converted_html_detail(file_id): d = {} outline_file = os.path.join(OFFICE_HTML_DIR, file_id, 'file.outline') @@ -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 diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index f2872933b9..282329dc67 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -79,7 +79,7 @@ def root(request): def validate_owner(request, repo_id): """ Check whether user in the request owns the repo. - + """ ret = is_repo_owner(request.user.username, repo_id) @@ -131,7 +131,7 @@ def get_system_default_repo_id(): def check_repo_access_permission(repo_id, user): """Check repo access permission of a user, always return 'rw' when repo is system repo and user is admin. - + Arguments: - `repo_id`: - `user`: @@ -140,13 +140,13 @@ def check_repo_access_permission(repo_id, user): return 'rw' else: return seafile_api.check_repo_access_permission(repo_id, user.username) - + def get_file_access_permission(repo_id, path, username): """Check user has permission to view the file. 1. check whether this file is private shared. 2. if failed, check whether the parent of this directory is private shared. """ - + pfs = PrivateFileDirShare.objects.get_private_share_in_file(username, repo_id, path) if pfs is None: @@ -157,11 +157,11 @@ def get_file_access_permission(repo_id, path, username): return None else: return pfs.permission - + def gen_path_link(path, repo_name): """ Generate navigate paths and links in repo page. - + """ if path and path[-1] != '/': path += '/' @@ -178,9 +178,9 @@ def gen_path_link(path, repo_name): if repo_name: paths.insert(0, repo_name) links.insert(0, '/') - + zipped = zip(paths, links) - + return zipped def get_repo_dirents(request, repo, commit, path, offset=-1, limit=-1): @@ -584,7 +584,7 @@ def repo_change_passwd(request, repo_id): {'repo_name': repo.name}) return HttpResponse(json.dumps({'success': True}), content_type=content_type) - + def upload_error_msg (code): err_msg = _(u'Internal Server Error') if (code == 0): @@ -644,7 +644,7 @@ def update_file_error(request, repo_id): 'zipped': zipped, 'err_msg': err_msg, }, context_instance=RequestContext(request)) - + @login_required def repo_history(request, repo_id): """ @@ -663,8 +663,8 @@ def repo_history(request, repo_id): server_crypto = UserOptions.objects.is_server_crypto(username) except CryptoOptionNotSetError: # Assume server_crypto is ``False`` if this option is not set. - server_crypto = False - + server_crypto = False + password_set = False if repo.props.encrypted and \ (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)): @@ -723,8 +723,8 @@ def repo_view_snapshot(request, repo_id): server_crypto = UserOptions.objects.is_server_crypto(username) except CryptoOptionNotSetError: # Assume server_crypto is ``False`` if this option is not set. - server_crypto = False - + server_crypto = False + password_set = False if repo.props.encrypted and \ (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)): @@ -779,8 +779,8 @@ def repo_history_revert(request, repo_id): server_crypto = UserOptions.objects.is_server_crypto(username) except CryptoOptionNotSetError: # Assume server_crypto is ``False`` if this option is not set. - server_crypto = False - + server_crypto = False + password_set = False if repo.props.encrypted and \ (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)): @@ -889,7 +889,7 @@ def create_default_library(request): default_repo, '/', obj_name, username, 0) except SearpcError as e: logger.error(e) - return + return UserOptions.objects.set_default_repo(username, default_repo) @@ -984,7 +984,7 @@ def myhome(request): @user_mods_check def starred(request): """List starred files. - + Arguments: - `request`: """ @@ -995,7 +995,7 @@ def starred(request): return render_to_response('starred.html', { "starred_files": starred_files, }, context_instance=RequestContext(request)) - + @login_required @user_mods_check @@ -1007,24 +1007,24 @@ def devices(request): return render_to_response('devices.html', { "devices": user_devices, }, context_instance=RequestContext(request)) - + @login_required_ajax def unlink_device(request): content_type = 'application/json; charset=utf-8' platform = request.POST.get('platform', '') device_id = request.POST.get('device_id', '') - + if not platform or not device_id: return HttpResponseBadRequest(json.dumps({'error': _(u'Argument missing')}), content_type=content_type) - + try: do_unlink_device(request.user.username, platform, device_id) except: return HttpResponse(json.dumps({'error': _(u'Internal server error')}), status=500, content_type=content_type) - + return HttpResponse(json.dumps({'success': True}), content_type=content_type) @login_required @@ -1078,7 +1078,7 @@ def unsetinnerpub(request, repo_id): # quota_usage = seafserv_threaded_rpc.get_user_quota_usage(owner_name) # user_dict = user_info(request, owner_name) - + # return render_to_response('ownerhome.html', { # "owned_repos": owned_repos, # "quota_usage": quota_usage, @@ -1090,10 +1090,10 @@ def unsetinnerpub(request, repo_id): def repo_set_access_property(request, repo_id): ap = request.GET.get('ap', '') seafserv_threaded_rpc.repo_set_access_property(repo_id, ap) - + return HttpResponseRedirect(reverse('repo', args=[repo_id])) -@login_required +@login_required def repo_del_file(request, repo_id): if check_repo_access_permission(repo_id, request.user) != 'rw': return render_permission_error(request, _('Failed to delete file.')) @@ -1109,7 +1109,7 @@ def repo_del_file(request, repo_id): url = reverse('repo', args=[repo_id]) + ('?p=%s' % urllib2.quote(parent_dir.encode('utf-8'))) return HttpResponseRedirect(url) - + def repo_access_file(request, repo_id, obj_id): """Delete or download file. TODO: need to be rewrite. @@ -1190,7 +1190,7 @@ def file_upload_progress_page(request): 'upload_progress_con_id': upload_progress_con_id, }, context_instance=RequestContext(request)) -@login_required +@login_required def validate_filename(request): repo_id = request.GET.get('repo_id') filename = request.GET.get('filename') @@ -1240,7 +1240,7 @@ def render_file_revisions (request, repo_id): if not commits: return render_error(request) - + # Check whether user is repo owner if validate_owner(request, repo_id): is_owner = True @@ -1400,10 +1400,10 @@ def view_shared_dir(request, token): repo_id = fileshare.repo_id path = request.GET.get('p', '') path = fileshare.path if not path else path - if path[-1] != '/': # Normalize dir path + if path[-1] != '/': # Normalize dir path path += '/' - - if not path.startswith(fileshare.path): + + if not path.startswith(fileshare.path): path = fileshare.path # Can not view upper dir of shared dir repo = get_repo(repo_id) @@ -1417,13 +1417,13 @@ def view_shared_dir(request, token): zipped = gen_path_link(path, '') if path == fileshare.path: # When user view the shared dir.. - # increase shared link view_cnt, + # increase shared link view_cnt, fileshare = FileShare.objects.get(token=token) fileshare.view_cnt = F('view_cnt') + 1 fileshare.save() traffic_over_limit = user_traffic_over_limit(fileshare.username) - + return render_to_response('view_shared_dir.html', { 'repo': repo, 'token': token, @@ -1461,7 +1461,7 @@ def view_shared_upload_link(request, token): else: return render_to_response('share_access_validation.html', d, context_instance=RequestContext(request)) - + username = uploadlink.username repo_id = uploadlink.repo_id path = uploadlink.path @@ -1525,9 +1525,9 @@ def pubrepo(request): """ if not request.user.permissions.can_view_org(): raise Http404 - + username = request.user.username - + if request.cloud_mode and request.user.org is not None: org_id = request.user.org.org_id public_repos = seaserv.list_org_inner_pub_repos(org_id, username) @@ -1538,7 +1538,7 @@ def pubrepo(request): 'public_repos': public_repos, 'create_shared_repo': True, }, context_instance=RequestContext(request)) - + if not request.cloud_mode: public_repos = seaserv.list_inner_pub_repos(username) for r in public_repos: @@ -1558,14 +1558,14 @@ def pubgrp(request): """ if not request.user.permissions.can_view_org(): raise Http404 - + if request.cloud_mode and request.user.org is not None: org_id = request.user.org.org_id groups = seaserv.get_org_groups(org_id, -1, -1) return render_to_response('organizations/pubgrp.html', { 'groups': groups, }, context_instance=RequestContext(request)) - + if not request.cloud_mode: groups = seaserv.get_personal_groups(-1, -1) return render_to_response('pubgrp.html', { @@ -1579,7 +1579,7 @@ def get_pub_users(request, start, limit): url_prefix = request.user.org.url_prefix users_plus_one = seaserv.get_org_users_by_url_prefix(url_prefix, start, limit) - + elif request.cloud_mode: raise Http404 # no pubuser in cloud mode @@ -1608,7 +1608,7 @@ def pubuser(request): """ if not request.user.permissions.can_view_org(): raise Http404 - + # Make sure page request is an int. If not, deliver first page. try: current_page = int(request.GET.get('page', '1')) @@ -1629,21 +1629,21 @@ def pubuser(request): users = users_plus_one[:per_page] username = request.user.username contacts = Contact.objects.get_contacts_by_user(username) - contact_emails = [] + contact_emails = [] for c in contacts: contact_emails.append(c.contact_email) for u in users: if u.email == username or u.email in contact_emails: u.can_be_contact = False else: - u.can_be_contact = True + u.can_be_contact = True return render_to_response('pubuser.html', { 'users': users, 'current_page': current_page, 'has_prev': has_prev, 'has_next': has_next, - 'page_range': page_range, + 'page_range': page_range, }, context_instance=RequestContext(request)) @login_required_ajax @@ -1693,7 +1693,7 @@ def repo_download_dir(request, repo_id): dirname = os.path.basename(path.rstrip('/')) # Here use `rstrip` to cut out last '/' in path else: dirname = repo.name - + allow_download = False fileshare_token = request.GET.get('t', '') from_shared_link = False @@ -1789,18 +1789,18 @@ def group_events_data(events): utc = dt.replace(tzinfo=timezone.utc) local = timezone.make_naive(utc, tz) return local - + event_groups = [] for e in events: e.time = utc_to_local(e.timestamp) - e.date = e.time.strftime("%Y-%m-%d") + e.date = e.time.strftime("%Y-%m-%d") if e.etype == 'repo-update': e.author = e.commit.creator_name elif e.etype == 'repo-create': e.author = e.creator else: e.author = e.repo_owner - + if len(event_groups) == 0 or \ len(event_groups) > 0 and e.date != event_groups[-1]['date']: event_group = {} @@ -1814,7 +1814,7 @@ def group_events_data(events): def pdf_full_view(request): '''For pdf view with pdf.js.''' - + repo_id = request.GET.get('repo_id', '') obj_id = request.GET.get('obj_id', '') file_name = request.GET.get('file_name', '') @@ -1840,7 +1840,7 @@ def convert_cmmt_desc_link(request): # perm check if check_repo_access_permission(repo_id, request.user) is None: raise Http404 - + diff_result = seafserv_threaded_rpc.get_diff(repo_id, '', cmmt_id) if not diff_result: raise Http404 @@ -1849,7 +1849,7 @@ def convert_cmmt_desc_link(request): if name not in d.name: # skip to next diff_result if file/folder user clicked does not # match the diff_result - continue + continue if d.status == 'add' or d.status == 'mod': # Add or modify file return HttpResponseRedirect(reverse('repo_view_file', args=[repo_id]) + \ @@ -1879,7 +1879,7 @@ def toggle_modules(request): referer = request.META.get('HTTP_REFERER', None) next = settings.SITE_ROOT if referer is None else referer - + username = request.user.username personal_wiki = request.POST.get('personal_wiki', 'off') if personal_wiki == 'on': diff --git a/thirdpart/registration/views.py b/thirdpart/registration/views.py index 768e24147c..90cc49d7c1 100644 --- a/thirdpart/registration/views.py +++ b/thirdpart/registration/views.py @@ -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)