diff --git a/oioioi/base/static/scss/_layout.scss b/oioioi/base/static/scss/_layout.scss index df666f738..5fe31e6c8 100644 --- a/oioioi/base/static/scss/_layout.scss +++ b/oioioi/base/static/scss/_layout.scss @@ -22,6 +22,10 @@ $oioioi-narrow-input-max-width: 300px !default; padding-left: 0; } +.padding-bottom-md { + padding-bottom: 1em; +} + .h-scrollable { overflow-x: auto; } diff --git a/oioioi/base/templates/ingredients/navbar-user.html b/oioioi/base/templates/ingredients/navbar-user.html index 67fb49499..666fda120 100644 --- a/oioioi/base/templates/ingredients/navbar-user.html +++ b/oioioi/base/templates/ingredients/navbar-user.html @@ -12,11 +12,12 @@
{% blocktrans %}You are about to disable two-factor authentication. This + weakens your account security, are you sure?{% endblocktrans %}
+{% trans "Tokens will be generated by your token generator." %}
+ {% elif default_device_type == 'PhoneDevice' %} +{% blocktrans with primary=default_device|device_action %}Primary method: {{ primary }}{% endblocktrans %}
+ {% elif default_device_type == 'RemoteYubikeyDevice' %} +{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}
+ {% endif %} + + {% if available_phone_methods %} +{% blocktrans %}If your primary method is not available, we are able to + send backup tokens to the phone numbers listed below.{% endblocktrans %}
+{% trans "Add Phone Number" %}
+ {% endif %} + ++ {% blocktrans %}If you don't have any device with you, you can access + your account using backup tokens.{% endblocktrans %} + {% blocktrans count counter=backup_tokens %} + You have only one backup token remaining. + {% plural %} + You have {{ counter }} backup tokens remaining. + {% endblocktrans %} +
+ + +{% blocktrans %}However we strongly discourage you to do so, you can + also disable two-factor authentication for your account.{% endblocktrans %}
++ {% trans "Disable Two-Factor Authentication" %}
+ {% else %} +{% blocktrans %}Two-factor authentication is not enabled for your + account. Enable two-factor authentication for enhanced account + security.{% endblocktrans %}
+ + {% endif %} +{% endblock %} diff --git a/oioioi/base/urls.py b/oioioi/base/urls.py index 40e9bc2d8..0ed25bc0c 100644 --- a/oioioi/base/urls.py +++ b/oioioi/base/urls.py @@ -1,9 +1,10 @@ -from django.conf.urls import url +from django.conf.urls import url, include from oioioi.base import admin, views from oioioi.base.main_page import main_page_view urlpatterns = [ + url(r'', include('two_factor.urls', 'two_factor')), url(r'^force_error/$', views.force_error_view, name='force_error'), url(r'^force_permission_denied/$', views.force_permission_denied_view, name='force_permission_denied'), @@ -15,6 +16,8 @@ name='delete_account'), url(r'^generate_key/$', views.generate_key_view, name='generate_key'), +# don't include without appropriate patching! admincdocs provides its own +# login view which can be used to bypass 2FA. # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/logout/$', views.logout_view), url(r'^admin/', admin.site.urls), diff --git a/oioioi/base/views.py b/oioioi/base/views.py index 85a5fa070..80db5e410 100644 --- a/oioioi/base/views.py +++ b/oioioi/base/views.py @@ -9,8 +9,7 @@ from django.views.decorators.http import require_POST, require_GET from django.core.exceptions import SuspiciousOperation from django.core.urlresolvers import reverse, reverse_lazy -from django.contrib.auth.views import logout as auth_logout, \ - login as auth_login +from django.contrib.auth.views import logout as auth_logout from django.views.decorators.vary import vary_on_headers, vary_on_cookie from django.views.decorators.cache import cache_control from django.views.defaults import page_not_found @@ -24,8 +23,12 @@ from oioioi.base.processors import site_name import traceback +from two_factor.views import LoginView as Login2FAView + account_menu_registry.register('change_password', _("Change password"), lambda request: reverse('auth_password_change'), order=100) +account_menu_registry.register('two_factor_auth', _("Two factor authentication"), + lambda request: reverse('two_factor:profile'), order=150) class ForcedError(StandardError): @@ -113,7 +116,7 @@ def login_view(request, redirect_field_name=REDIRECT_FIELD_NAME, **kwargs): redirect_to = request.GET.get(redirect_field_name, None) return safe_redirect(request, redirect_to) else: - return auth_login(request, extra_context=site_name(request), **kwargs) + return Login2FAView.as_view(**kwargs)(request) @require_GET diff --git a/oioioi/default_settings.py b/oioioi/default_settings.py index 9b1f2f248..b209c2b29 100755 --- a/oioioi/default_settings.py +++ b/oioioi/default_settings.py @@ -144,6 +144,7 @@ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', # must be after AuthenticationMiddleware 'oioioi.base.middleware.AnnotateUserBackendMiddleware', 'oioioi.su.middleware.SuAuthenticationMiddleware', 'oioioi.su.middleware.SuFirstTimeRedirectionMiddleware', @@ -167,8 +168,9 @@ ROOT_URLCONF = 'oioioi.urls' -LOGIN_URL = '/login' +LOGIN_URL = 'two_factor:login' LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'wsgi.application' @@ -224,6 +226,11 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', + + 'django_otp', + 'django_otp.plugins.otp_static', + 'django_otp.plugins.otp_totp', + 'two_factor', ) AUTHENTICATION_BACKENDS = ( diff --git a/oioioi/maintenancemode/tests.py b/oioioi/maintenancemode/tests.py index c2a335147..b1e4a0fe8 100644 --- a/oioioi/maintenancemode/tests.py +++ b/oioioi/maintenancemode/tests.py @@ -24,7 +24,7 @@ def test_not_logged_redirect(self): self.assertRedirects(response, reverse('maintenance')) self.assertEquals(response.context['message'], 'test message') self.assertContains(response, 'test message') - response = self.client.post(reverse('login')) + response = self.client.get(reverse('login')) self.assertEquals(response.status_code, 200) def test_logged_user_redirect(self): diff --git a/setup.py b/setup.py index 888504e49..4ef1af4ee 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ author_email='sio2@sio2project.mimuw.edu.pl', url='http://sio2project.mimuw.edu.pl', install_requires=[ - "Django>=1.9,<1.10", + "Django>=1.9,<1.10", # when upgrading, upgrade also django-two-factor-auth! "pytz>=2013b", "sqlalchemy", "BeautifulSoup", @@ -39,6 +39,7 @@ # Earlier versions of django-nose are incompatible with Django 1.9 "django-nose>=1.4", "nose-picker>=0.5.3", + "django-two-factor-auth==1.5.0", # latest version for Django 1.9 "django-registration-redux>=1.6,<2.0",