diff --git a/seahub/api2/endpoints/admin/group_libraries.py b/seahub/api2/endpoints/admin/group_libraries.py
new file mode 100644
index 0000000000..f779dec034
--- /dev/null
+++ b/seahub/api2/endpoints/admin/group_libraries.py
@@ -0,0 +1,107 @@
+# 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 %}
-
-
- |
- {% trans "Name" %} |
- {% trans "Size" %} |
- {% trans "Shared By" %} |
- {% trans "Operations" %} |
-
-
- {% for repo in repos %}
-
- {% if repo.encrypted %}
-  |
- {% elif repo.permission == 'r' %}
-  |
- {% else %}
-  |
- {% endif %}
- {% if repo.encrypted %}
- {{ repo.name }} |
- {% elif enable_sys_admin_view_repo %}
- {{ repo.name }} |
- {% else %}
- {{ repo.name }} |
- {% endif %}
- {{ repo.size|filesizeformat }} |
- {{ repo.user }} |
-
-
- |
-
- {% endfor %}
-
- {% 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 80edbf45fd..4a3d99f388 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 95454e7aa0..0887f88ba1 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