From 798a69d6dbc4873e8ed59a8aa5712530a7ae74b5 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Tue, 14 Jun 2016 14:22:59 +0800 Subject: [PATCH 01/16] Fix invalid email in register issue, https://github.com/haiwen/seafile/issues/1672 --- seahub/base/accounts.py | 10 +++++++- tests/seahub/base/test_accounts.py | 37 +++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py index e720045850..ee87d2e9a7 100644 --- a/seahub/base/accounts.py +++ b/seahub/base/accounts.py @@ -1,4 +1,6 @@ # encoding: utf-8 +import re + from django import forms from django.core.mail import send_mail from django.utils import translation @@ -530,9 +532,15 @@ class RegistrationForm(forms.Form): password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False), label=_("Password (again)")) + @classmethod + def allow_register(self, email): + prog = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", + re.IGNORECASE) + return False if prog.match(email) is None else True + def clean_email(self): email = self.cleaned_data['email'] - if not is_valid_username(email): + if not self.allow_register(email): raise forms.ValidationError(_("Enter a valid email address.")) emailuser = ccnet_threaded_rpc.get_emailuser(email) diff --git a/tests/seahub/base/test_accounts.py b/tests/seahub/base/test_accounts.py index d89beadde2..842a6e7018 100644 --- a/tests/seahub/base/test_accounts.py +++ b/tests/seahub/base/test_accounts.py @@ -1,5 +1,5 @@ from seahub.test_utils import BaseTestCase -from seahub.base.accounts import User +from seahub.base.accounts import User, RegistrationForm from post_office.models import Email @@ -15,3 +15,38 @@ class UserTest(BaseTestCase): assert len(Email.objects.all()) > 0 # email = Email.objects.all()[0] # print email.html_message + + +class RegistrationFormTest(BaseTestCase): + def setUp(self): + self.valid_emails = [ + 'a@1.com', + 'a.1@1.com', + 'a+.1@1.com-pany', + 'a+-_.1@1.com-pany', + ] + + self.invalid_emails = [ + '"a"@1.com', + ' {% endblock %} diff --git a/seahub/institutions/urls.py b/seahub/institutions/urls.py index fededaf9eb..c1f828af2c 100644 --- a/seahub/institutions/urls.py +++ b/seahub/institutions/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import patterns, url -from .views import info, useradmin, user_info, user_remove, useradmin_search +from .views import (info, useradmin, user_info, user_remove, useradmin_search, + user_toggle_status) urlpatterns = patterns( '', @@ -9,4 +10,5 @@ urlpatterns = patterns( url(r'^useradmin/info/(?P[^/]+)/$', user_info, name='user_info'), url(r'^useradmin/remove/(?P[^/]+)/$', user_remove, name='user_remove'), url('^useradmin/search/$', useradmin_search, name="useradmin_search"), + url(r'^useradmin/toggle_status/(?P[^/]+)/$', user_toggle_status, name='user_toggle_status'), ) diff --git a/seahub/institutions/views.py b/seahub/institutions/views.py index b2f869c91b..316cf1530a 100644 --- a/seahub/institutions/views.py +++ b/seahub/institutions/views.py @@ -1,8 +1,9 @@ +import json import logging from django.core.urlresolvers import reverse from django.contrib import messages -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render_to_response from django.template import RequestContext from django.utils.translation import ugettext as _ @@ -10,13 +11,16 @@ import seaserv from seaserv import seafile_api from pysearpc import SearpcError +from seahub.auth.decorators import login_required_ajax from seahub.base.accounts import User from seahub.base.decorators import require_POST from seahub.base.models import UserLastLogin from seahub.institutions.decorators import (inst_admin_required, inst_admin_can_manage_user) from seahub.profile.models import Profile, DetailedProfile +from seahub.utils import is_valid_username, clear_token from seahub.utils.rpc import mute_seafile_api +from seahub.views.sysadmin import email_user_on_activation logger = logging.getLogger(__name__) @@ -181,3 +185,46 @@ def user_remove(request, email): messages.error(request, _(u'Failed to delete: the user does not exist')) return HttpResponseRedirect(next) + +@login_required_ajax +@require_POST +@inst_admin_required +@inst_admin_can_manage_user +def user_toggle_status(request, email): + content_type = 'application/json; charset=utf-8' + + if not is_valid_username(email): + return HttpResponse(json.dumps({'success': False}), status=400, + content_type=content_type) + + try: + user_status = int(request.POST.get('s', 0)) + except ValueError: + user_status = 0 + + try: + user = User.objects.get(email) + user.is_active = bool(user_status) + result_code = user.save() + if result_code == -1: + return HttpResponse(json.dumps({'success': False}), status=403, + content_type=content_type) + + if user.is_active is True: + try: + email_user_on_activation(user) + email_sent = True + except Exception as e: + logger.error(e) + email_sent = False + + return HttpResponse(json.dumps({'success': True, + 'email_sent': email_sent, + }), content_type=content_type) + else: + clear_token(user.email) + return HttpResponse(json.dumps({'success': True}), + content_type=content_type) + except User.DoesNotExist: + return HttpResponse(json.dumps({'success': False}), status=500, + content_type=content_type) diff --git a/tests/seahub/institutions/test_views.py b/tests/seahub/institutions/test_views.py index e58a25cfdb..5be6e0375a 100644 --- a/tests/seahub/institutions/test_views.py +++ b/tests/seahub/institutions/test_views.py @@ -1,8 +1,10 @@ +from django.core import mail from django.conf import settings from django.core.urlresolvers import reverse from django.http.cookie import parse_cookie from django.test import override_settings +from seahub.base.accounts import User from seahub.institutions.models import Institution, InstitutionAdmin from seahub.profile.models import Profile from seahub.test_utils import BaseTestCase @@ -67,3 +69,47 @@ class UseradminSearchTest(InstTestBase): assert resp.context['inst'] == self.inst assert len(resp.context['users']) == 2 assert resp.context['q'] == '@' + + +class UserToggleStatusTest(InstTestBase): + @override_settings( + MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES, + MULTI_INSTITUTION=True + ) + def test_can_activate(self): + self.login_as(self.user) + self.assertEqual(len(mail.outbox), 0) + + old_passwd = self.admin.enc_password + resp = self.client.post( + reverse('institutions:user_toggle_status', args=[self.admin.username]), + {'s': 1}, + HTTP_X_REQUESTED_WITH='XMLHttpRequest' + ) + self.assertEqual(200, resp.status_code) + self.assertContains(resp, '"success": true') + + u = User.objects.get(email=self.admin.username) + assert u.is_active is True + assert u.enc_password == old_passwd + self.assertEqual(len(mail.outbox), 1) + + @override_settings( + MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES, + MULTI_INSTITUTION=True + ) + def test_can_deactivate(self): + self.login_as(self.user) + + old_passwd = self.admin.enc_password + resp = self.client.post( + reverse('institutions:user_toggle_status', args=[self.admin.username]), + {'s': 0}, + HTTP_X_REQUESTED_WITH='XMLHttpRequest' + ) + self.assertEqual(200, resp.status_code) + self.assertContains(resp, '"success": true') + + u = User.objects.get(email=self.admin.username) + assert u.is_active is False + assert u.enc_password == old_passwd From d89c3bd90609c1af7b091a1398f0d669074aa603 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 8 Jul 2016 10:15:13 +0800 Subject: [PATCH 11/16] update create dir sub repo api --- seahub/api2/urls.py | 2 +- seahub/api2/views.py | 121 ++++++++++++++++++++------------- tests/api/test_dir_sub_repo.py | 118 ++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 48 deletions(-) create mode 100644 tests/api/test_dir_sub_repo.py diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index 186520bb76..51d6298b07 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -55,7 +55,7 @@ urlpatterns = patterns('', url(r'^repos/(?P[-0-9-a-f]{36})/file/revert/$', FileRevert.as_view(), name='api2-file-revert'), url(r'^repos/(?P[-0-9-a-f]{36})/file/shared-link/$', FileSharedLinkView.as_view()), url(r'^repos/(?P[-0-9-a-f]{36})/dir/$', DirView.as_view(), name='DirView'), - url(r'^repos/(?P[-0-9-a-f]{36})/dir/sub_repo/$', DirSubRepoView.as_view()), + url(r'^repos/(?P[-0-9-a-f]{36})/dir/sub_repo/$', DirSubRepoView.as_view(), name="api2-dir-sub-repo"), url(r'^repos/(?P[-0-9-a-f]{36})/dir/shared_items/$', DirSharedItemsEndpoint.as_view(), name="api2-dir-shared-items"), url(r'^repos/(?P[-0-9-a-f]{36})/dir/download/$', DirDownloadView.as_view(), name='api2-dir-download'), url(r'^repos/(?P[-0-9-a-f]{36})/dir/revert/$', DirRevert.as_view(), name='api2-dir-revert'), diff --git a/seahub/api2/views.py b/seahub/api2/views.py index abb91be3c0..990afc43b0 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -2660,67 +2660,94 @@ class DirRevert(APIView): class DirSubRepoView(APIView): - authentication_classes = (TokenAuthentication, ) + authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsAuthenticated,) - throttle_classes = (UserRateThrottle, ) + throttle_classes = (UserRateThrottle,) - # from seahub.views.ajax.py::sub_repo def get(self, request, repo_id, format=None): - ''' - check if a dir has a corresponding sub_repo - if it does not have, create one - ''' + """ Create sub-repo for folder - result = {} + Permission checking: + 1. user with `r` or `rw` permission. + 2. password correct for encrypted repo. + """ - path = request.GET.get('p') - name = request.GET.get('name') - password = request.GET.get('password', None) + # argument check + path = request.GET.get('p', None) + if not path: + error_msg = 'p invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + name = request.GET.get('name', None) + if not name: + error_msg = 'name invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # recourse check repo = get_repo(repo_id) if not repo: - result['error'] = 'Library not found.' - return HttpResponse(json.dumps(result), status=404, content_type=json_content_type) + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) - if not (path and name): - result['error'] = 'Argument missing' - return HttpResponse(json.dumps(result), status=400, content_type=json_content_type) + # permission check + if not check_folder_permission(request, repo_id, path) or \ + not request.user.permissions.can_add_repo(): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) username = request.user.username - - # check if the sub-lib exist - try: - sub_repo = seafile_api.get_virtual_repo(repo_id, path, username) - except SearpcError, e: - result['error'] = e.msg - return HttpResponse(json.dumps(result), status=500, content_type=json_content_type) - - if sub_repo: - result['sub_repo_id'] = sub_repo.id - else: - if not request.user.permissions.can_add_repo(): - return api_error(status.HTTP_403_FORBIDDEN, - 'You do not have permission to create library.') - - # create a sub-lib - try: - # use name as 'repo_name' & 'repo_desc' for sub_repo - if repo.encrypted: - if password: - sub_repo_id = seafile_api.create_virtual_repo(repo_id, - path, name, name, username, password) + password = request.GET.get('password', '') + if repo.encrypted: + # check password for encrypted repo + if not password: + error_msg = 'password invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + else: + try: + seafile_api.set_passwd(repo_id, username, password) + except SearpcError as e: + if e.msg == 'Bad arguments': + error_msg = 'Bad arguments' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + elif e.msg == 'Incorrect password': + error_msg = _(u'Wrong password') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + elif e.msg == 'Internal server error': + error_msg = _(u'Internal server error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) else: - result['error'] = 'Password Required.' - return HttpResponse(json.dumps(result), status=403, content_type=json_content_type) + error_msg = _(u'Decrypt library error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + # create sub-lib for encrypted repo + try: + if is_org_context(request): + org_id = request.user.org.org_id + sub_repo_id = seafile_api.create_org_virtual_repo( + org_id, repo_id, path, name, name, username, password) else: - sub_repo_id = seafile_api.create_virtual_repo(repo_id, path, name, name, username) + sub_repo_id = seafile_api.create_virtual_repo( + repo_id, path, name, name, username, password) + except SearpcError as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + else: + # create sub-lib for common repo + try: + if is_org_context(request): + org_id = request.user.org.org_id + sub_repo_id = seafile_api.create_org_virtual_repo( + org_id, repo_id, path, name, name, username) + else: + sub_repo_id = seafile_api.create_virtual_repo( + repo_id, path, name, name, username) + except SearpcError as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - result['sub_repo_id'] = sub_repo_id - except SearpcError, e: - result['error'] = e.msg - return HttpResponse(json.dumps(result), status=500, content_type=json_content_type) - - return HttpResponse(json.dumps(result), content_type=json_content_type) + return Response({'sub_repo_id': sub_repo_id}) ########## Sharing class SharedRepos(APIView): diff --git a/tests/api/test_dir_sub_repo.py b/tests/api/test_dir_sub_repo.py new file mode 100644 index 0000000000..5efb02e410 --- /dev/null +++ b/tests/api/test_dir_sub_repo.py @@ -0,0 +1,118 @@ +import os +import json +from django.core.urlresolvers import reverse + +from seaserv import seafile_api +from seahub.test_utils import BaseTestCase +from tests.common.utils import randstring + +try: + from seahub.settings import LOCAL_PRO_DEV_ENV +except ImportError: + LOCAL_PRO_DEV_ENV = False + +class DirSubRepoViewTest(BaseTestCase): + + def setUp(self): + self.user_name = self.user.username + self.user_repo_id = self.repo.id + self.user_folder_path = self.folder + self.user_folder_name = os.path.basename(self.folder.rstrip('/')) + + self.admin_name = self.admin.username + + self.url = reverse("api2-dir-sub-repo", args=[self.user_repo_id]) + + def tearDown(self): + self.remove_repo() + + def test_can_create_dir_sub_repo(self): + self.login_as(self.user) + + args = "?p=%s&name=%s" % (self.user_folder_path, self.user_folder_name) + resp = self.client.get(self.url + args) + json_resp = json.loads(resp.content) + assert len(json_resp['sub_repo_id']) == 36 + + def test_can_create_in_encrypted_lib(self): + + password = randstring(8) + encrypted_repo_id = seafile_api.create_repo( + 'encrypted_repo_name', '', self.user_name, password) + + dirname = randstring(8) + seafile_api.post_dir(repo_id=encrypted_repo_id, + parent_dir='/', dirname=dirname, username=self.user_name) + + self.login_as(self.user) + + url = reverse("api2-dir-sub-repo", args=[encrypted_repo_id]) + args = "?p=/%s&name=%s&password=%s" % (dirname, dirname, password) + resp = self.client.get(url + args) + json_resp = json.loads(resp.content) + assert len(json_resp['sub_repo_id']) == 36 + + self.remove_repo(encrypted_repo_id) + + def test_create_in_encrypted_lib_with_invalid_password(self): + + password = randstring(8) + encrypted_repo_id = seafile_api.create_repo( + 'encrypted_repo_name', '', self.user_name, password) + + dirname = randstring(8) + seafile_api.post_dir(repo_id=encrypted_repo_id, + parent_dir='/', dirname=dirname, username=self.user_name) + + self.login_as(self.user) + + url = reverse("api2-dir-sub-repo", args=[encrypted_repo_id]) + + # test invalid password argument + args = "?p=/%s&name=%s&invalid_password=%s" % (dirname, dirname, password) + resp = self.client.get(url + args) + self.assertEqual(400, resp.status_code) + + # test wrong password + args = "?p=/%s&name=%s&password=%s" % (dirname, dirname, 'invalid_password') + resp = self.client.get(url + args) + self.assertEqual(400, resp.status_code) + json_resp = json.loads(resp.content) + assert json_resp['error_msg'] == 'Wrong password' + + self.remove_repo(encrypted_repo_id) + + def test_create_with_invalid_repo_permission(self): + self.login_as(self.admin) + + args = "?p=%s&name=%s" % (self.user_folder_path, self.user_folder_name) + resp = self.client.get(self.url + args) + self.assertEqual(403, resp.status_code) + + def test_create_with_r_permission_folder(self): + + if not LOCAL_PRO_DEV_ENV: + return + + self.set_user_folder_r_permission_to_admin() + + self.login_as(self.admin) + + args = "?p=%s&name=%s" % (self.user_folder_path, self.user_folder_name) + resp = self.client.get(self.url + args) + json_resp = json.loads(resp.content) + assert len(json_resp['sub_repo_id']) == 36 + + def test_create_with_rw_permission_folder(self): + + if not LOCAL_PRO_DEV_ENV: + return + + self.set_user_folder_r_permission_to_admin() + + self.login_as(self.admin) + + args = "?p=%s&name=%s" % (self.user_folder_path, self.user_folder_name) + resp = self.client.get(self.url + args) + json_resp = json.loads(resp.content) + assert len(json_resp['sub_repo_id']) == 36 From 4f43f31468adb5a172cf0af4c70c694e91891ae2 Mon Sep 17 00:00:00 2001 From: lian Date: Mon, 11 Jul 2016 12:08:44 +0800 Subject: [PATCH 12/16] [api] update license info --- seahub/api2/endpoints/admin/sysinfo.py | 22 +++++++++++----- tests/api/endpoints/admin/test_sysinfo.py | 31 ++++++++++++++++++++++- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/seahub/api2/endpoints/admin/sysinfo.py b/seahub/api2/endpoints/admin/sysinfo.py index 299b59b42a..0035521689 100644 --- a/seahub/api2/endpoints/admin/sysinfo.py +++ b/seahub/api2/endpoints/admin/sysinfo.py @@ -5,7 +5,6 @@ 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 pysearpc import SearpcError @@ -15,7 +14,6 @@ from seahub.utils.licenseparse import parse_license from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle -from seahub.api2.utils import api_error import seahub.settings try: @@ -29,9 +27,14 @@ class SysInfo(APIView): """Show system info. """ authentication_classes = (TokenAuthentication, SessionAuthentication) - throttle_classes = (UserRateThrottle, ) + throttle_classes = (UserRateThrottle,) permission_classes = (IsAdminUser,) + def _get_license_dict(self): + license_file = os.path.join(seahub.settings.PROJECT_ROOT, '../../seafile-license.txt') + license_dict = parse_license(license_file) + return license_dict + def get(self, request, format=None): # count repos try: @@ -92,15 +95,20 @@ class SysInfo(APIView): is_pro = is_pro_version() if is_pro: - license_file = os.path.join(seahub.settings.PROJECT_ROOT, '../../seafile-license.txt') - license_dict = parse_license(license_file) + license_dict = self._get_license_dict() else: license_dict = {} if license_dict: with_license = True + try: + max_users = int(license_dict.get('MaxUsers', '')) + except ValueError as e: + logger.error(e) + max_users = 0 else: with_license = False + max_users = 0 info = { 'users_count': active_users + inactive_users, @@ -111,8 +119,8 @@ class SysInfo(APIView): 'multi_tenancy_enabled': multi_tenancy_enabled, 'is_pro': is_pro, 'with_license': with_license, - 'license_expiration': getattr(license_dict, 'Expiration', ''), - 'license_maxusers': getattr(license_dict, 'MaxUsers', ''), + 'license_expiration': license_dict.get('Expiration', ''), + 'license_maxusers': max_users, } return Response(info) diff --git a/tests/api/endpoints/admin/test_sysinfo.py b/tests/api/endpoints/admin/test_sysinfo.py index 11821d1d5f..b7a3c7ee8e 100644 --- a/tests/api/endpoints/admin/test_sysinfo.py +++ b/tests/api/endpoints/admin/test_sysinfo.py @@ -1,5 +1,7 @@ import json +from mock import patch + from django.core.urlresolvers import reverse from seahub.test_utils import BaseTestCase @@ -11,7 +13,10 @@ class SysinfoTest(BaseTestCase): def tearDown(self): self.remove_repo() - def test_get_sysinfo(self): + @patch('seahub.api2.endpoints.admin.sysinfo.is_pro_version') + def test_get_sysinfo_in_community_edition(self, mock_is_pro_version): + + mock_is_pro_version.return_value = False url = reverse('api-v2.1-sysinfo') resp = self.client.get(url) @@ -20,3 +25,27 @@ class SysinfoTest(BaseTestCase): assert len(json_resp) == 10 assert json_resp['is_pro'] is False assert json_resp['multi_tenancy_enabled'] is False + assert json_resp['license_maxusers'] == 0 + + @patch('seahub.api2.endpoints.admin.sysinfo.is_pro_version') + @patch('seahub.api2.endpoints.admin.sysinfo.SysInfo._get_license_dict') + def test_get_sysinfo_in_pro_edition(self, mock_get_license_dict, mock_is_pro_version): + + mock_is_pro_version.return_value = True + mock_get_license_dict.return_value = { + 'Hash': '2981bd12cf0c83c81aaa453ce249ffdd2e492ed2220f3c89c57f06518de36c487c873be960577a0534f3de4ac2bb52d3918016aaa07d60dccbce92673bc23604f4d8ff547f88287c398f74f16e114a8a3b978cce66961fd0facd283da7b050b5fc6205934420e1b4a65daf1c6dcdb2dc78e38a3799eeb5533779595912f1723129037f093f925d8ab94478c8aded304c62d003c07a6e98e706fdf81b6f73c3a806f523bbff1a92f8eb8ea325e09b2b80acfc4b99dd0f5b339d5ed832da00bad3394b9d40a09cce6066b6dc2c9b2ec47338de41867f5c2380c96f7708a5e9cdf244fbdfa1cc174751b90e74e620f53778593b84ec3b15175c3e432c20dcb4cfde', + 'Name': 'Test', + 'Mode': 'life-time', + 'Licencetype': 'User', + 'LicenceKEY': '1461659711', + 'Expiration': '2016-5-6', + 'MaxUsers': '500', + 'ProductID': 'Seafile server' + } + + url = reverse('api-v2.1-sysinfo') + resp = self.client.get(url) + json_resp = json.loads(resp.content) + + assert len(json_resp) == 10 + assert json_resp['license_maxusers'] == 500 From 8f1dbb7ea35df4b1716d575be32cd455dabc9222 Mon Sep 17 00:00:00 2001 From: lian Date: Tue, 12 Jul 2016 15:04:36 +0800 Subject: [PATCH 13/16] update search user api --- seahub/api2/endpoints/search_user.py | 30 +++++++++++-------- tests/api/{ => endpoints}/test_search_user.py | 22 ++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) rename tests/api/{ => endpoints}/test_search_user.py (85%) diff --git a/seahub/api2/endpoints/search_user.py b/seahub/api2/endpoints/search_user.py index 2d642dc57f..4c1497c806 100644 --- a/seahub/api2/endpoints/search_user.py +++ b/seahub/api2/endpoints/search_user.py @@ -8,19 +8,20 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView from rest_framework import status +from django.conf import settings + import seaserv from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error -from seahub.utils import is_org_context +from seahub.utils import is_valid_email, is_org_context from seahub.base.accounts import User from seahub.base.templatetags.seahub_tags import email2nickname from seahub.profile.models import Profile from seahub.contacts.models import Contact from seahub.avatar.templatetags.avatar_tags import api_avatar_url -from seahub.settings import ENABLE_GLOBAL_ADDRESSBOOK, ENABLE_SEARCH_FROM_LDAP_DIRECTLY class SearchUser(APIView): @@ -50,7 +51,7 @@ class SearchUser(APIView): users_result = [] username = request.user.username - if request.cloud_mode: + if settings.CLOUD_MODE: if is_org_context(request): url_prefix = request.user.org.url_prefix users = seaserv.get_org_users_by_url_prefix(url_prefix, -1, -1) @@ -61,13 +62,14 @@ class SearchUser(APIView): # when search profile, only search users in org # 'nickname__icontains' for search by nickname # 'contact_email__icontains' for search by contact email - users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & \ - (Q(nickname__icontains=q)) | \ - Q(contact_email__icontains=q)).values('user') - elif ENABLE_GLOBAL_ADDRESSBOOK: + users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & + (Q(nickname__icontains=q)) | Q(contact_email__icontains=q)).values('user') + + elif settings.ENABLE_GLOBAL_ADDRESSBOOK: users_from_ccnet = search_user_from_ccnet(q) - users_from_profile = Profile.objects.filter(Q(contact_email__icontains=q) | \ + users_from_profile = Profile.objects.filter(Q(contact_email__icontains=q) | Q(nickname__icontains=q)).values('user') + else: # in cloud mode, user will be added to Contact when share repo users = [] @@ -83,16 +85,18 @@ class SearchUser(APIView): users.append(c) users_from_ccnet = filter(lambda u: q in u.email, users) + if is_valid_email(q): + users_from_ccnet += search_user_from_ccnet(q) + # 'user__in' for only get profile of contacts # 'nickname__icontains' for search by nickname # 'contact_email__icontains' for search by contact - users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & \ - (Q(nickname__icontains=q)) | \ - Q(contact_email__icontains=q)).values('user') + users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & + (Q(nickname__icontains=q)) | Q(contact_email__icontains=q)).values('user') else: users_from_ccnet = search_user_from_ccnet(q) - users_from_profile = Profile.objects.filter(Q(contact_email__icontains=q) | \ + users_from_profile = Profile.objects.filter(Q(contact_email__icontains=q) | Q(nickname__icontains=q)).values('user') # remove inactive users and add to result @@ -158,7 +162,7 @@ def search_user_from_ccnet(q): users.extend(ldap_imported_users) count = len(users) - if count < 10 and ENABLE_SEARCH_FROM_LDAP_DIRECTLY: + if count < 10 and settings.ENABLE_SEARCH_FROM_LDAP_DIRECTLY: all_ldap_users = seaserv.ccnet_threaded_rpc.search_ldapusers(q, 0, 10 - count) users.extend(all_ldap_users) diff --git a/tests/api/test_search_user.py b/tests/api/endpoints/test_search_user.py similarity index 85% rename from tests/api/test_search_user.py rename to tests/api/endpoints/test_search_user.py index 23fe57d45f..c7cca572af 100644 --- a/tests/api/test_search_user.py +++ b/tests/api/endpoints/test_search_user.py @@ -1,6 +1,8 @@ import json +from mock import patch from django.core.urlresolvers import reverse +from django.test import override_settings from seahub.profile.models import Profile from seahub.profile.utils import refresh_cache @@ -11,6 +13,7 @@ class SearchUserTest(BaseTestCase): self.login_as(self.user) self.endpoint = reverse('search-user') + @override_settings(CLOUD_MODE = False) def test_can_search(self): email = self.admin.email nickname = 'admin_test' @@ -29,6 +32,7 @@ class SearchUserTest(BaseTestCase): assert json_resp['users'][0]['name'] == nickname assert json_resp['users'][0]['contact_email'] == contact_email + @override_settings(CLOUD_MODE = False) def test_search_myself(self): email = self.user.email nickname = 'user_test' @@ -47,6 +51,7 @@ class SearchUserTest(BaseTestCase): assert json_resp['users'][0]['name'] == nickname assert json_resp['users'][0]['contact_email'] == contact_email + @override_settings(CLOUD_MODE = False) def test_search_without_myself(self): email = self.user.email resp = self.client.get(self.endpoint + '?include_self=0&q=' + email) @@ -55,6 +60,7 @@ class SearchUserTest(BaseTestCase): self.assertEqual(200, resp.status_code) assert len(json_resp['users']) == 0 + @override_settings(CLOUD_MODE = False) def test_search_unregistered_user(self): resp = self.client.get(self.endpoint + '?q=unregistered_user@seafile.com') json_resp = json.loads(resp.content) @@ -62,6 +68,7 @@ class SearchUserTest(BaseTestCase): self.assertEqual(200, resp.status_code) assert len(json_resp['users']) == 0 + @override_settings(CLOUD_MODE = False) def test_can_search_by_nickname(self): admin_email = self.admin.email @@ -81,6 +88,7 @@ class SearchUserTest(BaseTestCase): assert json_resp['users'][0]['name'] == 'Carl Smith' assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' + @override_settings(CLOUD_MODE = False) def test_can_search_by_nickname_insensitive(self): admin_email = self.admin.email @@ -112,6 +120,7 @@ class SearchUserTest(BaseTestCase): assert json_resp['users'][0]['name'] == 'Carl Smith' assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' + @override_settings(CLOUD_MODE = False) def test_can_search_by_contact_email(self): admin_email = self.admin.email nickname = 'admin_test' @@ -132,3 +141,16 @@ class SearchUserTest(BaseTestCase): assert json_resp['users'][0]['name'] == nickname assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' + @override_settings(CLOUD_MODE = True) + @override_settings(ENABLE_GLOBAL_ADDRESSBOOK = False) + @patch('seahub.api2.endpoints.search_user.is_org_context') + def test_search_full_email(self, mock_is_org_context): + + mock_is_org_context.return_value = False + + resp = self.client.get(self.endpoint + '?q=%s' % self.admin.username) + json_resp = json.loads(resp.content) + + self.assertEqual(200, resp.status_code) + assert json_resp['users'][0]['email'] == self.admin.username + From 70adf6d552b2247cffe44aca9e6e6d5d2e73ed63 Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 13 Jul 2016 11:22:36 +0800 Subject: [PATCH 14/16] [sysadmin] add search user by name & contact email --- seahub/views/sysadmin.py | 21 ++++++-- .../seahub/views/sysadmin/test_user_search.py | 52 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/seahub/views/sysadmin/test_user_search.py diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index ff51d94e8a..c6de6e7d1e 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -11,6 +11,7 @@ import csv, chardet, StringIO import time from constance import config +from django.db.models import Q from django.conf import settings as dj_settings from django.core.urlresolvers import reverse from django.contrib import messages @@ -22,7 +23,7 @@ from django.utils.translation import ugettext as _ import seaserv from seaserv import ccnet_threaded_rpc, seafserv_threaded_rpc, \ - seafile_api, get_group, get_group_members + seafile_api, get_group, get_group_members, ccnet_api from pysearpc import SearpcError from seahub.base.accounts import User @@ -1767,10 +1768,24 @@ def user_search(request): """ email = request.GET.get('email', '') - users = ccnet_threaded_rpc.search_emailusers('DB', email, -1, -1) - ldap_users = ccnet_threaded_rpc.search_emailusers('LDAP', email, -1, -1) + # search user from ccnet db + users = ccnet_api.search_emailusers('DB', email, -1, -1) + + # search user from ccnet ldap + ldap_users = ccnet_api.search_emailusers('LDAP', email, -1, -1) users.extend(ldap_users) + # search user from profile + users_from_profile = Profile.objects.filter((Q(nickname__icontains=email)) | + Q(contact_email__icontains=email)) + + for user in users_from_profile: + try: + user_obj = User.objects.get(email=user.user) + except User.DoesNotExist: + continue + users.append(user_obj) + last_logins = UserLastLogin.objects.filter(username__in=[x.email for x in users]) if ENABLE_TRIAL_ACCOUNT: trial_users = TrialAccount.objects.filter(user_or_org__in=[x.email for x in users]) diff --git a/tests/seahub/views/sysadmin/test_user_search.py b/tests/seahub/views/sysadmin/test_user_search.py new file mode 100644 index 0000000000..d16455d929 --- /dev/null +++ b/tests/seahub/views/sysadmin/test_user_search.py @@ -0,0 +1,52 @@ +from django.core.urlresolvers import reverse + +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + +class UserResetTest(BaseTestCase): + def setUp(self): + self.user_name = self.user.username + + def test_can_search_user_from_ccnet(self): + self.login_as(self.admin) + + q = self.user_name[:3] + resp = self.client.get(reverse('user_search') + '?email=%s' % q) + self.assertEqual(200, resp.status_code) + self.assertTemplateUsed('sysadmin/user_search.html') + self.assertContains(resp, self.user_name) + + def test_can_search_user_from_profile_by_name(self): + self.login_as(self.admin) + + nickname = 'nickname' + p = Profile.objects.add_or_update(self.user_name, nickname=nickname) + p.save() + + resp = self.client.get(reverse('user_search') + '?email=%s' % nickname) + self.assertEqual(200, resp.status_code) + self.assertTemplateUsed('sysadmin/user_search.html') + self.assertContains(resp, self.user_name) + + def test_can_search_user_from_profile_by_contact_email(self): + self.login_as(self.admin) + + contact_email= 'contact@email.com' + p = Profile.objects.add_or_update(self.user_name, nickname='nickname') + p.contact_email = contact_email + p.save() + + resp = self.client.get(reverse('user_search') + + '?email=%s' % contact_email) + + self.assertEqual(200, resp.status_code) + self.assertTemplateUsed('sysadmin/user_search.html') + self.assertContains(resp, self.user_name) + + def test_search_user_with_invalid_user_permission(self): + self.login_as(self.user) + + resp = self.client.get(reverse('user_search') + + '?email=%s' % self.user_name) + + self.assertEqual(404, resp.status_code) From fcd3e836e397efb061057fb766613faa180f6530 Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 13 Jul 2016 16:31:05 +0800 Subject: [PATCH 15/16] [api] return licensed to info when get sysinfo --- seahub/api2/endpoints/admin/sysinfo.py | 1 + seahub/templates/js/sysadmin-templates.html | 2 ++ tests/api/endpoints/admin/test_sysinfo.py | 9 ++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/seahub/api2/endpoints/admin/sysinfo.py b/seahub/api2/endpoints/admin/sysinfo.py index 0035521689..336e34425c 100644 --- a/seahub/api2/endpoints/admin/sysinfo.py +++ b/seahub/api2/endpoints/admin/sysinfo.py @@ -121,6 +121,7 @@ class SysInfo(APIView): 'with_license': with_license, 'license_expiration': license_dict.get('Expiration', ''), 'license_maxusers': max_users, + 'license_to': license_dict.get('Name', ''), } return Response(info) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index c06920519f..53a6dc4ca4 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -73,6 +73,8 @@
<% if (is_pro) { %> {% trans "Professional Edition" %} <% if (with_license) { %> + {% trans "licensed to" %} <%- license_to %> + , {% trans "expires on" %} <%- license_expiration %> <% } %> <% } else { %> diff --git a/tests/api/endpoints/admin/test_sysinfo.py b/tests/api/endpoints/admin/test_sysinfo.py index b7a3c7ee8e..478c849bfe 100644 --- a/tests/api/endpoints/admin/test_sysinfo.py +++ b/tests/api/endpoints/admin/test_sysinfo.py @@ -22,7 +22,7 @@ class SysinfoTest(BaseTestCase): resp = self.client.get(url) json_resp = json.loads(resp.content) - assert len(json_resp) == 10 + assert len(json_resp) == 11 assert json_resp['is_pro'] is False assert json_resp['multi_tenancy_enabled'] is False assert json_resp['license_maxusers'] == 0 @@ -31,10 +31,12 @@ class SysinfoTest(BaseTestCase): @patch('seahub.api2.endpoints.admin.sysinfo.SysInfo._get_license_dict') def test_get_sysinfo_in_pro_edition(self, mock_get_license_dict, mock_is_pro_version): + test_user = 'Test user' + mock_is_pro_version.return_value = True mock_get_license_dict.return_value = { 'Hash': '2981bd12cf0c83c81aaa453ce249ffdd2e492ed2220f3c89c57f06518de36c487c873be960577a0534f3de4ac2bb52d3918016aaa07d60dccbce92673bc23604f4d8ff547f88287c398f74f16e114a8a3b978cce66961fd0facd283da7b050b5fc6205934420e1b4a65daf1c6dcdb2dc78e38a3799eeb5533779595912f1723129037f093f925d8ab94478c8aded304c62d003c07a6e98e706fdf81b6f73c3a806f523bbff1a92f8eb8ea325e09b2b80acfc4b99dd0f5b339d5ed832da00bad3394b9d40a09cce6066b6dc2c9b2ec47338de41867f5c2380c96f7708a5e9cdf244fbdfa1cc174751b90e74e620f53778593b84ec3b15175c3e432c20dcb4cfde', - 'Name': 'Test', + 'Name': test_user, 'Mode': 'life-time', 'Licencetype': 'User', 'LicenceKEY': '1461659711', @@ -47,5 +49,6 @@ class SysinfoTest(BaseTestCase): resp = self.client.get(url) json_resp = json.loads(resp.content) - assert len(json_resp) == 10 + assert len(json_resp) == 11 assert json_resp['license_maxusers'] == 500 + assert json_resp['license_to'] == test_user From 12fd7a93d923150fc3b3e98f65b1cbf8601f070e Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 13 Jul 2016 18:08:28 +0800 Subject: [PATCH 16/16] [sysadmin] update rm org user --- seahub/templates/sysadmin/sys_org_info_user.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/seahub/templates/sysadmin/sys_org_info_user.html b/seahub/templates/sysadmin/sys_org_info_user.html index 7745b088f8..f49df4a31c 100644 --- a/seahub/templates/sysadmin/sys_org_info_user.html +++ b/seahub/templates/sysadmin/sys_org_info_user.html @@ -45,8 +45,8 @@ {% if not user.is_self %} - {% trans "Delete" %} - {% trans "ResetPwd" %} + {% trans "Delete" %} + {% trans "ResetPwd" %} {% endif %} @@ -63,12 +63,14 @@