From 718bdbb59b58014cdcc555b3812f8f86a2f77965 Mon Sep 17 00:00:00 2001 From: lian Date: Thu, 9 Feb 2017 14:59:49 +0800 Subject: [PATCH 1/2] update admin manage group page --- .../api2/endpoints/admin/group_libraries.py | 109 +++++++++ seahub/api2/endpoints/admin/group_members.py | 223 ++++++++++++++++++ seahub/api2/endpoints/group_members.py | 13 +- seahub/group/utils.py | 20 +- .../templates/institutions/user_info.html | 2 +- seahub/templates/js/sysadmin-templates.html | 124 +++++++++- .../sysadmin/sys_admin_group_info.html | 90 ------- seahub/templates/sysadmin/userinfo.html | 2 +- seahub/urls.py | 7 +- seahub/views/sysadmin.py | 20 -- .../scripts/app/views/group-manage-members.js | 2 +- static/scripts/app/views/group-settings.js | 2 +- static/scripts/common.js | 14 ++ .../sysadmin-app/collection/group-members.js | 29 +++ .../sysadmin-app/collection/group-repos.js | 29 +++ .../sysadmin-app/models/group-member.js | 12 + .../scripts/sysadmin-app/models/group-repo.js | 25 ++ static/scripts/sysadmin-app/models/repo.js | 10 +- static/scripts/sysadmin-app/router.js | 20 +- .../sysadmin-app/views/group-member.js | 118 +++++++++ .../sysadmin-app/views/group-members.js | 187 +++++++++++++++ .../scripts/sysadmin-app/views/group-repo.js | 75 ++++++ .../scripts/sysadmin-app/views/group-repos.js | 102 ++++++++ .../endpoints/admin/test_group_libraries.py | 64 +++++ .../api/endpoints/admin/test_group_members.py | 104 ++++++++ tests/ui/test_sudo_mode.py | 6 - 26 files changed, 1265 insertions(+), 144 deletions(-) create mode 100644 seahub/api2/endpoints/admin/group_libraries.py create mode 100644 seahub/api2/endpoints/admin/group_members.py delete mode 100644 seahub/templates/sysadmin/sys_admin_group_info.html create mode 100644 static/scripts/sysadmin-app/collection/group-members.js create mode 100644 static/scripts/sysadmin-app/collection/group-repos.js create mode 100644 static/scripts/sysadmin-app/models/group-member.js create mode 100644 static/scripts/sysadmin-app/models/group-repo.js create mode 100644 static/scripts/sysadmin-app/views/group-member.js create mode 100644 static/scripts/sysadmin-app/views/group-members.js create mode 100644 static/scripts/sysadmin-app/views/group-repo.js create mode 100644 static/scripts/sysadmin-app/views/group-repos.js create mode 100644 tests/api/endpoints/admin/test_group_libraries.py create mode 100644 tests/api/endpoints/admin/test_group_members.py diff --git a/seahub/api2/endpoints/admin/group_libraries.py b/seahub/api2/endpoints/admin/group_libraries.py new file mode 100644 index 0000000000..d10fc5dfee --- /dev/null +++ b/seahub/api2/endpoints/admin/group_libraries.py @@ -0,0 +1,109 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging + +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 rest_framework import status + +import seaserv +from seaserv import seafile_api, ccnet_api + +from seahub.utils import is_org_context + +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_group_repo_info(repo): + result = {} + result['repo_id'] = repo.repo_id + result['name'] = repo.repo_name + result['size'] = repo.size + result['shared_by'] = repo.user + result['permission'] = repo.permission + result['group_id'] = repo.group_id + result['encrypted'] = repo.encrypted + + return result + + +class AdminGroupLibraries(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request, group_id, format=None): + """ List all group repos + + Permission checking: + 1. only admin can perform this action. + """ + + group_id = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + if is_org_context(request): + org_id = request.user.org.org_id + repos = seafile_api.get_org_group_repos(org_id, group_id) + else: + repos = seafile_api.get_repos_by_group(group_id) + + group_repos_info = [] + for repo in repos: + repo_info = get_group_repo_info(repo) + group_repos_info.append(repo_info) + + group_libraries = { + 'group_id': group_id, + 'group_name': group.group_name, + 'libraries': group_repos_info + } + + return Response(group_libraries) + + +class AdminGroupLibrary(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def delete(self, request, group_id, repo_id, format=None): + """ Unshare repo from group + + Permission checking: + 1. only admin can perform this action. + """ + + 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 = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + if is_org_context(request): + org_id = request.user.org.org_id + seaserv.del_org_group_repo(repo_id, org_id, group_id) + else: + repo_owner = seafile_api.get_repo_owner(repo_id) + 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/group_members.py b/seahub/api2/endpoints/admin/group_members.py new file mode 100644 index 0000000000..ae118b1ed0 --- /dev/null +++ b/seahub/api2/endpoints/admin/group_members.py @@ -0,0 +1,223 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging + +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 rest_framework import status + +from seaserv import seafile_api, ccnet_api + +from seahub.group.utils import get_group_member_info, is_group_member +from seahub.avatar.settings import AVATAR_DEFAULT_SIZE +from seahub.base.accounts import User + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +logger = logging.getLogger(__name__) + + +class AdminGroupMembers(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def get(self, request, group_id, format=None): + """ List all group members + + Permission checking: + 1. only admin can perform this action. + """ + + group_id = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + avatar_size = int(request.GET.get('avatar_size', + AVATAR_DEFAULT_SIZE)) + except ValueError: + avatar_size = AVATAR_DEFAULT_SIZE + + try: + members = ccnet_api.get_group_members(group_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + group_members_info = [] + for m in members: + member_info = get_group_member_info(request, group_id, m.user_name, avatar_size) + group_members_info.append(member_info) + + group_members = { + 'group_id': group_id, + 'group_name': group.group_name, + 'members': group_members_info + } + + return Response(group_members) + + def post(self, request, group_id): + """ + Bulk add group members. + + Permission checking: + 1. only admin can perform this action. + """ + + # argument check + group_id = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + emails = request.POST.getlist('email', '') + if not emails: + error_msg = 'Email invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + emails_need_add = [] + + for email in emails: + try: + User.objects.get(email=email) + except User.DoesNotExist: + result['failed'].append({ + 'email': email, + 'error_msg': 'User %s not found.' % email + }) + continue + + if ccnet_api.is_group_user(group_id, email): + result['failed'].append({ + 'email': email, + 'error_msg': 'User %s is already a group member.' % email + }) + continue + + emails_need_add.append(email) + + # Add user to group. + for email in emails_need_add: + try: + ccnet_api.group_add_member(group_id, group.creator_name, email) + member_info = get_group_member_info(request, group_id, email) + result['success'].append(member_info) + except Exception as e: + logger.error(e) + result['failed'].append({ + 'email': email, + 'error_msg': 'Internal Server Error' + }) + + return Response(result) + + +class AdminGroupMember(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def put(self, request, group_id, email, format=None): + """ update role of a group member + + Permission checking: + 1. only admin can perform this action. + """ + + # argument check + group_id = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + User.objects.get(email=email) + except User.DoesNotExist: + error_msg = 'User %s not found.' % email + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + if not is_group_member(group_id, email): + error_msg = 'Email %s invalid.' % email + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + is_admin = request.data.get('is_admin', '') + try: + # set/unset a specific group member as admin + if is_admin.lower() == 'true': + ccnet_api.group_set_admin(group_id, email) + elif is_admin.lower() == 'false': + ccnet_api.group_unset_admin(group_id, email) + else: + error_msg = 'is_admin invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + member_info = get_group_member_info(request, group_id, email) + return Response(member_info) + + def delete(self, request, group_id, email, format=None): + """ Delete an user from group + + Permission checking: + 1. only admin can perform this action. + """ + + # argument check + group_id = int(group_id) + group = ccnet_api.get_group(group_id) + if not group: + error_msg = 'Group %d not found.' % group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + try: + User.objects.get(email=email) + except User.DoesNotExist: + error_msg = 'User %s not found.' % email + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # delete member from group + try: + if not is_group_member(group_id, email): + return Response({'success': True}) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + if group.creator_name == email: + error_msg = '%s is group owner, can not be removed.' % email + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + try: + ccnet_api.group_remove_member(group_id, group.creator_name, email) + # remove repo-group share info of all 'email' owned repos + seafile_api.remove_group_repos_by_owner(group_id, email) + 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/group_members.py b/seahub/api2/endpoints/group_members.py index eb4f57bfd1..14b237af61 100644 --- a/seahub/api2/endpoints/group_members.py +++ b/seahub/api2/endpoints/group_members.py @@ -50,7 +50,7 @@ class GroupMembers(APIView): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - members = seaserv.get_group_members(group_id) + members = ccnet_api.get_group_members(group_id) except SearpcError as e: logger.error(e) @@ -169,13 +169,12 @@ class GroupMember(APIView): # set/unset a specific group member as admin if is_admin.lower() == 'true': - seaserv.ccnet_threaded_rpc.group_set_admin(group_id, email) + ccnet_api.group_set_admin(group_id, email) elif is_admin.lower() == 'false': - seaserv.ccnet_threaded_rpc.group_unset_admin(group_id, email) + ccnet_api.group_unset_admin(group_id, email) else: error_msg = 'is_admin invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' @@ -204,7 +203,7 @@ class GroupMember(APIView): # user leave group if username == email: try: - seaserv.ccnet_threaded_rpc.quit_group(group_id, username) + ccnet_api.quit_group(group_id, username) # remove repo-group share info of all 'email' owned repos seafile_api.remove_group_repos_by_owner(group_id, email) return Response({'success': True}) @@ -217,14 +216,14 @@ class GroupMember(APIView): try: if is_group_owner(group_id, username): # group owner can delete all group member - seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email) + ccnet_api.group_remove_member(group_id, username, email) seafile_api.remove_group_repos_by_owner(group_id, email) return Response({'success': True}) elif is_group_admin(group_id, username): # group admin can NOT delete group owner/admin if not is_group_admin_or_owner(group_id, email): - seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email) + ccnet_api.group_remove_member(group_id, username, email) seafile_api.remove_group_repos_by_owner(group_id, email) return Response({'success': True}) else: diff --git a/seahub/group/utils.py b/seahub/group/utils.py index 9a4bb05bd8..5b8f1d0091 100644 --- a/seahub/group/utils.py +++ b/seahub/group/utils.py @@ -4,6 +4,7 @@ import re import logging import seaserv +from seaserv import ccnet_api from seahub.utils import is_org_context from seahub.profile.models import Profile @@ -44,7 +45,7 @@ def check_group_name_conflict(request, new_group_name): if request.cloud_mode: checked_groups = seaserv.get_personal_groups_by_user(username) else: - checked_groups = seaserv.ccnet_threaded_rpc.get_all_groups(-1, -1) + checked_groups = ccnet_api.get_all_groups(-1, -1) for g in checked_groups: if g.group_name == new_group_name: @@ -53,13 +54,13 @@ def check_group_name_conflict(request, new_group_name): return False def is_group_member(group_id, email): - return seaserv.is_group_user(group_id, email) + return ccnet_api.is_group_user(int(group_id), email) def is_group_admin(group_id, email): - return seaserv.check_group_staff(group_id, email) + return ccnet_api.check_group_staff(int(group_id), email) def is_group_owner(group_id, email): - group = seaserv.get_group(group_id) + group = ccnet_api.get_group(int(group_id)) if email == group.creator_name: return True else: @@ -85,14 +86,23 @@ def get_group_member_info(request, group_id, email, avatar_size=AVATAR_DEFAULT_S logger.error(e) avatar_url = get_default_avatar_url() - is_admin = seaserv.check_group_staff(group_id, email) + role = 'Member' + group = ccnet_api.get_group(int(group_id)) + is_admin = ccnet_api.check_group_staff(int(group_id), email) + if email == group.creator_name: + role = 'Owner' + elif is_admin: + role = 'Admin' + member_info = { + 'group_id': group_id, "name": email2nickname(email), 'email': email, "contact_email": Profile.objects.get_contact_email_by_user(email), "login_id": login_id, "avatar_url": request.build_absolute_uri(avatar_url), "is_admin": is_admin, + "role": role, } return member_info diff --git a/seahub/institutions/templates/institutions/user_info.html b/seahub/institutions/templates/institutions/user_info.html index aadb5988c5..c59dbf6356 100644 --- a/seahub/institutions/templates/institutions/user_info.html +++ b/seahub/institutions/templates/institutions/user_info.html @@ -119,7 +119,7 @@ {% for group in personal_groups %} - {{ group.group_name }} + {{ group.group_name }} {{ group.role }} {{ group.timestamp|tsstr_sec }} diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index 3cbac5d3aa..0c72041fb3 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -598,7 +598,7 @@ + + + + + + + + + + + + diff --git a/seahub/templates/sysadmin/sys_admin_group_info.html b/seahub/templates/sysadmin/sys_admin_group_info.html deleted file mode 100644 index 8d4f95b2bc..0000000000 --- a/seahub/templates/sysadmin/sys_admin_group_info.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends "sysadmin/base.html" %} -{% load i18n group_avatar_tags avatar_tags seahub_tags %} -{% load url from future %} - - -{% block right_panel %} - -

- Groups - / - {{ group.group_name }} -

- -
- - -
- {% if repos %} - - - - - - - - - - {% for repo in repos %} - - {% if repo.encrypted %} - - {% elif repo.permission == 'r' %} - - {% else %} - - {% endif %} - {% if repo.encrypted %} - - {% elif enable_sys_admin_view_repo %} - - {% else %} - - {% endif %} - - - - - {% endfor %} -
{% trans "Name" %}{% trans "Size" %}{% trans "Shared By" %}{% trans "Operations" %}
{% trans {% trans {% trans {{ repo.name }}{{ repo.name }}{{ repo.name }}{{ repo.size|filesizeformat }}{{ repo.user }} -
-
- {% else %} -
-

{% trans "No library has shared to this group" %}

-
- {% endif %} -
- -
- -
-
- -{% endblock %} - - -{% block extra_script %} - -{% endblock %} diff --git a/seahub/templates/sysadmin/userinfo.html b/seahub/templates/sysadmin/userinfo.html index 15e452c38c..1e85d11079 100644 --- a/seahub/templates/sysadmin/userinfo.html +++ b/seahub/templates/sysadmin/userinfo.html @@ -256,7 +256,7 @@ {% for group in personal_groups %} - {{ group.group_name }} + {{ group.group_name }} {{ group.role }} {{ group.timestamp|tsstr_sec }} {% trans "Delete" %} diff --git a/seahub/urls.py b/seahub/urls.py index 6f7802651d..f4f7181396 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -48,6 +48,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.group_libraries import AdminGroupLibraries, AdminGroupLibrary +from seahub.api2.endpoints.admin.group_members import AdminGroupMembers, AdminGroupMember from seahub.api2.endpoints.admin.shares import AdminShares # Uncomment the next two lines to enable the admin: @@ -214,6 +216,10 @@ urlpatterns = patterns( url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/dirents/$', AdminLibraryDirents.as_view(), name='api-v2.1-admin-library-dirents'), url(r'^api/v2.1/admin/groups/$', AdminGroups.as_view(), name='api-v2.1-admin-groups'), url(r'^api/v2.1/admin/groups/(?P\d+)/$', AdminGroup.as_view(), name='api-v2.1-admin-group'), + url(r'^api/v2.1/admin/groups/(?P\d+)/libraries/$', AdminGroupLibraries.as_view(), name='api-v2.1-admin-group-libraries'), + url(r'^api/v2.1/admin/groups/(?P\d+)/libraries/(?P[-0-9a-f]{36})/$', AdminGroupLibrary.as_view(), name='api-v2.1-admin-group-library'), + url(r'^api/v2.1/admin/groups/(?P\d+)/members/$', AdminGroupMembers.as_view(), name='api-v2.1-admin-group-members'), + url(r'^api/v2.1/admin/groups/(?P\d+)/members/(?P[^/]+)/$', AdminGroupMember.as_view(), name='api-v2.1-admin-group-member'), url(r'^api/v2.1/admin/libraries/(?P[-0-9a-f]{36})/dirent/$', AdminLibraryDirent.as_view(), name='api-v2.1-admin-library-dirent'), 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'), @@ -253,7 +259,6 @@ urlpatterns = patterns( url(r'^sys/useradmin/ldap/imported$', sys_user_admin_ldap_imported, name='sys_useradmin_ldap_imported'), url(r'^sys/useradmin/admins/$', sys_user_admin_admins, name='sys_useradmin_admins'), url(r'^sys/groupadmin/export-excel/$', sys_group_admin_export_excel, name='sys_group_admin_export_excel'), - url(r'^sys/groupadmin/(?P\d+)/$', sys_admin_group_info, name='sys_admin_group_info'), url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'), url(r'^sys/orgadmin/search/$', sys_org_search, name='sys_org_search'), url(r'^sys/orgadmin/(?P\d+)/set_quota/$', sys_org_set_quota, name='sys_org_set_quota'), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index 5122a742ea..68fe518f9b 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -1064,26 +1064,6 @@ def sys_group_admin_export_excel(request): wb.save(response) return response -@login_required -@sys_staff_required -def sys_admin_group_info(request, group_id): - - group_id = int(group_id) - group = get_group(group_id) - org_id = request.GET.get('org_id', None) - if org_id: - repos = seafile_api.get_org_group_repos(org_id, group_id) - else: - repos = seafile_api.get_repos_by_group(group_id) - members = get_group_members(group_id) - - return render_to_response('sysadmin/sys_admin_group_info.html', { - 'group': group, - 'repos': repos, - 'members': members, - 'enable_sys_admin_view_repo': ENABLE_SYS_ADMIN_VIEW_REPO, - }, context_instance=RequestContext(request)) - @login_required @sys_staff_required def sys_org_admin(request): diff --git a/static/scripts/app/views/group-manage-members.js b/static/scripts/app/views/group-manage-members.js index 5f037cc42b..4a8bf8d334 100644 --- a/static/scripts/app/views/group-manage-members.js +++ b/static/scripts/app/views/group-manage-members.js @@ -167,7 +167,7 @@ define([ var err_str = ''; if (data.failed.length > 0) { $(data.failed).each(function(index, item) { - err_str += item.email + ': ' + item.error_msg + '
'; + err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '
'; }); _this.$error.html(err_str).show(); } diff --git a/static/scripts/app/views/group-settings.js b/static/scripts/app/views/group-settings.js index fdb5677146..c2464f3283 100644 --- a/static/scripts/app/views/group-settings.js +++ b/static/scripts/app/views/group-settings.js @@ -270,7 +270,7 @@ define([ if (data.failed.length > 0) { var err_msg = ''; $(data.failed).each(function(index, item) { - err_msg += item.email + ': ' + item.error_msg + '
'; + err_msg += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '
'; }); $error.html(err_msg).removeClass('hide'); Common.enableButton($submitBtn); diff --git a/static/scripts/common.js b/static/scripts/common.js index 7d27072344..8bc66ceb55 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -179,6 +179,10 @@ define([ case 'admin-library-dirents': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/dirents/'; case 'admin-groups': return siteRoot + 'api/v2.1/admin/groups/'; case 'admin-group': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/'; + case 'admin-group-libraries': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/'; + case 'admin-group-library': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/' + options.repo_id + '/'; + case 'admin-group-members': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/'; + case 'admin-group-member': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/' + options.email+ '/'; 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 + '/'; @@ -286,6 +290,16 @@ define([ } }, + getLibIconTitle: function(is_encrypted, is_readonly) { + if (is_encrypted) { + return gettext("Encrypted library"); + } else if (is_readonly) { + return gettext("Read-Only library"); + } else { + return gettext("Read-Write library"); + } + }, + isHiDPI: function() { var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1; if (pixelRatio > 1) { diff --git a/static/scripts/sysadmin-app/collection/group-members.js b/static/scripts/sysadmin-app/collection/group-members.js new file mode 100644 index 0000000000..27a150846e --- /dev/null +++ b/static/scripts/sysadmin-app/collection/group-members.js @@ -0,0 +1,29 @@ +define([ + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/models/group-member' +], function(_, Backbone, Common, GroupMemberModel) { + + 'use strict'; + + var GroupMemberCollection = Backbone.Collection.extend({ + + model: GroupMemberModel, + + setGroupId: function(group_id) { + this.group_id = group_id; + }, + + parse: function (data) { + this.group_name= data.group_name; + return data.members; // return the array + }, + + url: function () { + return Common.getUrl({name: 'admin-group-members', group_id: this.group_id}); + } + }); + + return GroupMemberCollection; +}); diff --git a/static/scripts/sysadmin-app/collection/group-repos.js b/static/scripts/sysadmin-app/collection/group-repos.js new file mode 100644 index 0000000000..4fec1500fe --- /dev/null +++ b/static/scripts/sysadmin-app/collection/group-repos.js @@ -0,0 +1,29 @@ +define([ + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/models/group-repo' +], function(_, Backbone, Common, GroupRepoModel) { + + 'use strict'; + + var GroupRepoCollection = Backbone.Collection.extend({ + + model: GroupRepoModel, + + setGroupId: function(group_id) { + this.group_id = group_id; + }, + + parse: function (data) { + this.group_name= data.group_name; + return data.libraries; // return the array + }, + + url: function () { + return Common.getUrl({name: 'admin-group-libraries', group_id: this.group_id}); + } + }); + + return GroupRepoCollection; +}); diff --git a/static/scripts/sysadmin-app/models/group-member.js b/static/scripts/sysadmin-app/models/group-member.js new file mode 100644 index 0000000000..c40016c51b --- /dev/null +++ b/static/scripts/sysadmin-app/models/group-member.js @@ -0,0 +1,12 @@ +define([ + 'underscore', + 'backbone', + 'common' +], function(_, Backbone, Common) { + + 'use strict'; + + var GroupMember = Backbone.Model.extend({}); + + return GroupMember; +}); diff --git a/static/scripts/sysadmin-app/models/group-repo.js b/static/scripts/sysadmin-app/models/group-repo.js new file mode 100644 index 0000000000..2c410429a4 --- /dev/null +++ b/static/scripts/sysadmin-app/models/group-repo.js @@ -0,0 +1,25 @@ +define([ + 'underscore', + 'backbone', + 'common' +], function(_, Backbone, Common) { + + 'use strict'; + + var GroupRepo = Backbone.Model.extend({ + + getIconUrl: function(size) { + var is_encrypted = this.get('encrypted'); + var is_readonly = this.get('permission') == "r" ? true : false; + return Common.getLibIconUrl(is_encrypted, is_readonly, size); + }, + + getIconTitle: function() { + var is_encrypted = this.get('encrypted'); + var is_readonly = this.get('permission') == "r" ? true : false; + return Common.getLibIconTitle(is_encrypted, is_readonly); + } + }); + + return GroupRepo; +}); diff --git a/static/scripts/sysadmin-app/models/repo.js b/static/scripts/sysadmin-app/models/repo.js index 496e0df5a2..f78364e93c 100644 --- a/static/scripts/sysadmin-app/models/repo.js +++ b/static/scripts/sysadmin-app/models/repo.js @@ -12,14 +12,8 @@ define([ }, getIconTitle: function() { - var icon_title = ''; - if (this.get('encrypted')) { - icon_title = gettext("Encrypted library"); - } else { - icon_title = gettext("Read-Write library"); - } - - return icon_title; + var is_encrypted = this.get('encrypted'); + return Common.getLibIconTitle(is_encrypted, false); } }); diff --git a/static/scripts/sysadmin-app/router.js b/static/scripts/sysadmin-app/router.js index 2d93c5c33e..46ce2b1870 100644 --- a/static/scripts/sysadmin-app/router.js +++ b/static/scripts/sysadmin-app/router.js @@ -16,12 +16,14 @@ define([ 'sysadmin-app/views/dir', 'sysadmin-app/views/groups', 'sysadmin-app/views/search-groups', + 'sysadmin-app/views/group-repos', + 'sysadmin-app/views/group-members', 'app/views/account' ], function($, Backbone, Common, SideNavView, DashboardView, DesktopDevicesView, MobileDevicesView, DeviceErrorsView, ReposView, SearchReposView, SystemReposView, TrashReposView, SearchTrashReposView, DirView, GroupsView, SearchGroupsView, - AccountView) { + GroupReposView, GroupMembersView, AccountView) { "use strict"; @@ -40,6 +42,8 @@ define([ 'libs/:repo_id(/*path)': 'showLibraryDir', 'groups/': 'showGroups', 'search-groups/': 'showSearchGroups', + 'groups/:group_id/libs/': 'showGroupLibraries', + 'groups/:group_id/members/': 'showGroupMembers', // Default '*actions': 'showDashboard' }, @@ -69,6 +73,8 @@ define([ this.groupsView = new GroupsView(); this.searchGroupsView = new SearchGroupsView(); + this.groupReposView = new GroupReposView(); + this.groupMembersView = new GroupMembersView(); app.ui.accountView = this.accountView = new AccountView(); @@ -205,6 +211,18 @@ define([ this.searchGroupsView.show({ 'name': decodeURIComponent(group_name) }); + }, + + showGroupLibraries: function(group_id) { + this.switchCurrentView(this.groupReposView); + this.sideNavView.setCurTab('groups'); + this.groupReposView.show(group_id); + }, + + showGroupMembers: function(group_id) { + this.switchCurrentView(this.groupMembersView); + this.sideNavView.setCurTab('groups'); + this.groupMembersView.show(group_id); } }); diff --git a/static/scripts/sysadmin-app/views/group-member.js b/static/scripts/sysadmin-app/views/group-member.js new file mode 100644 index 0000000000..a6db75be9c --- /dev/null +++ b/static/scripts/sysadmin-app/views/group-member.js @@ -0,0 +1,118 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { + + 'use strict'; + + var GroupMemberView = HLItemView.extend({ + + tagName: 'tr', + + template: _.template($('#group-member-item-tmpl').html()), + + events: { + 'click .user-role-edit-icon': 'showEdit', + 'change .user-role-select': 'editRole', + 'click .member-delete-btn': 'deleteGroupMember' + }, + + initialize: function() { + HLItemView.prototype.initialize.call(this); + this.listenTo(this.model, 'change', this.render); + + var _this = this; + $(document).on('click', function(e) { + var target = e.target || event.srcElement; + if (!_this.$('.user-role-edit-icon, .user-role-select').is(target)) { + _this.$('.cur-role, .user-role-edit-icon').show(); + _this.$('.user-role-select').hide(); + } + }); + }, + + showEdit: function() { + this.$('.cur-role, .user-role-edit-icon').hide(); + this.$('.user-role-select').show(); + }, + + editRole: function() { + var _this = this; + + // '0': member, '1': admin + var val = this.$('[name="role"]').val(); + var is_admin = val == 1 ? true : false; + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-group-member', + 'group_id': _this.model.get('group_id'), + 'email': _this.model.get('email') + }), + type: 'put', + dataType: 'json', + beforeSend: Common.prepareCSRFToken, + data: { + 'is_admin': is_admin + }, + success: function(data) { + _this.model.set({ + 'is_admin': data['is_admin'], + 'role': data['role'] + }); + }, + error: function(xhr) { + var err_msg; + if (xhr.responseText) { + err_msg = $.parseJSON(xhr.responseText).error_msg; + } else { + err_msg = gettext("Failed. Please check the network."); + } + Common.feedback(err_msg, 'error'); + } + }); + }, + + deleteGroupMember: function() { + var _this = this; + var email = this.model.get('email'); + var popupTitle = gettext("Delete Member"); + var popupContent = gettext("Are you sure you want to delete %s ?").replace('%s', '' + Common.HTMLescape(email) + ''); + var yesCallback = function() { + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-group-member', + 'group_id': _this.model.get('group_id'), + 'email': email + }), + type: 'DELETE', + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function() { + _this.$el.remove(); + var msg = gettext("Successfully deleted member {placeholder}").replace('{placeholder}', email); + Common.feedback(msg, 'success'); + }, + error: function(xhr, textStatus, errorThrown) { + Common.ajaxErrorHandler(xhr, textStatus, errorThrown); + }, + complete: function() { + $.modal.close(); + } + }); + }; + Common.showConfirm(popupTitle, popupContent, yesCallback); + return false; + }, + + render: function() { + this.$el.html(this.template(this.model.toJSON())); + return this; + } + + }); + + return GroupMemberView; +}); diff --git a/static/scripts/sysadmin-app/views/group-members.js b/static/scripts/sysadmin-app/views/group-members.js new file mode 100644 index 0000000000..79d2bcb66f --- /dev/null +++ b/static/scripts/sysadmin-app/views/group-members.js @@ -0,0 +1,187 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/views/group-member', + 'sysadmin-app/collection/group-members' +], function($, _, Backbone, Common, GroupMemberView, GroupMemberCollection) { + + 'use strict'; + + var GroupMembersView = Backbone.View.extend({ + + id: 'admin-groups', + + tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()), + template: _.template($("#group-members-tmpl").html()), + addMemberFormTemplate: _.template($('#add-group-member-form-tmpl').html()), + + initialize: function() { + this.groupMemberCollection = new GroupMemberCollection(); + this.listenTo(this.groupMemberCollection, 'add', this.addOne); + this.listenTo(this.groupMemberCollection, 'reset', this.reset); + }, + + events: { + 'click #js-add-group-member': 'addGroupMember' + }, + + addGroupMember: function () { + var $form = $(this.addMemberFormTemplate()), + _this = this; + + $form.modal(); + $('#simplemodal-container').css({'height':'auto', 'width':'auto'}); + + $('[name="email"]', $form).select2($.extend( + Common.contactInputOptionsForSelect2(), { + width: '275px', + containerCss: {'margin-bottom': '5px'}, + placeholder: gettext("Search user or enter email and press Enter") + })); + + $form.submit(function() { + var group_id = _this.groupMemberCollection.group_id; + var emails = $.trim($('[name="email"]', $form).val()); + var $error = $('.error', $form); + var $submitBtn = $('[type="submit"]', $form); + + if (!emails) { + $error.html(gettext("Email is required.")).show(); + return false; + } + + var input_emails = []; + var emails_list = emails.split(','); + for (var i = 0; i < emails_list.length; i++) { + input_emails.push(emails_list[i]); + } + + $error.hide(); + Common.disableButton($submitBtn); + + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-group-members', + 'group_id': group_id + }), + type: 'POST', + dataType: 'json', + beforeSend: Common.prepareCSRFToken, + traditional: true, + data: {'email': input_emails}, + success: function(data) { + if (data.success.length > 0) { + _this.groupMemberCollection.add(data.success, {prepend: true}); + } + + var err_str = ''; + if (data.failed.length > 0) { + $(data.failed).each(function(index, item) { + err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '
'; + }); + $error.html(err_str).show(); + Common.enableButton($submitBtn); + } else { + Common.closeModal(); + } + + }, + error: function(jqXHR, textStatus, errorThrown){ + var err_msg; + if (jqXHR.responseText) { + err_msg = jqXHR.responseJSON.error_msg; + } else { + err_msg = gettext('Please check the network.'); + } + $error.html(err_msg).show(); + Common.enableButton($submitBtn); + } + }); + return false; + }); + return false; + }, + + render: function() { + var group_id = this.groupMemberCollection.group_id; + this.$el.html(this.tabNavTemplate({'cur_tab': 'members', 'group_id': group_id}) + this.template()); + + this.$table = this.$('table'); + this.$tableBody = $('tbody', this.$table); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); + }, + + initPage: function() { + this.$table.hide(); + this.$tableBody.empty(); + this.$loadingTip.show(); + this.$emptyTip.hide(); + }, + + hide: function() { + this.$el.detach(); + this.attached = false; + }, + + show: function(group_id) { + if (!this.attached) { + this.attached = true; + $("#right-panel").html(this.$el); + } + + // init collection + this.groupMemberCollection.setGroupId(group_id); + this.render(); + this.showGroupMembers(); + }, + + showGroupMembers: function() { + this.initPage(); + var _this = this; + + this.groupMemberCollection.fetch({ + cache: false, + reset: true, + error: function(collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + Common.feedback(err_msg, 'error'); + } + }); + }, + + reset: function() { + this.$loadingTip.hide(); + if (this.groupMemberCollection.length > 0) { + this.groupMemberCollection.each(this.addOne, this); + this.$table.show(); + } else { + this.$emptyTip.show(); + } + this.$('.path-bar').append(this.groupMemberCollection.group_name); + }, + + addOne: function(item, collection, options) { + var view = new GroupMemberView({model: item}); + if (options.prepend) { + this.$tableBody.prepend(view.render().el); + } else { + this.$tableBody.append(view.render().el); + } + } + }); + + return GroupMembersView; + +}); diff --git a/static/scripts/sysadmin-app/views/group-repo.js b/static/scripts/sysadmin-app/views/group-repo.js new file mode 100644 index 0000000000..b47fbffbb5 --- /dev/null +++ b/static/scripts/sysadmin-app/views/group-repo.js @@ -0,0 +1,75 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { + + 'use strict'; + + var GroupRepoView = HLItemView.extend({ + + tagName: 'tr', + + template: _.template($('#group-library-item-tmpl').html()), + + events: { + 'click .repo-unshare-btn': 'unshareGroupLibrary' + }, + + initialize: function() { + HLItemView.prototype.initialize.call(this); + }, + + unshareGroupLibrary: function() { + var _this = this; + var repo_name = this.model.get('name'); + var popupTitle = gettext("Unshare Library"); + var popupContent = gettext("Are you sure you want to unshare %s ?").replace('%s', '' + Common.HTMLescape(repo_name) + ''); + var yesCallback = function() { + $.ajax({ + url: Common.getUrl({ + 'name': 'admin-group-library', + 'group_id': _this.model.get('group_id'), + 'repo_id': _this.model.get('repo_id') + }), + type: 'DELETE', + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function() { + _this.$el.remove(); + var msg = gettext("Successfully unshared library {placeholder}").replace('{placeholder}', repo_name); + Common.feedback(msg, 'success'); + }, + error: function(xhr, textStatus, errorThrown) { + Common.ajaxErrorHandler(xhr, textStatus, errorThrown); + }, + complete: function() { + $.modal.close(); + } + }); + }; + Common.showConfirm(popupTitle, popupContent, yesCallback); + return false; + }, + + render: function() { + var data = this.model.toJSON(), + icon_size = Common.isHiDPI() ? 96 : 24, + icon_url = this.model.getIconUrl(icon_size); + + data['icon_url'] = icon_url; + data['icon_title'] = this.model.getIconTitle(); + data['formatted_size'] = Common.fileSizeFormat(data['size'], 1), + data['enable_sys_admin_view_repo'] = app.pageOptions.enable_sys_admin_view_repo; + data['is_pro'] = app.pageOptions.is_pro; + this.$el.html(this.template(data)); + + return this; + } + + }); + + return GroupRepoView; +}); diff --git a/static/scripts/sysadmin-app/views/group-repos.js b/static/scripts/sysadmin-app/views/group-repos.js new file mode 100644 index 0000000000..b59c6e37f5 --- /dev/null +++ b/static/scripts/sysadmin-app/views/group-repos.js @@ -0,0 +1,102 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/views/group-repo', + 'sysadmin-app/collection/group-repos' +], function($, _, Backbone, Common, GroupRepoView, GroupRepoCollection) { + + 'use strict'; + + var GroupReposView = Backbone.View.extend({ + + id: 'admin-groups', + + tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()), + template: _.template($("#group-libraries-tmpl").html()), + + initialize: function() { + this.groupRepoCollection = new GroupRepoCollection(); + this.listenTo(this.groupRepoCollection, 'add', this.addOne); + this.listenTo(this.groupRepoCollection, 'reset', this.reset); + }, + + render: function() { + var group_id = this.groupRepoCollection.group_id; + this.$el.html(this.tabNavTemplate({'cur_tab': 'libs', 'group_id': group_id}) + this.template()); + + this.$table = this.$('table'); + this.$tableBody = $('tbody', this.$table); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); + }, + + initPage: function() { + this.$table.hide(); + this.$tableBody.empty(); + this.$loadingTip.show(); + this.$emptyTip.hide(); + }, + + hide: function() { + this.$el.detach(); + this.attached = false; + }, + + show: function(group_id) { + if (!this.attached) { + this.attached = true; + $("#right-panel").html(this.$el); + } + + // init collection + this.groupRepoCollection.setGroupId(group_id); + this.render(); + this.showGroupLibraries(); + }, + + showGroupLibraries: function() { + this.initPage(); + var _this = this; + + this.groupRepoCollection.fetch({ + cache: false, + reset: true, + error: function(collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + Common.feedback(err_msg, 'error'); + } + }); + }, + + reset: function() { + this.$loadingTip.hide(); + if (this.groupRepoCollection.length > 0) { + this.groupRepoCollection.each(this.addOne, this); + this.$table.show(); + } else { + this.$emptyTip.show(); + } + + this.$('.path-bar').append(this.groupRepoCollection.group_name); + }, + + addOne: function(library) { + var view = new GroupRepoView({model: library}); + this.$tableBody.append(view.render().el); + } + }); + + return GroupReposView; + +}); diff --git a/tests/api/endpoints/admin/test_group_libraries.py b/tests/api/endpoints/admin/test_group_libraries.py new file mode 100644 index 0000000000..d8e6a22adf --- /dev/null +++ b/tests/api/endpoints/admin/test_group_libraries.py @@ -0,0 +1,64 @@ +import json + +from seaserv import seafile_api +from django.core.urlresolvers import reverse +from seahub.test_utils import BaseTestCase + +class GroupLibrariesTest(BaseTestCase): + + def setUp(self): + self.user_name = self.user.username + self.admin_name = self.admin.username + self.group_id = self.group.id + self.repo_id = self.repo.id + + seafile_api.set_group_repo(self.repo_id, self.group_id, + self.admin.username, 'r') + + def tearDown(self): + self.remove_group() + + def test_can_get(self): + self.login_as(self.admin) + url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id]) + resp = self.client.get(url) + + json_resp = json.loads(resp.content) + assert json_resp['libraries'][0]['repo_id'] == self.repo_id + assert json_resp['group_id'] == self.group_id + + def test_can_not_get_if_not_admin(self): + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id]) + resp = self.client.get(url) + self.assertEqual(403, resp.status_code) + + def test_can_unshare(self): + + # make sure repo is shared to group + repos = seafile_api.get_repos_by_group(self.group_id) + assert len(repos) == 1 + + self.login_as(self.admin) + url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id]) + resp = self.client.delete(url) + self.assertEqual(200, resp.status_code) + + # make sure repo is unshared + repos = seafile_api.get_repos_by_group(self.group_id) + assert len(repos) == 0 + + def test_can_not_unshare_if_not_admin(self): + + # make sure repo is shared to group + repos = seafile_api.get_repos_by_group(self.group_id) + assert len(repos) == 1 + + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id]) + resp = self.client.delete(url) + self.assertEqual(403, resp.status_code) + + # make sure repo is unshared + repos = seafile_api.get_repos_by_group(self.group_id) + assert len(repos) == 1 diff --git a/tests/api/endpoints/admin/test_group_members.py b/tests/api/endpoints/admin/test_group_members.py new file mode 100644 index 0000000000..dc38e85716 --- /dev/null +++ b/tests/api/endpoints/admin/test_group_members.py @@ -0,0 +1,104 @@ +import json + +from seaserv import ccnet_api +from django.core.urlresolvers import reverse +from seahub.test_utils import BaseTestCase + +class GroupMembersTest(BaseTestCase): + + def setUp(self): + self.user_name = self.user.username + self.admin_name = self.admin.username + self.group_id = self.group.id + self.repo_id = self.repo.id + + def tearDown(self): + self.remove_group() + + def test_can_get(self): + self.login_as(self.admin) + url = reverse('api-v2.1-admin-group-members', + args=[self.group_id]) + resp = self.client.get(url) + + json_resp = json.loads(resp.content) + assert json_resp['members'][0]['email'] == self.user_name + assert json_resp['group_id'] == self.group_id + + def test_can_not_get_if_not_admin(self): + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-members', + args=[self.group_id]) + resp = self.client.get(url) + self.assertEqual(403, resp.status_code) + + def test_can_add(self): + self.login_as(self.admin) + url = reverse('api-v2.1-admin-group-members', + args=[self.group_id]) + + data = {'email': self.admin_name} + resp = self.client.post(url, data) + + json_resp = json.loads(resp.content) + assert json_resp['success'][0]['group_id'] == self.group_id + assert json_resp['success'][0]['email'] == self.admin_name + + def test_can_not_add_if_not_admin(self): + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-members', + args=[self.group_id]) + + data = {'email': self.admin_name} + resp = self.client.post(url, data) + self.assertEqual(403, resp.status_code) + + def test_can_delete_group_member(self): + + ccnet_api.group_add_member(self.group_id, self.user_name, + self.admin_name) + + # make sure member in group + members = ccnet_api.get_group_members(self.group_id) + assert len(members) == 2 + + self.login_as(self.admin) + url = reverse('api-v2.1-admin-group-member', + args=[self.group_id, self.admin_name]) + resp = self.client.delete(url) + self.assertEqual(200, resp.status_code) + + # make sure member is deleted + members = ccnet_api.get_group_members(self.group_id) + assert len(members) == 1 + + def test_can_not_delete_if_not_admin(self): + + ccnet_api.group_add_member(self.group_id, self.user_name, + self.admin_name) + + # make sure member in group + members = ccnet_api.get_group_members(self.group_id) + assert len(members) == 2 + + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-member', + args=[self.group_id, self.admin_name]) + resp = self.client.delete(url) + self.assertEqual(403, resp.status_code) + + # make sure member is not deleted + members = ccnet_api.get_group_members(self.group_id) + assert len(members) == 2 + + def test_can_not_delete_group_owner(self): + + # make sure member in group + members = ccnet_api.get_group_members(self.group_id) + assert len(members) == 1 + + self.login_as(self.user) + url = reverse('api-v2.1-admin-group-member', + args=[self.group_id, self.user_name]) + resp = self.client.delete(url) + self.assertEqual(403, resp.status_code) diff --git a/tests/ui/test_sudo_mode.py b/tests/ui/test_sudo_mode.py index 2849059c37..4ae3f30b0a 100644 --- a/tests/ui/test_sudo_mode.py +++ b/tests/ui/test_sudo_mode.py @@ -21,12 +21,6 @@ def test_sudo_mode_required(admin_browser_once): 'the browser should be redirected back to the previous page' ) - b.visit('/sys/groupadmin/') - assert b.path == '/sys/groupadmin/', ( - 'once the admin enters the password, ' - 'he would not be asked again within a certain time' - ) - @pytest.mark.xfail def test_sudo_mode_rejects_wrong_password(admin_browser_once): b = admin_browser_once From b0f2ddab0053db51851ad91ff881d9a3e49c2f99 Mon Sep 17 00:00:00 2001 From: lian Date: Thu, 9 Feb 2017 18:01:04 +0800 Subject: [PATCH 2/2] update --- seahub/api2/endpoints/admin/group_libraries.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/seahub/api2/endpoints/admin/group_libraries.py b/seahub/api2/endpoints/admin/group_libraries.py index d10fc5dfee..f779dec034 100644 --- a/seahub/api2/endpoints/admin/group_libraries.py +++ b/seahub/api2/endpoints/admin/group_libraries.py @@ -6,12 +6,10 @@ from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status - import seaserv from seaserv import seafile_api, ccnet_api from seahub.utils import is_org_context - from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error