diff --git a/seahub/api2/endpoints/admin/two_factor_auth.py b/seahub/api2/endpoints/admin/two_factor_auth.py new file mode 100644 index 0000000000..0bc2ad7e97 --- /dev/null +++ b/seahub/api2/endpoints/admin/two_factor_auth.py @@ -0,0 +1,35 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +from rest_framework import status +from rest_framework.permissions import IsAdminUser +from rest_framework.authentication import SessionAuthentication +from rest_framework.response import Response + +from seahub.base.accounts import User +from seahub.api2.base import APIView +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import json_response, api_error +from seahub.api2.authentication import TokenAuthentication +from seahub.utils.two_factor_auth import has_two_factor_auth, two_factor_auth_enabled + + +class TwoFactorAuthView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def delete(self, request, email): + if not email: + error_msg = "email can not be empty" + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + try: + _user = User.objects.get(email=email) + except User.DoesNotExist: + error_msg = "User %s not found" % email + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + from seahub_extra.two_factor import devices_for_user + devices = devices_for_user(_user) + if devices: + for device in devices: + device.delete() + return Response({'success':True}, status=status.HTTP_200_OK) diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index ddad0c9475..a2e39e262b 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -4,6 +4,7 @@ from django.conf.urls import patterns, url, include from .views import * from .views_misc import ServerInfoView from .views_auth import LogoutDeviceView, ClientLoginTokenView +from .endpoints.admin.two_factor_auth import TwoFactorAuthView from .endpoints.dir_shared_items import DirSharedItemsEndpoint from .endpoints.account import Account from .endpoints.shared_upload_links import SharedUploadLinksView @@ -24,6 +25,7 @@ urlpatterns = patterns('', url(r'^server-info/$', ServerInfoView.as_view()), url(r'^logout-device/$', LogoutDeviceView.as_view()), url(r'^client-login/$', ClientLoginTokenView.as_view()), + url(r'^two-factor-auth/(?P\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/$', TwoFactorAuthView.as_view(), name="two-factor-auth-view"), url(r'^device-wiped/$', RemoteWipeReportView.as_view()), url(r'^wopi/', include('seahub.wopi.urls')), diff --git a/seahub/templates/sysadmin/userinfo.html b/seahub/templates/sysadmin/userinfo.html index 997e3e3826..33bfb70b8a 100644 --- a/seahub/templates/sysadmin/userinfo.html +++ b/seahub/templates/sysadmin/userinfo.html @@ -94,6 +94,19 @@ + + {% if two_factor_auth_enabled %} +
{% trans "Two-Factor Authentication" %}
+
+ {% if default_device %} +

{% trans "Status" %}: {% trans "enabled" %}

+ + {% else %} + + {% endif %} +
+ {% endif %} +
{% csrf_token %} @@ -129,7 +142,7 @@
-
+
{% if owned_repos %} @@ -178,7 +191,7 @@ {% endif %} -
+
{% if in_repos %}
@@ -216,7 +229,7 @@ {% endif %} -
@@ -264,7 +277,7 @@ {% endif %} -
+
{% if personal_groups %}
@@ -332,6 +345,27 @@ $('#set-quota').click(function() { $('#simplemodal-container').css({'width':'auto', 'height':'auto'}); }); +$("#disable-two-factor-auth").click(function(){ + $.ajax({ + url: '{% url 'two-factor-auth-view' email %}', + type: 'DELETE', + cache: false, + beforeSend: prepareCSRFToken, + success:function(data){ + location.reload(true); + }, + error: function(xhr, textStatus, errorThrown) { + var err_msg; + if (xhr.responseText) { + err_msg = $.parseJSON(xhr.responseText).error_msg; + } else { + err_msg = "{% trans "Failed. Please check the network." %}"; + } + feedback(err_msg, 'error'); + } + }); +}); + $('#set-name-form').submit(function() { var nickname = $.trim($('[name="nickname"]', $(this)).val()); var $name = $('#nickname'); @@ -488,6 +522,7 @@ $('#set-quota-form').submit(function() { }); return false; }); + addConfirmTo($('.rm-grp'), { 'title': "{% trans "Delete Group" %}", 'con': "{% trans "Are you sure you want to delete %s ?" %}", diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index 3677e43bcd..2468b067d5 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -76,7 +76,7 @@ try: from seahub.settings import MULTI_TENANCY except ImportError: MULTI_TENANCY = False -from seahub.utils.two_factor_auth import HAS_TWO_FACTOR_AUTH +from seahub.utils.two_factor_auth import has_two_factor_auth, HAS_TWO_FACTOR_AUTH from termsandconditions.models import TermsAndConditions logger = logging.getLogger(__name__) @@ -618,6 +618,13 @@ def user_info(request, email): else: g.role = _('Member') + _default_device = False + _has_two_factor_auth = has_two_factor_auth() + if _has_two_factor_auth: + from seahub_extra.two_factor.utils import default_device + _user = User.objects.get(email=email) + _default_device = default_device(_user) + return render_to_response( 'sysadmin/userinfo.html', { 'owned_repos': owned_repos, @@ -631,6 +638,8 @@ def user_info(request, email): 'user_shared_links': user_shared_links, 'enable_sys_admin_view_repo': ENABLE_SYS_ADMIN_VIEW_REPO, 'personal_groups': personal_groups, + 'two_factor_auth_enabled': _has_two_factor_auth, + 'default_device': _default_device, }, context_instance=RequestContext(request)) @login_required_ajax diff --git a/tests/api/endpoints/admin/test_two_factor_auth.py b/tests/api/endpoints/admin/test_two_factor_auth.py new file mode 100644 index 0000000000..ce234195f7 --- /dev/null +++ b/tests/api/endpoints/admin/test_two_factor_auth.py @@ -0,0 +1,41 @@ +import os +import pytest +from django.core.urlresolvers import reverse + +from seahub.test_utils import BaseTestCase + +TRAVIS = 'TRAVIS' in os.environ + + +@pytest.mark.skipif(TRAVIS, reason="") +class TwoFactorAuthViewTest(BaseTestCase): + def setUp(self): + self.login_as(self.admin) + + def test_can_disable_two_factor_auth(self): + from seahub_extra.two_factor.models import (StaticDevice, TOTPDevice, + PhoneDevice) + totp = TOTPDevice(user=self.admin, name="", confirmed=1) + totp.save() + + from seahub_extra.two_factor import devices_for_user + devices = devices_for_user(self.admin) + i = 0 + for device in devices_for_user(self.admin): + if device: + i+=1 + assert i > 0 + resp = self.client.delete(reverse('two-factor-auth-view', args=[str(self.admin.username)])) + assert resp.status_code == 200 + i = 0 + for device in devices_for_user(self.admin): + if device: + i+=1 + assert i == 0 + + def tearDown(self): + try: + for device in devices_for_user(self.admin): + device.delete() + except: + pass