diff --git a/media/css/seahub.css b/media/css/seahub.css index 308221bf7f..b2024fe410 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -1848,6 +1848,7 @@ textarea:-moz-placeholder {/* for FF */ .displayed-op .op:hover { text-decoration:none; } +.audit-item .audit-select-hidden, .repo-file-list .hidden-op { position:absolute; background:#fff; @@ -1856,6 +1857,7 @@ textarea:-moz-placeholder {/* for FF */ border-radius:5px; z-index:10; } +.audit-select-hidden li a, .hidden-op li a { display:block; padding:0 12px; @@ -1864,6 +1866,13 @@ textarea:-moz-placeholder {/* for FF */ width:500px; padding:10px 20px; } +.perm-dir-tree-cont { + padding:5px; + height:100px; + overflow:auto; + border:1px solid #eee; + margin:5px 0 10px; +} .file-tree-cont, .dir-tree-cont { padding:5px; height:280px; @@ -3347,6 +3356,11 @@ textarea:-moz-placeholder {/* for FF */ } /* repo setting */ +.user-perm-add-tr input, +.group-perm-add-tr input { + padding:2px 5px; +} + .user-perm-add-perm, .group-perm-add-perm, .perm-toggle-select, @@ -3368,3 +3382,25 @@ textarea:-moz-placeholder {/* for FF */ #perm-add-jstree-wrap { background:#fcfcfc; } + +/* file audit*/ +.audit-show-select { + font-weight: normal; + font-size: 13px; + padding-right: 2px; +} +#audit-unselect-op div { + display: inline-block; + height: 20px; + border: 1px solid #ccc; + background: #f2f2f2; + cursor: pointer; +} +#audit-unselect-op span { + margin-right: 5px; +} +#audit-unselect-op a { + margin: 0 8px; + text-decoration: none; + font-size: 14px; +} diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 7c17879ac8..57be64c459 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -26,7 +26,6 @@ from django.http import HttpResponse, Http404 from django.template import RequestContext from django.template.loader import render_to_string from django.shortcuts import render_to_response -from django.utils import timezone from .throttling import ScopedRateThrottle from .authentication import TokenAuthentication @@ -63,6 +62,7 @@ from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ get_org_user_events from seahub.utils.star import star_file, unstar_file from seahub.utils.file_types import IMAGE, DOCUMENT +from seahub.utils.timeutils import utc_to_local from seahub.views import validate_owner, is_registered_user, \ group_events_data, get_diff, create_default_library, get_owned_repo_list, \ list_inner_pub_repos, get_virtual_repos_by_owner, check_folder_permission @@ -2549,12 +2549,6 @@ class EventsView(APIView): else: d['author'] = e.repo_owner - def utc_to_local(dt): - tz = timezone.get_default_timezone() - utc = dt.replace(tzinfo=timezone.utc) - local = timezone.make_naive(utc, tz) - return local - epoch = datetime.datetime(1970, 1, 1) local = utc_to_local(e.timestamp) time_diff = local - epoch diff --git a/seahub/group/templates/group/group_info.html b/seahub/group/templates/group/group_info.html index 1ee53abf79..ab975492c7 100644 --- a/seahub/group/templates/group/group_info.html +++ b/seahub/group/templates/group/group_info.html @@ -49,7 +49,7 @@ {% endif %} {{ repo.owner|email2nickname }} - + {% if is_staff or repo.share_from_me %} {% endif %} @@ -141,8 +141,9 @@ $('.cancel-share').click(function() { var btn_ct = $(this).parent(), repo_id = btn_ct.data('id'), repo_owner = btn_ct.attr('data-owner'), + repo_perm = btn_ct.attr('data-perm'), repo_name = btn_ct.attr('data-name'); - $(this).data('url', '{% url 'repo_remove_share' %}?repo_id=' + e(repo_id) + '&from=' + e(repo_owner) + '&gid={{ group.id }}').attr('data-target', repo_name); + $(this).data('url', '{% url 'repo_remove_share' %}?repo_id=' + e(repo_id) + '&from=' + e(repo_owner) + '&gid={{ group.id }}' + '&permission=' + e(repo_perm)).attr('data-target', repo_name); }); addConfirmTo($('.cancel-share'), { 'title': "{% trans "Unshare Library" %}", diff --git a/seahub/settings.py b/seahub/settings.py index 999906ffaa..c29548364d 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -427,6 +427,11 @@ PREVIEW_DEFAULT_SIZE = '100' # for origin image file: size(MB) THUMBNAIL_IMAGE_SIZE_LIMIT = 30 +##################### +# Folder Permission # +##################### +ENABLE_FOLDER_PERM = False + ################# # Email sending # ################# diff --git a/seahub/share/templates/share/list_priv_shared_folders.html b/seahub/share/templates/share/list_priv_shared_folders.html index b158d1e205..789b10dd68 100644 --- a/seahub/share/templates/share/list_priv_shared_folders.html +++ b/seahub/share/templates/share/list_priv_shared_folders.html @@ -40,14 +40,14 @@ {{ repo.props.repo_desc }} {% if repo.props.share_type == 'group' %} - + {% endif %} {% if repo.props.share_type == 'personal' %} - + {% endif %} {% if repo.props.share_type == 'public' %} {% if not org %} - + {% else %} {% endif %} @@ -92,10 +92,8 @@ $('.share-permission-select').change(function() { success: function(data) { if (data['success']) { feedback("{% trans "Edit succeeded" %}", 'success'); - select.prev().children('.share-permission-cur-value').html(select.children('option[value="' +select.val() + '"]').text()); + location.reload(true); } - select.addClass('hide'); - select.prev().removeClass('hide'); }, error: function() { feedback("{% trans "Edit failed." %}", 'error'); diff --git a/seahub/share/templates/share/repos.html b/seahub/share/templates/share/repos.html index e88f619ae7..0e4374511b 100644 --- a/seahub/share/templates/share/repos.html +++ b/seahub/share/templates/share/repos.html @@ -44,13 +44,13 @@ {{ repo.props.repo_desc }} {% if repo.props.share_type == 'group' %} - + {% endif %} {% if repo.props.share_type == 'personal' %} - + {% endif %} {% if repo.props.share_type == 'public' %} - + {% endif %} diff --git a/seahub/share/templates/share/user_share_list.html b/seahub/share/templates/share/user_share_list.html index 95de698b34..b8284ba986 100644 --- a/seahub/share/templates/share/user_share_list.html +++ b/seahub/share/templates/share/user_share_list.html @@ -56,9 +56,9 @@ {% endif %} {% if repo.share_in %} - + {% else %} - + {% endif %} diff --git a/seahub/share/views.py b/seahub/share/views.py index de2b59464f..e5568cfa06 100644 --- a/seahub/share/views.py +++ b/seahub/share/views.py @@ -40,8 +40,8 @@ from seahub.views import is_registered_user, check_repo_access_permission, \ from seahub.utils import render_permission_error, string2list, render_error, \ gen_token, gen_shared_link, gen_shared_upload_link, gen_dir_share_link, \ gen_file_share_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \ - is_valid_username, send_html_email, is_org_context, \ - normalize_file_path, normalize_dir_path + is_valid_username, send_html_email, is_org_context, normalize_file_path, \ + normalize_dir_path, send_perm_audit_msg, get_origin_repo_info from seahub.settings import SITE_ROOT, REPLACE_FROM_EMAIL, ADD_REPLY_TO_HEADER # Get an instance of a logger @@ -253,8 +253,18 @@ def share_repo(request): if is_valid_username(share_to): share_to_users.append(share_to) + origin_repo_id, origin_path = get_origin_repo_info(repo.id) + if origin_repo_id is not None: + perm_repo_id = origin_repo_id + perm_path = origin_path + else: + perm_repo_id = repo.id + perm_path = '/' + if share_to_all: share_to_public(request, repo, permission) + send_perm_audit_msg('add-repo-perm', username, 'all', \ + perm_repo_id, perm_path, permission) if not check_user_share_quota(username, repo, users=share_to_users, groups=share_to_groups): @@ -266,11 +276,15 @@ def share_repo(request): for group in share_to_groups: share_to_group(request, repo, group, permission) + send_perm_audit_msg('add-repo-perm', username, group.id, \ + perm_repo_id, perm_path, permission) for email in share_to_users: # Add email to contacts. mail_sended.send(sender=None, user=request.user.username, email=email) share_to_user(request, repo, email, permission) + send_perm_audit_msg('add-repo-perm', username, email, \ + perm_repo_id, perm_path, permission) return HttpResponseRedirect(next) @@ -285,10 +299,23 @@ def repo_remove_share(request): repo_id = request.GET.get('repo_id', '') group_id = request.GET.get('gid', '') from_email = request.GET.get('from', '') - if not is_valid_username(from_email): + perm = request.GET.get('permission', None) + if not is_valid_username(from_email) or perm is None: return render_error(request, _(u'Argument is not valid')) username = request.user.username + repo = seafile_api.get_repo(repo_id) + if not repo: + return render_error(request, _(u'Library does not exist')) + + origin_repo_id, origin_path = get_origin_repo_info(repo.id) + if origin_repo_id is not None: + perm_repo_id = origin_repo_id + perm_path = origin_path + else: + perm_repo_id = repo.id + perm_path = '/' + # if request params don't have 'gid', then remove repos that share to # to other person; else, remove repos that share to groups if not group_id: @@ -304,6 +331,8 @@ def repo_remove_share(request): org_remove_share(org_id, repo_id, from_email, to_email) else: seaserv.remove_share(repo_id, from_email, to_email) + send_perm_audit_msg('delete-repo-perm', from_email, to_email, \ + perm_repo_id, perm_path, perm) else: try: group_id = int(group_id) @@ -323,6 +352,8 @@ def repo_remove_share(request): del_org_group_repo(repo_id, org_id, group_id) else: seafile_api.unset_group_repo(repo_id, group_id, from_email) + send_perm_audit_msg('delete-repo-perm', from_email, group_id, \ + perm_repo_id, perm_path, perm) messages.success(request, _('Successfully removed share')) @@ -566,6 +597,19 @@ def share_permission_admin(request): permission = form.cleaned_data['permission'] from_email = request.user.username + repo = seafile_api.get_repo(repo_id) + if not repo: + return render_error(request, _(u'Library does not exist')) + + origin_repo_id, origin_path = get_origin_repo_info(repo.id) + if origin_repo_id is not None: + perm_repo_id = origin_repo_id + perm_path = origin_path + else: + perm_repo_id = repo.id + perm_path = '/' + + if share_type == 'personal': if not is_valid_username(email_or_group): return HttpResponse(json.dumps({'success': False}), status=400, @@ -579,6 +623,9 @@ def share_permission_admin(request): else: seafile_api.set_share_permission(repo_id, from_email, email_or_group, permission) + send_perm_audit_msg('modify-repo-perm', from_email, \ + email_or_group, perm_repo_id, perm_path, permission) + except SearpcError: return HttpResponse(json.dumps({'success': False}), status=500, content_type=content_type) @@ -592,8 +639,12 @@ def share_permission_admin(request): seaserv.seafserv_threaded_rpc.set_org_group_repo_permission( org_id, int(email_or_group), repo_id, permission) else: - seafile_api.set_group_repo_permission(int(email_or_group), - repo_id, permission) + group_id = int(email_or_group) + seafile_api.set_group_repo_permission(group_id, + repo_id, + permission) + send_perm_audit_msg('modify-repo-perm', from_email, \ + group_id, perm_repo_id, perm_path, permission) except SearpcError: return HttpResponse(json.dumps({'success': False}), status=500, content_type=content_type) @@ -608,6 +659,8 @@ def share_permission_admin(request): org_id, repo_id, permission) else: seafile_api.add_inner_pub_repo(repo_id, permission) + send_perm_audit_msg('modify-repo-perm', from_email, 'all', \ + perm_repo_id, perm_path, permission) except SearpcError: return HttpResponse(json.dumps({'success': False}), status=500, content_type=content_type) diff --git a/seahub/templates/pubrepo.html b/seahub/templates/pubrepo.html index 55932167ff..18837a5e99 100644 --- a/seahub/templates/pubrepo.html +++ b/seahub/templates/pubrepo.html @@ -38,7 +38,7 @@ {{ repo.props.user|email2nickname }} {% if request.user.is_staff or repo.share_from_me %} - + {% endif %} diff --git a/seahub/templates/repo_basic_info.html b/seahub/templates/repo_basic_info.html index 4d0f7691ac..b5d354615e 100644 --- a/seahub/templates/repo_basic_info.html +++ b/seahub/templates/repo_basic_info.html @@ -23,8 +23,10 @@ {% if not repo.encrypted %}
  • {% trans "Shared Links" %}
  • {% endif %} -
  • {% trans "Sharing Management" %}
  • -
  • {% trans "SubFolder Permission" %}
  • +
  • {% trans "Sharing Permission" %}
  • + {% if ENABLE_FOLDER_PERM %} +
  • {% trans "Folder Permission" %}
  • + {% endif %} {% endblock %} diff --git a/seahub/templates/repo_change_password.html b/seahub/templates/repo_change_password.html index e281db9268..90ff64cfec 100644 --- a/seahub/templates/repo_change_password.html +++ b/seahub/templates/repo_change_password.html @@ -23,8 +23,10 @@ {% if not repo.encrypted %}
  • {% trans "Shared Links" %}
  • {% endif %} -
  • {% trans "Sharing Management" %}
  • -
  • {% trans "SubFolder Permission" %}
  • +
  • {% trans "Sharing Permission" %}
  • + {% if ENABLE_FOLDER_PERM %} +
  • {% trans "Folder Permission" %}
  • + {% endif %} {% endblock %} diff --git a/seahub/templates/repo_folder_perm.html b/seahub/templates/repo_folder_perm.html index bc0dd47b7b..4f5a240ccd 100644 --- a/seahub/templates/repo_folder_perm.html +++ b/seahub/templates/repo_folder_perm.html @@ -23,8 +23,8 @@ {% if not repo.encrypted %}
  • {% trans "Shared Links" %}
  • {% endif %} -
  • {% trans "Sharing Management" %}
  • -
  • {% trans "SubFolder Permission" %}
  • +
  • {% trans "Sharing Permission" %}
  • +
  • {% trans "Folder Permission" %}
  • {% endblock %} @@ -33,7 +33,7 @@

    {% trans "Library Settings" %}

    {% trans "Folder Permission" %}

    -

    {% trans "View and manage all the folder permissions in this library." %}

    +

    {% trans "View and manage all folder permissions in this library." %}

      @@ -54,9 +54,9 @@ - + - + @@ -71,7 +71,7 @@ {% if user_folder_perms %} {% for perm in user_folder_perms %} - + {{ perm.user }} {{ perm.folder_name }} @@ -107,11 +107,11 @@ - + - + @@ -126,7 +126,7 @@ {% if group_folder_perms %} {% for perm in group_folder_perms %} - + {{ perm.group_name }} {{ perm.folder_name }} @@ -157,7 +157,7 @@

      {% trans "Please selecte a directory" %}

      -
      +

      @@ -197,7 +197,7 @@ group_list.push({value:group_id, label:group_name}); user_input.bind('autocompleteopen', function(e, ui) { var widget = user_input.autocomplete('widget'); - widget.css({'max-width':user_input.width() - 2 * parseInt(widget.css('padding')), 'max-height':$(window).height() + $(window).scrollTop() - user_input.offset().top - user_input.outerHeight()}); + widget.css({'max-width':user_input.width() + 2 * parseInt(widget.css('padding-top')), 'max-height':$(window).height() + $(window).scrollTop() - user_input.offset().top - user_input.outerHeight()}); }) .autocomplete({ source: user_list @@ -205,7 +205,7 @@ user_input.bind('autocompleteopen', function(e, ui) { group_input.bind('autocompleteopen', function(e, ui) { var widget = group_input.autocomplete('widget'); - widget.css({'max-width':group_input.width() - 2 * parseInt(widget.css('padding')), 'max-height':$(window).height() + $(window).scrollTop() - group_input.offset().top - group_input.outerHeight()}); + widget.css({'max-width':group_input.width() + 2 * parseInt(widget.css('padding-top')), 'max-height':$(window).height() + $(window).scrollTop() - group_input.offset().top - group_input.outerHeight()}); }) .autocomplete({ source: group_list, @@ -250,15 +250,16 @@ $('.perm-toggle-select').on('change', function() { }); $('.perm-delete-btn').on('click', function() { var perm_item = $(this).parents('.perm-item'), + perm = perm_item.attr('data-perm'), path = perm_item.attr('data-path'); if (is_user_tab == true) { var user = perm_item.attr('data-user'), - data = { 'user': user, 'path': path }, + data = { 'user': user, 'path': path, 'perm': perm }, url = '{% url 'remove_user_folder_permission' repo.id %}'; } else { var group_id = perm_item.attr('data-group_id'), - data = { 'group_id': group_id, 'path': path }, + data = { 'group_id': group_id, 'path': path, 'perm': perm}, url = '{% url 'remove_group_folder_permission' repo.id %}'; } @@ -329,7 +330,7 @@ function selectClickHandler(event) { } if (path_array.length == 1) { path = path_array.shift(); - folder_name.val('Root Directory'); + folder_name.val('/'); } else { var root = path_array.shift(), path = root + path_array.join('/'), diff --git a/seahub/templates/repo_share_manage.html b/seahub/templates/repo_share_manage.html index 76ec1f0a21..6f186215f0 100644 --- a/seahub/templates/repo_share_manage.html +++ b/seahub/templates/repo_share_manage.html @@ -23,8 +23,10 @@ {% if not repo.encrypted %}
    • {% trans "Shared Links" %}
    • {% endif %} -
    • {% trans "Sharing Management" %}
    • -
    • {% trans "SubFolder Permission" %}
    • +
    • {% trans "Sharing Permission" %}
    • + {% if ENABLE_FOLDER_PERM %} +
    • {% trans "Folder Permission" %}
    • + {% endif %}
    {% endblock %} @@ -33,7 +35,7 @@

    {% trans "Library Settings" %}

    {% trans "Sharing Management" %}

    -

    {% trans "View and manage all the share of this library." %}

    +

    {% trans "View and manage sharing permissions of this library." %}

    @@ -72,7 +74,7 @@ - + {% endfor %} @@ -105,7 +107,7 @@ - + {% endfor %} diff --git a/seahub/templates/repo_shared_link.html b/seahub/templates/repo_shared_link.html index 959f908bce..1d544c10de 100644 --- a/seahub/templates/repo_shared_link.html +++ b/seahub/templates/repo_shared_link.html @@ -23,8 +23,10 @@ {% if not repo.encrypted %}
  • {% trans "Shared Links" %}
  • {% endif %} -
  • {% trans "Sharing Management" %}
  • -
  • {% trans "SubFolder Permission" %}
  • +
  • {% trans "Sharing Permission" %}
  • + {% if ENABLE_FOLDER_PERM %} +
  • {% trans "Folder Permission" %}
  • + {% endif %}
    {% endblock %} diff --git a/seahub/templates/repo_transfer_owner.html b/seahub/templates/repo_transfer_owner.html index dfad1c46f2..c903e248c6 100644 --- a/seahub/templates/repo_transfer_owner.html +++ b/seahub/templates/repo_transfer_owner.html @@ -23,8 +23,10 @@ {% if not repo.encrypted %}
  • {% trans "Shared Links" %}
  • {% endif %} -
  • {% trans "Sharing Management" %}
  • -
  • {% trans "SubFolder Permission" %}
  • +
  • {% trans "Sharing Permission" %}
  • + {% if ENABLE_FOLDER_PERM %} +
  • {% trans "Folder Permission" %}
  • + {% endif %}
    {% endblock %} diff --git a/seahub/templates/shared_file_view.html b/seahub/templates/shared_file_view.html index d1623764a6..e62b20d456 100644 --- a/seahub/templates/shared_file_view.html +++ b/seahub/templates/shared_file_view.html @@ -33,7 +33,7 @@ {% endif %} {% endif %} {% if not traffic_over_limit %} - {% trans "Download" %} ({{file_size|filesizeformat}}) + {% trans "Download" %} ({{file_size|filesizeformat}}) {% endif %}
    {% include 'snippets/file_content_html.html' %} diff --git a/seahub/templates/snippets/my_shared_repos.html b/seahub/templates/snippets/my_shared_repos.html index 6b2cd4f05a..1fff609577 100644 --- a/seahub/templates/snippets/my_shared_repos.html +++ b/seahub/templates/snippets/my_shared_repos.html @@ -26,7 +26,7 @@ {% endif %} {% if repo.share_type == 'personal' %} - + {% endif %} diff --git a/seahub/urls.py b/seahub/urls.py index 3126d74618..7dd39f1344 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -254,9 +254,13 @@ if getattr(settings, 'ENABLE_PAYMENT', False): if getattr(settings, 'ENABLE_SYSADMIN_EXTRA', False): - from seahub_extra.sysadmin_extra.views import sys_login_admin + from seahub_extra.sysadmin_extra.views import sys_login_admin, \ + sys_log_file_audit, sys_log_file_update, sys_log_perm_audit urlpatterns += patterns('', url(r'^sys/loginadmin/', sys_login_admin, name='sys_login_admin'), + url(r'^sys/log/fileaudit/', sys_log_file_audit, name='sys_log_file_audit'), + url(r'^sys/log/fileupdate/', sys_log_file_update, name='sys_log_file_update'), + url(r'^sys/log/permaudit/', sys_log_perm_audit, name='sys_log_perm_audit'), ) if getattr(settings, 'MULTI_TENANCY', False): diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 137578d0c8..56ab881453 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -10,6 +10,7 @@ import tempfile import locale import ConfigParser import mimetypes +import contextlib from datetime import datetime from urlparse import urlparse, urljoin @@ -31,8 +32,8 @@ from django.views.static import serve as django_static_serve from seahub.api2.models import Token, TokenV2 import seaserv -from seaserv import seafile_api -from seaserv import seafserv_rpc, seafserv_threaded_rpc, get_repo, get_commits,\ +from seaserv import seafile_api, send_message, seafserv_rpc, \ + seafserv_threaded_rpc, get_repo, get_commits,\ CCNET_SERVER_ADDR, CCNET_SERVER_PORT, get_org_by_id, is_org_staff, \ get_org_id_by_group, get_personal_groups_by_user, \ list_personal_repos_by_owner, get_group_repos, \ @@ -526,6 +527,14 @@ if EVENTS_CONFIG_FILE: EVENTS_ENABLED = True SeafEventsSession = seafevents.init_db_session_class(EVENTS_CONFIG_FILE) + @contextlib.contextmanager + def _get_seafevents_session(): + try: + session = SeafEventsSession() + yield session + finally: + session.close() + def _same_events(e1, e2): """Two events are equal should follow two rules: 1. event1.repo_id = event2.repo_id @@ -634,6 +643,48 @@ if EVENTS_CONFIG_FILE: def get_org_user_events(org_id, username, start, count): return _get_events(username, start, count, org_id=org_id) + def get_file_audit_events(email, org_id, repo_id, start, limit): + """Return file audit events list. (If no file audit, return 'None') + + For example: + ``get_file_audit_events(email, org_id, repo_id, 0, 10)`` returns the first 10 + events. + ``get_file_audit_events(email, org_id, repo_id, 5, 10)`` returns the 6th through + 15th events. + """ + with _get_seafevents_session() as session: + events = seafevents.get_file_audit_events(session, email, org_id, repo_id, start, limit) + + return events if events else None + + def get_file_update_events(email, org_id, repo_id, start, limit): + """Return file update events list. (If no file update, return 'None') + + For example: + ``get_file_update_events(email, org_id, repo_id, 0, 10)`` returns the first 10 + events. + ``get_file_update_events(email, org_id, repo_id, 5, 10)`` returns the 6th through + 15th events. + """ + with _get_seafevents_session() as session: + events = seafevents.get_file_update_events(session, email, org_id, repo_id, start, limit) + + return events if events else None + + def get_perm_audit_events(email, org_id, repo_id, start, limit): + """Return repo perm events list. (If no repo perm, return 'None') + + For example: + ``get_repo_perm_events(email, org_id, repo_id, 0, 10)`` returns the first 10 + events. + ``get_repo_perm_events(email, org_id, repo_id, 5, 10)`` returns the 6th through + 15th events. + """ + with _get_seafevents_session() as session: + events = seafevents.get_perm_audit_events(session, email, org_id, repo_id, start, limit) + + return events if events else None + else: EVENTS_ENABLED = False def get_user_events(): @@ -1192,3 +1243,31 @@ def clear_token(username): Token.objects.filter(user = username).delete() TokenV2.objects.filter(user = username).delete() seafile_api.delete_repo_tokens_by_email(username) + +def send_perm_audit_msg(etype, from_user, to, repo_id, path, perm): + """Send repo permission audit msg. + + Arguments: + - `request`: + - `repo`: + - `obj_id`: + - `dl_type`: web or api + """ + msg = 'perm-update\t%s\t%s\t%s\t%s\t%s\t%s' % \ + (etype, from_user, to, repo_id, path, perm) + msg_utf8 = msg.encode('utf-8') + + try: + send_message('seahub.stats', msg_utf8) + except Exception as e: + logger.error("Error when sending perm-audit-%s message: %s" % + (etype, str(e))) + +def get_origin_repo_info(repo_id): + repo = seafile_api.get_repo(repo_id) + if repo.origin_repo_id is not None: + origin_repo_id = repo.origin_repo_id + origin_path = repo.origin_path + return (origin_repo_id, origin_path) + + return (None, None) diff --git a/seahub/utils/repo.py b/seahub/utils/repo.py index 6a4060c8fb..14a6ed5736 100644 --- a/seahub/utils/repo.py +++ b/seahub/utils/repo.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- +import logging from django.utils.translation import ugettext as _ import seaserv from seaserv import seafile_api -from seahub.utils import EMPTY_SHA1, is_valid_username +from seahub.utils import EMPTY_SHA1 from seahub.views import check_repo_access_permission +from seahub.base.accounts import User + +logger = logging.getLogger(__name__) def list_dir_by_path(cmmt, path): if cmmt.root_id == EMPTY_SHA1: @@ -13,11 +17,11 @@ def list_dir_by_path(cmmt, path): else: return seafile_api.list_dir_by_commit_and_path(cmmt.repo_id, cmmt.id, path) -def check_user_folder_perm_args(request_user, repo_id, path, user, perm=None): +def check_user_folder_perm_args(from_user, repo_id, path, to_user, perm=None): if not seafile_api.get_repo(repo_id): return {'error': _(u'Library does not exist.'), 'status': 400} - if check_repo_access_permission(repo_id, request_user) != 'rw': + if check_repo_access_permission(repo_id, from_user) != 'rw': return {'error': _('Permission denied'), 'status': 403} if perm is not None: @@ -34,16 +38,21 @@ def check_user_folder_perm_args(request_user, repo_id, path, user, perm=None): if path != '/' and path.endswith('/'): return {'error': _('Path should NOT ends with "/"'), 'status': 400} - if user and not is_valid_username(user): - return {'error': _('Invalid username'), 'status': 400} + try: + user = User.objects.get(email = to_user) + except User.DoesNotExist: + user = None + + if user is None: + return {'error': _('Invalid username, should be a user already registered'), 'status': 400} return {'success': True} -def check_group_folder_perm_args(request_user, repo_id, path, group_id, perm = None): +def check_group_folder_perm_args(from_user, repo_id, path, group_id, perm = None): if not seafile_api.get_repo(repo_id): return {'error': _(u'Library does not exist.'), 'status': 400} - if check_repo_access_permission(repo_id, request_user) != 'rw': + if check_repo_access_permission(repo_id, from_user) != 'rw': return {'error': _('Permission denied'), 'status': 403} if perm is not None: @@ -60,7 +69,8 @@ def check_group_folder_perm_args(request_user, repo_id, path, group_id, perm = N if path != '/' and path.endswith('/'): return {'error': _('Path should NOT ends with "/"'), 'status': 400} - if group_id and not seaserv.get_group(group_id): + if not seaserv.get_group(group_id): return {'error': _('Invalid group'), 'status': 400} return {'success': True} + diff --git a/seahub/utils/timeutils.py b/seahub/utils/timeutils.py index 8ce0f950a3..daf988ce38 100644 --- a/seahub/utils/timeutils.py +++ b/seahub/utils/timeutils.py @@ -26,4 +26,10 @@ def value_to_db_datetime(value): # MySQL doesn't support microseconds return six.text_type(value.replace(microsecond=0)) - + +def utc_to_local(dt): + # change from UTC timezone to current seahub timezone + tz = timezone.get_default_timezone() + utc = dt.replace(tzinfo=timezone.utc) + local = timezone.make_naive(utc, tz) + return local diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 30a3c6e5a9..8b56d696b9 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -53,9 +53,10 @@ from seahub.utils import render_permission_error, render_error, list_to_string, gen_file_get_url, string2list, MAX_INT, IS_EMAIL_CONFIGURED, \ EVENTS_ENABLED, get_user_events, get_org_user_events, show_delete_days, \ TRAFFIC_STATS_ENABLED, get_user_traffic_stat, new_merge_with_no_conflict, \ - user_traffic_over_limit + user_traffic_over_limit, send_perm_audit_msg, get_origin_repo_info from seahub.utils.paginator import get_page_range from seahub.utils.star import get_dir_starred_files +from seahub.utils.timeutils import utc_to_local from seahub.views.modules import MOD_PERSONAL_WIKI, enable_mod_for_user, \ disable_mod_for_user from seahub.utils.devices import get_user_devices, do_unlink_device @@ -63,7 +64,8 @@ import seahub.settings as settings from seahub.settings import FILE_PREVIEW_MAX_SIZE, INIT_PASSWD, USE_PDFJS, \ FILE_ENCODING_LIST, FILE_ENCODING_TRY_LIST, AVATAR_FILE_STORAGE, \ SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, SEND_EMAIL_ON_RESETTING_USER_PASSWD, \ - ENABLE_SUB_LIBRARY, ENABLE_REPO_HISTORY_SETTING, REPO_PASSWORD_MIN_LENGTH + ENABLE_SUB_LIBRARY, ENABLE_REPO_HISTORY_SETTING, \ + REPO_PASSWORD_MIN_LENGTH, ENABLE_FOLDER_PERM # Get an instance of a logger logger = logging.getLogger(__name__) @@ -477,6 +479,7 @@ def repo_basic_info(request, repo_id): 'no_history_enabled': no_history_enabled, 'partial_history_enabled': partial_history_enabled, 'days_enabled': days_enabled, + 'ENABLE_FOLDER_PERM': ENABLE_FOLDER_PERM, }, context_instance=RequestContext(request)) @login_required @@ -491,6 +494,7 @@ def repo_transfer_owner(request, repo_id): return render_to_response('repo_transfer_owner.html', { 'repo': repo, + 'ENABLE_FOLDER_PERM': ENABLE_FOLDER_PERM, }, context_instance=RequestContext(request)) @login_required @@ -506,6 +510,7 @@ def repo_change_password(request, repo_id): return render_to_response('repo_change_password.html', { 'repo': repo, 'repo_password_min_length': REPO_PASSWORD_MIN_LENGTH, + 'ENABLE_FOLDER_PERM': ENABLE_FOLDER_PERM, }, context_instance=RequestContext(request)) @login_required @@ -565,6 +570,7 @@ def repo_shared_link(request, repo_id): 'repo': repo, 'fileshares': p_fileshares, 'uploadlinks': p_uploadlinks, + 'ENABLE_FOLDER_PERM': ENABLE_FOLDER_PERM, }, context_instance=RequestContext(request)) @login_required @@ -598,6 +604,7 @@ def repo_share_manage(request, repo_id): 'repo': repo, 'repo_share_user': repo_share_user, 'repo_share_group': repo_share_group, + 'ENABLE_FOLDER_PERM': ENABLE_FOLDER_PERM, }, context_instance=RequestContext(request)) @login_required @@ -607,11 +614,42 @@ def repo_folder_perm(request, repo_id): username = request.user.username can_access, repo = can_access_repo_setting(request, repo_id, username) - if not can_access: + if not can_access or not ENABLE_FOLDER_PERM: raise Http404 + def not_need_delete(perm): + repo_id = perm.repo_id + path = perm.path + group_id = perm.group_id if hasattr(perm, 'group_id') else None + email = perm.user if hasattr(perm, 'user') else None + + repo = get_repo(repo_id) + dir_id = seafile_api.get_dir_id_by_path(repo_id, path) + + if group_id is not None: + # is a group folder perm + group = get_group(group_id) + if repo is None or dir_id is None or group is None: + seafile_api.rm_folder_group_perm(repo_id, path, group_id) + return False + + if email is not None: + # is a user folder perm + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + user = None + + if repo is None or dir_id is None or user is None: + seafile_api.rm_folder_user_perm(repo_id, path, email) + return False + + return True + # for user folder permission user_folder_perms = seafile_api.list_folder_user_perm_by_repo(repo_id) + user_folder_perms = filter(lambda x: not_need_delete(x), user_folder_perms) + user_folder_perms.reverse() for folder_perm in user_folder_perms: @@ -624,6 +662,8 @@ def repo_folder_perm(request, repo_id): # for group folder permission group_folder_perms = seafile_api.list_folder_group_perm_by_repo(repo_id) + group_folder_perms = filter(lambda x: not_need_delete(x), group_folder_perms) + group_folder_perms.reverse() for folder_perm in group_folder_perms: @@ -633,15 +673,26 @@ def repo_folder_perm(request, repo_id): folder_perm.folder_name = _(u'Root Directory') else: folder_perm.folder_name = os.path.basename(folder_path) + folder_perm.group_name = get_group(folder_perm.group_id).group_name + # contacts that already registered + sys_contacts = [] contacts = Contact.objects.get_contacts_by_user(username) + for contact in contacts: + try: + user = User.objects.get(email = contact.contact_email) + except User.DoesNotExist: + user = None + + if user is not None: + sys_contacts.append(contact.contact_email) return render_to_response('repo_folder_perm.html', { 'repo': repo, 'user_folder_perms': user_folder_perms, 'group_folder_perms': group_folder_perms, - 'contacts': contacts, + 'contacts': sys_contacts, }, context_instance=RequestContext(request)) def upload_error_msg (code): @@ -1098,6 +1149,9 @@ def unsetinnerpub(request, repo_id): Only system admin, organization admin or repo owner can perform this op. """ repo = get_repo(repo_id) + perm = request.GET.get('permission', None) + if perm is None: + return render_error(request, _(u'Argument is not valid')) if not repo: messages.error(request, _('Failed to unshare the library, as it does not exist.')) return HttpResponseRedirect(reverse('share_admin')) @@ -1124,6 +1178,17 @@ def unsetinnerpub(request, repo_id): else: seaserv.unset_inner_pub_repo(repo.id) + origin_repo_id, origin_path = get_origin_repo_info(repo.id) + if origin_repo_id is not None: + perm_repo_id = origin_repo_id + perm_path = origin_path + else: + perm_repo_id = repo.id + perm_path = '/' + + send_perm_audit_msg('delete-repo-perm', username, 'all', \ + perm_repo_id, perm_path, perm) + messages.success(request, _('Unshare "%s" successfully.') % repo.name) except SearpcError: messages.error(request, _('Failed to unshare "%s".') % repo.name) @@ -1793,14 +1858,6 @@ def group_events_data(events): """ Group events according to the date. """ - # e.timestamp is a datetime.datetime in UTC - # change from UTC timezone to current seahub timezone - def utc_to_local(dt): - tz = timezone.get_default_timezone() - 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) diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index 88136d28e4..b15dd3d430 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -45,7 +45,7 @@ from seahub.utils import check_filename_with_rename, EMPTY_SHA1, \ new_merge_with_no_conflict, get_commit_before_new_merge, \ get_repo_last_modify, gen_file_upload_url, is_org_context, \ get_org_user_events, get_user_events, get_file_type_and_ext, \ - is_valid_username + is_valid_username, send_perm_audit_msg from seahub.utils.repo import check_group_folder_perm_args, \ check_user_folder_perm_args from seahub.utils.star import star_file, unstar_file @@ -1715,7 +1715,10 @@ def repo_history_changes(request, repo_id): # perm check if check_repo_access_permission(repo.id, request.user) is None: - return HttpResponse(json.dumps(changes), content_type=content_type) + if request.user.is_staff is True: + pass # Allow system staff to check repo changes + else: + return HttpResponse(json.dumps(changes), content_type=content_type) username = request.user.username try: @@ -2109,6 +2112,8 @@ def add_user_folder_permission(request, repo_id): try: seafile_api.add_folder_user_perm(repo_id, path, perm, user) + send_perm_audit_msg('add-repo-perm', request.user.username, user, \ + repo_id, path, perm) except SearpcError, e: return HttpResponse(json.dumps({"error": e.msg}), status=500, content_type=content_type) @@ -2127,8 +2132,9 @@ def remove_user_folder_permission(request, repo_id): user = request.GET.get('user', None) path = request.GET.get('path', None) + perm = request.GET.get('perm', None) - if not user or not path: + if not user or not path or not perm: return HttpResponse(json.dumps({"error": _('Argument missing')}), status=400, content_type=content_type) @@ -2140,6 +2146,8 @@ def remove_user_folder_permission(request, repo_id): try: seafile_api.rm_folder_user_perm(repo_id, path, user) + send_perm_audit_msg('delete-repo-perm', request.user.username, user, \ + repo_id, path, perm) return HttpResponse(json.dumps({'success': True}), content_type=content_type) except SearpcError, e: @@ -2161,7 +2169,6 @@ def toggle_user_folder_permission(request, repo_id): path = request.POST.get('path', None) perm = request.POST.get('perm', None) - print user, path, perm if not user or not path or not perm: return HttpResponse(json.dumps({"error": _('Argument missing')}), status=400, @@ -2174,6 +2181,8 @@ def toggle_user_folder_permission(request, repo_id): try: seafile_api.set_folder_user_perm(repo_id, path, perm, user) + send_perm_audit_msg('modify-repo-perm', request.user.username, user, \ + repo_id, path, perm) return HttpResponse(json.dumps({'success': True}), content_type=content_type) except SearpcError, e: @@ -2207,6 +2216,8 @@ def add_group_folder_permission(request, repo_id): try: seafile_api.add_folder_group_perm(repo_id, path, perm, group_id) + send_perm_audit_msg('add-repo-perm', request.user.username, \ + group_id, repo_id, path, perm) except SearpcError, e: return HttpResponse(json.dumps({"error": e.msg}), status=500, content_type=content_type) @@ -2225,8 +2236,9 @@ def remove_group_folder_permission(request, repo_id): group_id = int(request.GET.get('group_id', None)) path = request.GET.get('path', None) + perm = request.GET.get('perm', None) - if not group_id or not path: + if not group_id or not path or not perm: return HttpResponse(json.dumps({"error": _('Argument missing')}), status=400, content_type=content_type) @@ -2238,6 +2250,8 @@ def remove_group_folder_permission(request, repo_id): try: seafile_api.rm_folder_group_perm(repo_id, path, group_id) + send_perm_audit_msg('delete-repo-perm', request.user.username, \ + group_id, repo_id, path, perm) return HttpResponse(json.dumps({'success': True}), content_type=content_type) except SearpcError, e: @@ -2271,6 +2285,8 @@ def toggle_group_folder_permission(request, repo_id): try: seafile_api.set_folder_group_perm(repo_id, path, perm, group_id) + send_perm_audit_msg('modify-repo-perm', request.user.username, \ + group_id, repo_id, path, perm) return HttpResponse(json.dumps({'success': True}), content_type=content_type) except SearpcError, e: diff --git a/seahub/views/file.py b/seahub/views/file.py index e2ba3f5f75..5e8e76e513 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -1079,8 +1079,8 @@ def send_file_download_msg(request, repo, path, dl_type): ip = get_remote_ip(request) user_agent = request.META.get("HTTP_USER_AGENT") - msg = 'file-download-%s\t%s\t%s\t%s\t%s\t%s\t%s' % \ - (dl_type, username, ip, user_agent, repo.id, repo.name, path) + msg = 'file-download-%s\t%s\t%s\t%s\t%s\t%s' % \ + (dl_type, username, ip, user_agent, repo.id, path) msg_utf8 = msg.encode('utf-8') try: @@ -1137,6 +1137,7 @@ def download_file(request, repo_id, obj_id): # send stats message if from_shared_link: + send_file_download_msg(request, repo, path, 'share-link') try: file_size = seafile_api.get_file_size(repo.store_id, repo.version, obj_id)