diff options
author | Michał Górny <mgorny@gentoo.org> | 2013-08-08 16:01:11 +0200 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2013-08-09 22:39:53 +0200 |
commit | 29b63498ad9db38f268aa29a3fcc0af1b9806e1a (patch) | |
tree | 47302a64b4b5492b9b7534989221cd8469853eab /okupy | |
parent | Merge pull request #49 from mgorny/merged-settings (diff) | |
download | identity.gentoo.org-29b63498ad9db38f268aa29a3fcc0af1b9806e1a.tar.gz identity.gentoo.org-29b63498ad9db38f268aa29a3fcc0af1b9806e1a.tar.bz2 identity.gentoo.org-29b63498ad9db38f268aa29a3fcc0af1b9806e1a.zip |
Introduce initial code for two-phase auth support.
This commit adds a simple NoOTPDevice model that currently serves
the purpose of responding successfully to any request. The login view
has been extended with proper OTP device setup and initial verification
support.
Diffstat (limited to 'okupy')
-rw-r--r-- | okupy/accounts/views.py | 13 | ||||
-rw-r--r-- | okupy/otp/__init__.py | 28 | ||||
-rw-r--r-- | okupy/otp/nootp/__init__.py | 0 | ||||
-rw-r--r-- | okupy/otp/nootp/models.py | 11 | ||||
-rw-r--r-- | okupy/settings/__init__.py | 2 | ||||
-rw-r--r-- | okupy/tests/settings.py | 3 | ||||
-rw-r--r-- | okupy/tests/unit/views.py | 17 |
7 files changed, 64 insertions, 10 deletions
diff --git a/okupy/accounts/views.py b/okupy/accounts/views.py index f57b1fb..66e8c1c 100644 --- a/okupy/accounts/views.py +++ b/okupy/accounts/views.py @@ -4,7 +4,6 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth import (login as _login, logout as _logout, authenticate) -from django.contrib.auth.decorators import login_required from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.db import IntegrityError @@ -15,6 +14,7 @@ from django.shortcuts import redirect, render from django.utils.html import format_html from django.utils.http import urlencode from django.views.decorators.csrf import csrf_exempt +from django_otp.decorators import otp_required from openid.extensions.ax import FetchRequest, FetchResponse from openid.extensions.sreg import SRegRequest, SRegResponse @@ -31,6 +31,7 @@ from .openid_store import DjangoDBOpenIDStore from ..common.ldap_helpers import get_ldap_connection from ..common.exceptions import OkupyError from ..common.log import log_extra_data +from ..otp import init_otp # the following two are for exceptions import openid.yadis.discover @@ -56,7 +57,7 @@ class DevListsView(View): return render(request, self.template_name, {'devlist': devlist}) -@login_required +@otp_required def index(request): anon_ldap_user = get_ldap_connection() results = anon_ldap_user.search_s(settings.AUTH_LDAP_USER_DN_TEMPLATE % { @@ -151,8 +152,12 @@ def login(request): if user and user.is_active: _login(request, user) - if request.user.is_authenticated(): + # prepare devices, and see if OTP is enabled + init_otp(request) + if request.user.is_verified(): return redirect(next) + if request.user.is_authenticated(): + raise NotImplementedError('OTP form not implemented yet') if login_form is None: login_form = LoginForm() @@ -431,7 +436,7 @@ openid_ax_attribute_mapping = { } -@login_required +@otp_required def openid_auth_site(request): try: oreq = request.session['openid_request'] diff --git a/okupy/otp/__init__.py b/okupy/otp/__init__.py new file mode 100644 index 0000000..810a2e3 --- /dev/null +++ b/okupy/otp/__init__.py @@ -0,0 +1,28 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django_otp import login as otp_login +from django_otp.middleware import OTPMiddleware + +from .nootp.models import NoOTPDevice + +def init_otp(request): + """ + Initialize OTP after login. This sets up OTP devices + for django_otp and calls the middleware to fill + request.user.is_verified(). + """ + + nodev, created = NoOTPDevice.objects.get_or_create( + user=request.user, + defaults={ + 'name': 'OTP-disabled pass-through', + }) + if created: + nodev.save() + + # nootp may match already + if nodev.verify_token(): + otp_login(request, nodev) + + # add .is_verified() + OTPMiddleware().process_request(request) diff --git a/okupy/otp/nootp/__init__.py b/okupy/otp/nootp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/okupy/otp/nootp/__init__.py diff --git a/okupy/otp/nootp/models.py b/okupy/otp/nootp/models.py new file mode 100644 index 0000000..0a5b0af --- /dev/null +++ b/okupy/otp/nootp/models.py @@ -0,0 +1,11 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django_otp.models import Device + +class NoOTPDevice(Device): + """ A fake OTP device that successfully verifies token + if user has OTP disabled. """ + + def verify_token(self, token=None): + # TODO: put some real code + return True diff --git a/okupy/settings/__init__.py b/okupy/settings/__init__.py index da2a240..eaa496e 100644 --- a/okupy/settings/__init__.py +++ b/okupy/settings/__init__.py @@ -141,6 +141,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -155,6 +156,7 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'okupy.accounts', + 'okupy.otp.nootp', ) #Compressor settings diff --git a/okupy/tests/settings.py b/okupy/tests/settings.py index c6e625d..a002253 100644 --- a/okupy/tests/settings.py +++ b/okupy/tests/settings.py @@ -139,6 +139,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -159,7 +160,9 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_otp', 'okupy.accounts', + 'okupy.otp.nootp', 'okupy.tests' ) diff --git a/okupy/tests/unit/views.py b/okupy/tests/unit/views.py index a564170..0eeb5aa 100644 --- a/okupy/tests/unit/views.py +++ b/okupy/tests/unit/views.py @@ -5,13 +5,20 @@ from django.contrib.auth.models import AnonymousUser from django.core.urlresolvers import resolve from django.test import TestCase, RequestFactory +from django_otp.middleware import OTPMiddleware + from ...accounts.views import login, index, signup from ...accounts.forms import LoginForm -class LoginViewTests(TestCase): - request = RequestFactory().get('/login') +def anon_request(uri): + request = RequestFactory().get(uri) request.session = {} request.user = AnonymousUser() + OTPMiddleware().process_request(request) + return request + +class LoginViewTests(TestCase): + request = anon_request('/login') response = login(request) def test_login_url_resolves_to_login_view(self): @@ -25,8 +32,7 @@ class LoginViewTests(TestCase): self.assertTemplateUsed('login.html') class IndexViewTests(TestCase): - request = RequestFactory().get('/') - request.user = AnonymousUser() + request = anon_request('/') response = index(request) def test_index_url_resolves_to_index_view(self): @@ -40,8 +46,7 @@ class IndexViewTests(TestCase): self.assertTemplateUsed('index.html') class SignupViewTests(TestCase): - request = RequestFactory().get('/signup') - request.user = AnonymousUser() + request = anon_request('/signup') response = signup(request) def test_signup_url_resolves_to_signup_view(self): |