diff --git a/seahub/api2/endpoints/admin/library_group_shares.py b/seahub/api2/endpoints/admin/library_group_shares.py new file mode 100644 index 0000000000..982645d4f7 --- /dev/null +++ b/seahub/api2/endpoints/admin/library_group_shares.py @@ -0,0 +1,223 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView + +from seaserv import seafile_api, ccnet_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +logger = logging.getLogger(__name__) + +def get_library_group_share_info(share_item): + + group_id = share_item.group_id + group = ccnet_api.get_group(group_id) + + result = {} + result['group_id'] = group_id + result['group_name'] = group.group_name + result['permission'] = share_item.perm + result['repo_id'] = share_item.repo_id + + return result + + +class AdminLibraryGroupShares(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request, repo_id): + """ List all group shares of a repo + + Permission checking: + 1. admin user. + """ + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + try: + share_items = seafile_api.list_repo_shared_group_by_user(repo_owner, 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) + + result = [] + for share_item in share_items: + share_item_info = get_library_group_share_info(share_item) + result.append(share_item_info) + + return Response(result) + + def post(self, request, repo_id): + """ Admin share a library to group. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + group_ids = request.data.getlist('group_id') + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + + for group_id in group_ids: + try: + group_id = int(group_id) + except ValueError as e: + logger.error(e) + result['failed'].append({ + 'group_id': group_id, + 'error_msg': 'group_id %s invalid.' % group_id + }) + + continue + + group = ccnet_api.get_group(group_id) + if not group: + result['failed'].append({ + 'group_id': group_id, + 'error_msg': 'Group %s not found' % group_id + }) + + continue + + try: + seafile_api.set_group_repo(repo_id, group_id, repo_owner, permission) + except Exception as e: + logger.error(e) + result['failed'].append({ + "group_id": group_id, + 'error_msg': 'Internal Server Error' + }) + + continue + + result['success'].append({ + "group_id": group_id, + "group_name": group.group_name, + "permission": permission, + "repo_id": repo_id, + }) + + return Response(result) + +class AdminLibraryGroupShare(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def put(self, request, repo_id, format=None): + """ Update library group share permission. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group_id = request.data.get('group_id', None) + + try: + group_id = int(group_id) + except ValueError: + error_msg = 'group_id %s invalid.' % group_id + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %s not found' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + seafile_api.set_group_repo_permission(group_id, repo_id, permission) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + result = {} + result['group_id'] = group_id + result['group_name'] = group.group_name + result['permission'] = permission + result['repo_id'] = repo_id + + return Response(result) + + def delete(self, request, repo_id, format=None): + """ Delete library group share permission. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + group_id = request.data.get('group_id') + try: + group_id = int(group_id) + except ValueError: + return api_error(status.HTTP_400_BAD_REQUEST, 'group_id %s invalid' % group_id) + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + + try: + seafile_api.unset_group_repo(repo_id, group_id, repo_owner) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) diff --git a/seahub/api2/endpoints/admin/library_user_shares.py b/seahub/api2/endpoints/admin/library_user_shares.py new file mode 100644 index 0000000000..5e74efc9b1 --- /dev/null +++ b/seahub/api2/endpoints/admin/library_user_shares.py @@ -0,0 +1,233 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView +from seaserv import seafile_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +from seahub.base.accounts import User +from seahub.base.templatetags.seahub_tags import email2nickname + +from seahub.utils import (is_valid_username) + +logger = logging.getLogger(__name__) + +def get_library_user_share_info(share_item): + result = {} + result['user_email'] = share_item.user + result['user_name'] = email2nickname(share_item.user) + result['permission'] = share_item.perm + result['repo_id'] = share_item.repo_id + + return result + + +class AdminLibraryUserShares(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request, repo_id): + """ List all user shares of a repo + + Permission checking: + 1. admin user. + """ + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + try: + share_items = seafile_api.list_repo_shared_to(repo_owner, 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) + + result = [] + for share_item in share_items: + share_item_info = get_library_user_share_info(share_item) + result.append(share_item_info) + + return Response(result) + + def post(self, request, repo_id): + """ Admin share a library to user. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + share_to_users = request.data.getlist('email') + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + + for to_user in share_to_users: + if repo_owner == to_user: + result['failed'].append({ + 'user_email': to_user, + 'error_msg': 'email %s is library owner.' % to_user + }) + + continue + + if not is_valid_username(to_user): + result['failed'].append({ + 'user_email': to_user, + 'error_msg': 'email %s invalid.' % to_user + }) + + continue + + try: + User.objects.get(email=to_user) + except User.DoesNotExist: + result['failed'].append({ + 'user_email': to_user, + 'error_msg': 'User %s not found.' % to_user + }) + + continue + + try: + seafile_api.share_repo(repo_id, + repo_owner, to_user, permission) + except Exception as e: + logger.error(e) + result['failed'].append({ + 'user_email': to_user, + 'error_msg': 'Internal Server Error' + }) + + continue + + new_perm = seafile_api.check_permission_by_path(repo_id, '/', to_user) + result['success'].append({ + "repo_id": repo_id, + "user_email": to_user, + "user_name": email2nickname(to_user), + "permission": new_perm, + }) + + return Response(result) + +class AdminLibraryUserShare(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def put(self, request, repo_id, format=None): + """ Update library user share permission. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + to_user = request.data.get('user_email', None) + if not to_user or not is_valid_username(to_user): + error_msg = 'user_email invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + User.objects.get(email=to_user) + except User.DoesNotExist: + error_msg = 'User %s not found.' % to_user + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + + try: + seafile_api.set_share_permission( + repo_id, repo_owner, to_user, permission) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + new_perm = seafile_api.check_permission_by_path(repo_id, '/', to_user) + result = {} + result['user_email'] = to_user + result['user_name'] = email2nickname(to_user) + result['permission'] = new_perm + result['repo_id'] = repo_id + + return Response(result) + + def delete(self, request, repo_id, format=None): + """ Delete library user share permission. + + Permission checking: + 1. admin user. + """ + + # argument check + permission = request.data.get('permission', None) + if not permission or permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + to_user = request.data.get('user_email', None) + if not to_user or not is_valid_username(to_user): + error_msg = 'user_email invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # current `request.user.username` is admin user, + # so need to identify the repo owner specifically. + repo_owner = seafile_api.get_repo_owner(repo_id) + try: + seafile_api.remove_share(repo_id, repo_owner, to_user) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) diff --git a/seahub/api2/endpoints/search_group.py b/seahub/api2/endpoints/search_group.py new file mode 100644 index 0000000000..ff43b469e6 --- /dev/null +++ b/seahub/api2/endpoints/search_group.py @@ -0,0 +1,81 @@ +# Copyright (c) 2012-2016 Seafile Ltd. + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +from django.conf import settings + +from seaserv import ccnet_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +from seahub.utils import is_org_context +from seahub.utils.timeutils import timestamp_to_isoformat_timestr + + +def get_group_info(group_id): + group = ccnet_api.get_group(group_id) + isoformat_timestr = timestamp_to_isoformat_timestr(group.timestamp) + group_info = { + "id": group.id, + "name": group.group_name, + "owner": group.creator_name, + "created_at": isoformat_timestr, + } + + return group_info + +class SearchGroup(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def _can_use_global_address_book(self, request): + + return request.user.permissions.can_use_global_address_book() + + def get(self, request, format=None): + """ Search group. + + Permission checking: + 1. default(NOT guest) user; + """ + + # argument check + q = request.GET.get('q', None) + if not q: + error_msg = 'q invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # permission check + if not self._can_use_global_address_book(request): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if not settings.ENABLE_GLOBAL_ADDRESSBOOK: + error_msg = 'Feature disabled.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if is_org_context(request): + org_id = request.user.org.org_id + groups = ccnet_api.get_org_groups(org_id, -1, -1) + else: + groups = ccnet_api.get_all_groups(-1, -1) + + result = [] + for group in groups: + group_name = group.group_name + if not group_name: + continue + + if q.lower() in group_name.lower(): + group_info = get_group_info(group.id) + result.append(group_info) + + return Response(result) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index 4c69f1ae68..c9b6e305c1 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -372,6 +372,9 @@ + <% if (!encrypted) { %> + + <% } %> @@ -559,3 +562,106 @@ + + + + diff --git a/seahub/urls.py b/seahub/urls.py index 9cc2e7c753..565ecfe594 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -20,6 +20,7 @@ from seahub.views.sysadmin import * from seahub.views.ajax import * from seahub.api2.endpoints.groups import Groups, Group from seahub.api2.endpoints.group_members import GroupMembers, GroupMembersBulk, GroupMember +from seahub.api2.endpoints.search_group import SearchGroup from seahub.api2.endpoints.share_links import ShareLinks, ShareLink from seahub.api2.endpoints.shared_folders import SharedFolders from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo @@ -46,6 +47,8 @@ from seahub.api2.endpoints.admin.library_dirents import AdminLibraryDirents, Adm from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup +from seahub.api2.endpoints.admin.library_user_shares import AdminLibraryUserShares, AdminLibraryUserShare +from seahub.api2.endpoints.admin.library_group_shares import AdminLibraryGroupShares, AdminLibraryGroupShare # Uncomment the next two lines to enable the admin: #from django.contrib import admin @@ -186,6 +189,7 @@ urlpatterns = patterns( url(r'^api/v2.1/groups/(?P\d+)/members/$', GroupMembers.as_view(), name='api-v2.1-group-members'), url(r'^api/v2.1/groups/(?P\d+)/members/bulk/$', GroupMembersBulk.as_view(), name='api-v2.1-group-members-bulk'), url(r'^api/v2.1/groups/(?P\d+)/members/(?P[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'), + url(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'), url(r'^api/v2.1/shared-folders/$', SharedFolders.as_view(), name='api-v2.1-shared-folders'), url(r'^api/v2.1/shared-repos/$', SharedRepos.as_view(), name='api-v2.1-shared-repos'), url(r'^api/v2.1/shared-repos/(?P[-0-9a-f]{36})/$', SharedRepo.as_view(), name='api-v2.1-shared-repo'), @@ -205,7 +209,6 @@ urlpatterns = patterns( url(r'^api/v2.1/admin/device-errors/$', AdminDeviceErrors.as_view(), name='api-v2.1-admin-device-errors'), url(r'^api/v2.1/invitations/$', InvitationsView.as_view()), url(r'^api/v2.1/invitations/(?P[a-f0-9]{32})/$', InvitationView.as_view()), - url(r'^api/v2.1/admin/libraries/$', AdminLibraries.as_view(), name='api-v2.1-admin-libraries'), url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/$', AdminLibrary.as_view(), name='api-v2.1-admin-library'), url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/dirents/$', AdminLibraryDirents.as_view(), name='api-v2.1-admin-library-dirents'), @@ -215,6 +218,10 @@ urlpatterns = patterns( url(r'^api/v2.1/admin/system-library/$', AdminSystemLibrary.as_view(), name='api-v2.1-admin-system-library'), url(r'^api/v2.1/admin/trash-libraries/$', AdminTrashLibraries.as_view(), name='api-v2.1-admin-trash-libraries'), url(r'^api/v2.1/admin/trash-libraries/(?P[-0-9a-f]{36})/$', AdminTrashLibrary.as_view(), name='api-v2.1-admin-trash-library'), + url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/user-shares/$', AdminLibraryUserShares.as_view(), name='api-v2.1-admin-library-user-shares'), + url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/user-share/$', AdminLibraryUserShare.as_view(), name='api-v2.1-admin-library-user-share'), + url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/group-shares/$', AdminLibraryGroupShares.as_view(), name='api-v2.1-admin-library-group-shares'), + url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/group-share/$', AdminLibraryGroupShare.as_view(), name='api-v2.1-admin-library-group-share'), (r'^avatar/', include('seahub.avatar.urls')), (r'^notification/', include('seahub.notifications.urls')), diff --git a/static/scripts/common.js b/static/scripts/common.js index c30269b1c4..defe7ae589 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -141,6 +141,7 @@ define([ // Group case 'groups': return siteRoot + 'api/v2.1/groups/'; + case 'search_group': return siteRoot + 'api/v2.1/search-group/'; case 'group': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/'; case 'group_members': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/'; case 'group_member': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/' + options.email + '/'; @@ -180,6 +181,10 @@ define([ case 'admin-system-library': return siteRoot + 'api/v2.1/admin/system-library/'; case 'admin-trash-libraries': return siteRoot + 'api/v2.1/admin/trash-libraries/'; case 'admin-trash-library': return siteRoot + 'api/v2.1/admin/trash-libraries/' + options.repo_id + '/'; + case 'admin_library_user_shares': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/user-shares/'; + case 'admin_library_user_share': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/user-share/'; + case 'admin_library_group_shares': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/group-shares/'; + case 'admin_library_group_share': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/group-share/'; } }, @@ -634,6 +639,73 @@ define([ } }, + groupInputOptionsForSelect2: function() { + var _this = this; + return { + placeholder: gettext("Search groups"), + + // with 'tags', the user can directly enter, not just select + // tags need ``, not `