{gettext('Server Version: ')}{seafileVersion}
© 2019 {gettext('Seafile')}
{gettext('Server Version: ')}{seafileVersion}
© 2020 {gettext('Seafile')}
{this.renderExternalAboutLinks()}
' + error + '
'); - } else { - $('.error', popup).removeClass('hide'); - } - } - } - }); - } - - return false; - }); - -}); - -$(document).on('click', function(e) { - var target = e.target || event.srcElement; - var closePopup = function(popup, popup_switch) { - if (!popup.hasClass('hide') && !popup.is(target) && !popup.find('*').is(target) && !popup_switch.is(target) && !popup_switch.find('*').is(target) ) { - popup.addClass('hide'); - } - }; - closePopup($('#user-info-popup'), $('.account-toggle')); -}); - -// search: disable submit when input nothing -$('.search-form').on('submit', function() { - if (!$.trim($(this).find('.search-input').val())) { - return false; - } -}); - $('table').on('mouseenter', 'tr:gt(0)', function() { if (app.ui.currentDropDown || app.ui.freezeItemHightlight) { return; @@ -402,7 +348,7 @@ function userInputOPtionsForSelect2(user_search_url) { createSearchChoice: function(term) { return { - 'id': $.trim(term) + 'id': term.trim() }; }, diff --git a/seahub/api2/endpoints/admin/address_book/groups.py b/seahub/api2/endpoints/admin/address_book/groups.py index 5e85c6b570..b13aa28c80 100644 --- a/seahub/api2/endpoints/admin/address_book/groups.py +++ b/seahub/api2/endpoints/admin/address_book/groups.py @@ -221,8 +221,17 @@ class AdminAddressBookGroup(APIView): if not group: return Response({'success': True}) + org_id = None + if is_org_context(request): + # request called by org admin + org_id = request.user.org.org_id + try: - has_repo = seafile_api.if_group_has_group_owned_repo(group_id) + if org_id: + has_repo = seafile_api.org_if_group_has_group_owned_repo(org_id, group_id) + else: + has_repo = seafile_api.if_group_has_group_owned_repo(group_id) + child_groups = ccnet_api.get_child_groups(group_id) except Exception as e: logger.error(e) diff --git a/seahub/api2/endpoints/admin/org_users.py b/seahub/api2/endpoints/admin/org_users.py index 33ab642941..6c8bde1dda 100644 --- a/seahub/api2/endpoints/admin/org_users.py +++ b/seahub/api2/endpoints/admin/org_users.py @@ -28,7 +28,8 @@ from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error from seahub.api2.permissions import IsProVersion from seahub.api2.endpoints.utils import is_org_user -from seahub.utils.timeutils import timestamp_to_isoformat_timestr +from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \ + datetime_to_isoformat_timestr try: from seahub.settings import ORG_MEMBER_QUOTA_ENABLED @@ -53,8 +54,12 @@ def get_org_user_info(org_id, user_obj): user_info['quota_usage'] = org_user_quota_usage user_info['create_time'] = timestamp_to_isoformat_timestr(user_obj.ctime) - user_info['last_login'] = UserLastLogin.objects.get_by_username( - email).last_login if UserLastLogin.objects.get_by_username(email) else '' + + user_info['last_login'] = '' + last_login = UserLastLogin.objects.get_by_username(email).last_login \ + if UserLastLogin.objects.get_by_username(email) else '' + if last_login: + user_info['last_login'] = datetime_to_isoformat_timestr(last_login) return user_info diff --git a/seahub/api2/endpoints/file_history.py b/seahub/api2/endpoints/file_history.py index 5558eca84e..94ac02faa9 100644 --- a/seahub/api2/endpoints/file_history.py +++ b/seahub/api2/endpoints/file_history.py @@ -1,5 +1,6 @@ # Copyright (c) 2012-2016 Seafile Ltd. import logging +from datetime import datetime from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated @@ -108,6 +109,14 @@ class FileHistoryView(APIView): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) + # get repo history limit + try: + keep_days = seafile_api.get_repo_history_limit(repo_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + # get file history limit = request.GET.get('limit', 50) try: @@ -125,9 +134,14 @@ class FileHistoryView(APIView): result = [] for commit in file_revisions: - info = get_file_history_info(commit, avatar_size) - info['path'] = path - result.append(info) + present_time = datetime.utcnow() + history_time = datetime.utcfromtimestamp(commit.ctime) + if (keep_days == -1) or ((present_time - history_time).days < keep_days): + info = get_file_history_info(commit, avatar_size) + info['path'] = path + result.append(info) + + next_start_commit = next_start_commit if result else False return Response({ "data": result, @@ -182,11 +196,19 @@ class NewFileHistoryView(APIView): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) + # get repo history limit + try: + history_limit = seafile_api.get_repo_history_limit(repo_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + start = (page - 1) * per_page count = per_page try: - file_revisions, total_count = get_file_history(repo_id, path, start, count) + file_revisions, total_count = get_file_history(repo_id, path, start, count, history_limit) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' diff --git a/seahub/api2/endpoints/search_user.py b/seahub/api2/endpoints/search_user.py index 97d587e1b7..32314af6df 100644 --- a/seahub/api2/endpoints/search_user.py +++ b/seahub/api2/endpoints/search_user.py @@ -120,7 +120,8 @@ class SearchUser(APIView): ## search finished, now filter out some users # remove duplicate emails - email_list = list({}.fromkeys(email_list).keys()) + # get_emailusers_in_list can only accept 20 users at most + email_list = list({}.fromkeys(email_list).keys())[:20] email_result = [] diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py index 843aed8c20..9646cfe18e 100644 --- a/seahub/api2/endpoints/share_links.py +++ b/seahub/api2/endpoints/share_links.py @@ -2,12 +2,11 @@ import os import stat import json -import pytz import logging import posixpath from constance import config -from datetime import datetime from dateutil.relativedelta import relativedelta +import dateutil.parser from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated @@ -16,6 +15,7 @@ from rest_framework.views import APIView from rest_framework import status from django.utils import timezone +from django.utils.timezone import get_current_timezone from django.utils.translation import ugettext as _ from django.utils.http import urlquote @@ -281,13 +281,13 @@ class ShareLinks(APIView): elif expiration_time: try: - expire_date = datetime.fromisoformat(expiration_time) + expire_date = dateutil.parser.isoparse(expiration_time) except Exception as e: logger.error(e) error_msg = 'expiration_time invalid, should be iso format, for example: 2020-05-17T10:26:22+08:00' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - expire_date = expire_date.astimezone(pytz.UTC).replace(tzinfo=None) + expire_date = expire_date.astimezone(get_current_timezone()).replace(tzinfo=None) if SHARE_LINK_EXPIRE_DAYS_MIN > 0: expire_date_min_limit = timezone.now() + relativedelta(days=SHARE_LINK_EXPIRE_DAYS_MIN) diff --git a/seahub/api2/endpoints/upload_links.py b/seahub/api2/endpoints/upload_links.py index b571eaac6d..a98c5667f3 100644 --- a/seahub/api2/endpoints/upload_links.py +++ b/seahub/api2/endpoints/upload_links.py @@ -1,11 +1,10 @@ # Copyright (c) 2012-2016 Seafile Ltd. import os import json -import pytz import logging from constance import config -from datetime import datetime from dateutil.relativedelta import relativedelta +import dateutil.parser from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated @@ -14,6 +13,7 @@ from rest_framework.views import APIView from rest_framework import status from django.utils import timezone +from django.utils.timezone import get_current_timezone from django.utils.translation import ugettext as _ from seaserv import seafile_api @@ -178,13 +178,13 @@ class UploadLinks(APIView): elif expiration_time: try: - expire_date = datetime.fromisoformat(expiration_time) + expire_date = dateutil.parser.isoparse(expiration_time) except Exception as e: logger.error(e) error_msg = 'expiration_time invalid, should be iso format, for example: 2020-05-17T10:26:22+08:00' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - expire_date = expire_date.astimezone(pytz.UTC).replace(tzinfo=None) + expire_date = expire_date.astimezone(get_current_timezone()).replace(tzinfo=None) # resource check repo = seafile_api.get_repo(repo_id) diff --git a/seahub/api2/endpoints/zip_task.py b/seahub/api2/endpoints/zip_task.py index 6b1a1a3d5b..693dbce1ef 100644 --- a/seahub/api2/endpoints/zip_task.py +++ b/seahub/api2/endpoints/zip_task.py @@ -72,7 +72,8 @@ class ZipTaskView(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) # permission check - if not check_folder_permission(request, repo_id, parent_dir): + repo_folder_permission = check_folder_permission(request, repo_id, parent_dir) + if not repo_folder_permission: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) @@ -90,6 +91,11 @@ class ZipTaskView(APIView): error_msg = 'Folder %s not found.' % full_dir_path return api_error(status.HTTP_404_NOT_FOUND, error_msg) + if not json.loads(seafile_api.is_dir_downloadable(repo_id, json.dumps([full_dir_path]), \ + request.user.username, repo_folder_permission))['is_downloadable']: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + dir_size = seafile_api.get_dir_size( repo.store_id, repo.version, dir_id) @@ -106,6 +112,7 @@ class ZipTaskView(APIView): if download_type == 'download-multi': dirent_list = [] total_size = 0 + full_dirent_path_list = [] for dirent_name in dirent_name_list: dirent_name = dirent_name.strip('/') dirent_list.append(dirent_name) @@ -121,6 +128,13 @@ class ZipTaskView(APIView): else: total_size += current_dirent.size + full_dirent_path_list.append(full_dirent_path) + + if not json.loads(seafile_api.is_dir_downloadable(repo_id, json.dumps(full_dirent_path_list), \ + request.user.username, repo_folder_permission))['is_downloadable']: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + if total_size > seaserv.MAX_DOWNLOAD_DIR_SIZE: error_msg = _('Total size exceeds limit.') return api_error(status.HTTP_400_BAD_REQUEST, error_msg) @@ -192,7 +206,8 @@ class ZipTaskView(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) # permission check - if parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_download is False: + repo_folder_permission = check_folder_permission(request, repo_id, parent_dir) + if parse_repo_perm(repo_folder_permission).can_download is False: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) @@ -210,6 +225,11 @@ class ZipTaskView(APIView): error_msg = 'Folder %s not found.' % full_dir_path return api_error(status.HTTP_404_NOT_FOUND, error_msg) + if not json.loads(seafile_api.is_dir_downloadable(repo_id, json.dumps([full_dir_path]), \ + request.user.username, repo_folder_permission))['is_downloadable']: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + dir_size = seafile_api.get_dir_size( repo.store_id, repo.version, dir_id) @@ -226,6 +246,7 @@ class ZipTaskView(APIView): if download_type == 'download-multi': dirent_list = [] total_size = 0 + full_dirent_path_list = [] for dirent_name in dirent_name_list: dirent_name = dirent_name.strip('/') dirent_list.append(dirent_name) @@ -241,6 +262,13 @@ class ZipTaskView(APIView): else: total_size += current_dirent.size + full_dirent_path_list.append(full_dirent_path) + + if not json.loads(seafile_api.is_dir_downloadable(repo_id, json.dumps(full_dirent_path_list), \ + request.user.username, repo_folder_permission))['is_downloadable']: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + if total_size > seaserv.MAX_DOWNLOAD_DIR_SIZE: error_msg = _('Total size exceeds limit.') return api_error(status.HTTP_400_BAD_REQUEST, error_msg) diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 21894d136f..2ac83eb4ca 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -328,7 +328,12 @@ class AccountInfo(APIView): info['is_staff'] = request.user.is_staff if getattr(settings, 'MULTI_INSTITUTION', False): - info['is_inst_admin'] = request.user.inst_admin + from seahub.institutions.models import InstitutionAdmin + try: + InstitutionAdmin.objects.get(user=email) + info['is_inst_admin'] = True + except InstitutionAdmin.DoesNotExist: + info['is_inst_admin'] = False interval = UserOptions.objects.get_file_updates_email_interval(email) info['email_notification_interval'] = 0 if interval is None else interval diff --git a/seahub/base/management/commands/check_user_quota.py b/seahub/base/management/commands/check_user_quota.py new file mode 100644 index 0000000000..1818f7945c --- /dev/null +++ b/seahub/base/management/commands/check_user_quota.py @@ -0,0 +1,79 @@ +# Copyright (c) 2012-2016 Seafile Ltd. + +from django.core.management.base import BaseCommand +from django.utils.translation import ugettext as _ +from django.utils import translation + +from seaserv import ccnet_api, seafile_api + +from seahub.base.accounts import User +from seahub.profile.models import Profile +from seahub.utils import IS_EMAIL_CONFIGURED, send_html_email, \ + get_site_name + + +class Command(BaseCommand): + + help = "Used to send notice email when user's quota is almost full." + + def __init__(self, *args, **kwargs): + + super(Command, self).__init__(*args, **kwargs) + + def add_arguments(self, parser): + + parser.add_argument( + '--email', + help="Only check this user's quota." + ) + + def get_user_language(self, username): + return Profile.objects.get_user_language(username) + + def send_email(self, email): + + # save current language + cur_language = translation.get_language() + + # get and active user language + user_language = self.get_user_language(email) + translation.activate(user_language) + + orgs = ccnet_api.get_orgs_by_user(email) + if orgs: + org_id = orgs[0].org_id + quota_usage = seafile_api.get_org_user_quota_usage(org_id, email) + quota_total = seafile_api.get_org_user_quota(org_id, email) + else: + quota_usage = seafile_api.get_user_self_usage(email) + quota_total = seafile_api.get_user_quota(email) + + if IS_EMAIL_CONFIGURED and quota_total > 0 and quota_usage/quota_total > 0.9: + + data = {'email': email, 'quota_total': quota_total, 'quota_usage': quota_usage} + contact_email = Profile.objects.get_contact_email_by_user(email) + + print('Send email to %s(%s)' % (contact_email, email)) + + send_html_email(_(u'Your quota is almost full on %s') % get_site_name(), + 'user_quota_full.html', data, None, [contact_email]) + + # restore current language + translation.activate(cur_language) + + def handle(self, *args, **options): + + email = options.get('email', None) + if email: + try: + User.objects.get(email=email) + + self.send_email(email) + except User.DoesNotExist: + print('User %s not found' % email) + else: + user_obj_list = ccnet_api.get_emailusers('DB', -1, -1) + for user in user_obj_list: + self.send_email(user.email) + + print("Done") diff --git a/seahub/invitations/templates/invitations/token_view.html b/seahub/invitations/templates/invitations/token_view.html index 985a70c8c9..b687cc1d70 100644 --- a/seahub/invitations/templates/invitations/token_view.html +++ b/seahub/invitations/templates/invitations/token_view.html @@ -22,7 +22,7 @@ {% block extra_script %} -{% endblock %} - -{% block extra_script %} -{% endblock %} diff --git a/seahub/templates/registration/login.html b/seahub/templates/registration/login.html index 207ce67af5..2aa2287d78 100644 --- a/seahub/templates/registration/login.html +++ b/seahub/templates/registration/login.html @@ -149,11 +149,11 @@ $('#refresh-captcha').on('click', function() { }); $('#login-form').on('submit', function(){ - if (!$.trim($('input[name="login"]').val())) { + if (!$('input[name="login"]').val().trim()) { $('.error').removeClass('hide').html("{% trans "Email or username cannot be blank" %}"); return false; } - if (!$.trim($('input[name="password"]').val())) { + if (!$('input[name="password"]').val().trim()) { $('.error').removeClass('hide').html("{% trans "Password cannot be blank" %}"); return false; } diff --git a/seahub/templates/registration/password_change_form.html b/seahub/templates/registration/password_change_form.html index db2ffa6255..ee269df159 100644 --- a/seahub/templates/registration/password_change_form.html +++ b/seahub/templates/registration/password_change_form.html @@ -44,7 +44,7 @@ $("#id_new_password1") }) .on('keyup', function() { var pwd = $(this).val(); - if ($.trim(pwd)) { + if (pwd.trim()) { var level = getStrengthLevel(pwd); showStrength(level); } else { @@ -54,9 +54,9 @@ $("#id_new_password1") {% endif %} $('form').on('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()); + var old_pwd = $('input[name="old_password"]').val().trim(), + pwd1 = $('input[name="new_password1"]').val().trim(), + pwd2 = $('input[name="new_password2"]').val().trim(); if (!old_pwd) { $('.error').html("{% trans "Current password cannot be blank" %}").removeClass('hide'); diff --git a/seahub/templates/registration/password_set_form.html b/seahub/templates/registration/password_set_form.html index 1723f86a25..e3716d3b25 100644 --- a/seahub/templates/registration/password_set_form.html +++ b/seahub/templates/registration/password_set_form.html @@ -47,7 +47,7 @@ $("#id_new_password1") }) .on('keyup', function() { var pwd = $(this).val(); - if ($.trim(pwd)) { + if (pwd.trim()) { var level = getStrengthLevel(pwd); showStrength(level); } else { @@ -57,8 +57,8 @@ $("#id_new_password1") {% endif %} $('form').on('submit', function(){ - var pwd1 = $.trim($('input[name="new_password1"]').val()), - pwd2 = $.trim($('input[name="new_password2"]').val()); + var pwd1 = $('input[name="new_password1"]').val().trim(), + pwd2 = $('input[name="new_password2"]').val().trim(); if (!pwd1) { $('.error').html("{% trans "Password cannot be blank" %}").removeClass('hide'); diff --git a/seahub/templates/registration/registration_form.html b/seahub/templates/registration/registration_form.html index 5e3e86cac2..d179fec0ed 100644 --- a/seahub/templates/registration/registration_form.html +++ b/seahub/templates/registration/registration_form.html @@ -69,7 +69,7 @@ $("#id_password1") }) .on('keyup', function() { var pwd = $(this).val(); - if ($.trim(pwd)) { + if (pwd.trim()) { var level = getStrengthLevel(pwd); showStrength(level); } else { @@ -79,9 +79,9 @@ $("#id_password1") {% endif %} $('#signup-form').on('submit', function(){ - var email = $.trim($('input[name="email"]').val()), - pwd1 = $.trim($('input[name="password1"]').val()), - pwd2 = $.trim($('input[name="password2"]').val()); + var email = $('input[name="email"]').val().trim(), + pwd1 = $('input[name="password1"]').val().trim(), + pwd2 = $('input[name="password2"]').val().trim(); if (!email) { $('.error').html("{% trans "Email cannot be blank" %}").removeClass('hide'); diff --git a/seahub/templates/share_access_validation.html b/seahub/templates/share_access_validation.html index b2f22b7dc7..4bb1afa265 100644 --- a/seahub/templates/share_access_validation.html +++ b/seahub/templates/share_access_validation.html @@ -28,7 +28,7 @@ $('#share-passwd-form').on('submit', function() { var form = $(this), pwd = $('[name="password"]', form).val(), err = $('.error',form); - if (!$.trim(pwd)) { + if (!pwd.trim()) { err.html("{% trans "Please enter the password." %}").removeClass('hide'); return false; } diff --git a/seahub/templates/snippets/search_form.html b/seahub/templates/snippets/search_form.html deleted file mode 100644 index 3067bf2bfa..0000000000 --- a/seahub/templates/snippets/search_form.html +++ /dev/null @@ -1,79 +0,0 @@ -{% load i18n %} - - - - -