totp: rename tfa to totp
This commit is contained in:
parent
52d1920914
commit
fbf58801ec
|
@ -74,7 +74,7 @@ INSTALLED_APPS = [
|
|||
'passbook.oauth_client',
|
||||
'passbook.oauth_provider',
|
||||
'passbook.saml_idp',
|
||||
'passbook.tfa',
|
||||
'passbook.totp',
|
||||
]
|
||||
|
||||
# Message Tag fix for bootstrap CSS Classes
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
"""passbook tfa Header"""
|
||||
__version__ = '0.0.1-alpha'
|
||||
default_app_config = 'passbook.tfa.apps.PassbookTFAConfig'
|
|
@ -1,11 +0,0 @@
|
|||
"""passbook 2FA AppConfig"""
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
|
||||
class PassbookTFAConfig(AppConfig):
|
||||
"""passbook TFA AppConfig"""
|
||||
|
||||
name = 'passbook.tfa'
|
||||
label = 'passbook_tfa'
|
||||
mountpoint = 'user/tfa/'
|
|
@ -1,14 +0,0 @@
|
|||
"""passbook 2FA Urls"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from passbook.tfa import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='tfa-index'),
|
||||
path('qr/', views.qr_code, name='tfa-qr'),
|
||||
path('verify/', views.verify, name='tfa-verify'),
|
||||
# path('enable/', views.TFASetupView.as_view(), name='tfa-enable'),
|
||||
path('disable/', views.disable, name='tfa-disable'),
|
||||
path('user_settings/', views.user_settings, name='tfa-user_settings'),
|
||||
]
|
3
passbook/totp/__init__.py
Normal file
3
passbook/totp/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""passbook totp Header"""
|
||||
__version__ = '0.0.1-alpha'
|
||||
default_app_config = 'passbook.totp.apps.PassbookTOTPConfig'
|
11
passbook/totp/apps.py
Normal file
11
passbook/totp/apps.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
"""passbook TOTP AppConfig"""
|
||||
|
||||
from django.apps.config import AppConfig
|
||||
|
||||
|
||||
class PassbookTOTPConfig(AppConfig):
|
||||
"""passbook TOTP AppConfig"""
|
||||
|
||||
name = 'passbook.totp'
|
||||
label = 'passbook_totp'
|
||||
mountpoint = 'user/totp/'
|
|
@ -1,12 +1,12 @@
|
|||
"""passbook 2FA Forms"""
|
||||
"""passbook TOTP Forms"""
|
||||
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
TFA_CODE_VALIDATOR = RegexValidator(r'^[0-9a-z]{6,8}$',
|
||||
_('Only alpha-numeric characters are allowed.'))
|
||||
TOTP_CODE_VALIDATOR = RegexValidator(r'^[0-9a-z]{6,8}$',
|
||||
_('Only alpha-numeric characters are allowed.'))
|
||||
|
||||
|
||||
class PictureWidget(forms.widgets.Widget):
|
||||
|
@ -16,37 +16,37 @@ class PictureWidget(forms.widgets.Widget):
|
|||
return mark_safe("<img src=\"%s\" />" % value) # nosec
|
||||
|
||||
|
||||
class TFAVerifyForm(forms.Form):
|
||||
"""Simple Form to verify 2FA Code"""
|
||||
class TOTPVerifyForm(forms.Form):
|
||||
"""Simple Form to verify TOTP Code"""
|
||||
order = ['code']
|
||||
|
||||
code = forms.CharField(label=_('Code'), validators=[TFA_CODE_VALIDATOR],
|
||||
code = forms.CharField(label=_('Code'), validators=[TOTP_CODE_VALIDATOR],
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TFAVerifyForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
# This is a little helper so the field is focused by default
|
||||
self.fields['code'].widget.attrs.update({'autofocus': 'autofocus'})
|
||||
|
||||
|
||||
class TFASetupInitForm(forms.Form):
|
||||
"""Initial 2FA Setup form"""
|
||||
title = _('Set up 2FA')
|
||||
class TOTPSetupInitForm(forms.Form):
|
||||
"""Initial TOTP Setup form"""
|
||||
title = _('Set up TOTP')
|
||||
device = None
|
||||
confirmed = False
|
||||
qr_code = forms.CharField(widget=PictureWidget, disabled=True, required=False,
|
||||
label=_('Scan this Code with your 2FA App.'))
|
||||
code = forms.CharField(label=_('Code'), validators=[TFA_CODE_VALIDATOR])
|
||||
label=_('Scan this Code with your TOTP App.'))
|
||||
code = forms.CharField(label=_('Code'), validators=[TOTP_CODE_VALIDATOR])
|
||||
|
||||
def clean_code(self):
|
||||
"""Check code with new totp device"""
|
||||
if self.device is not None:
|
||||
if not self.device.verify_token(int(self.cleaned_data.get('code'))) \
|
||||
and not self.confirmed:
|
||||
raise forms.ValidationError(_("2FA Code does not match"))
|
||||
raise forms.ValidationError(_("TOTP Code does not match"))
|
||||
return self.cleaned_data.get('code')
|
||||
|
||||
|
||||
class TFASetupStaticForm(forms.Form):
|
||||
class TOTPSetupStaticForm(forms.Form):
|
||||
"""Static form to show generated static tokens"""
|
||||
tokens = forms.MultipleChoiceField(disabled=True, required=False)
|
|
@ -1,4 +1,4 @@
|
|||
"""passbook 2FA Middleware to force users with 2FA set up to verify"""
|
||||
"""passbook TOTP Middleware to force users with TOTP set up to verify"""
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
|
@ -6,24 +6,25 @@ from django.utils.http import urlencode
|
|||
from django_otp import user_has_device
|
||||
|
||||
|
||||
def tfa_force_verify(get_response):
|
||||
"""Middleware to force 2FA Verification"""
|
||||
def totp_force_verify(get_response):
|
||||
"""Middleware to force TOTP Verification"""
|
||||
|
||||
def middleware(request):
|
||||
"""Middleware to force 2FA Verification"""
|
||||
"""Middleware to force TOTP Verification"""
|
||||
|
||||
# pylint: disable=too-many-boolean-expressions
|
||||
if request.user.is_authenticated and \
|
||||
user_has_device(request.user) and \
|
||||
not request.user.is_verified() and \
|
||||
request.path != reverse('passbook_tfa:tfa-verify') and \
|
||||
request.path != reverse('passbook_totp:totp-verify') and \
|
||||
request.path != reverse('account-logout') and \
|
||||
not request.META.get('HTTP_AUTHORIZATION', '').startswith('Bearer'):
|
||||
# User has 2FA set up but is not verified
|
||||
# User has TOTP set up but is not verified
|
||||
|
||||
# At this point the request is already forwarded to the target destination
|
||||
# So we just add the current request's path as next parameter
|
||||
args = '?%s' % urlencode({'next': request.get_full_path()})
|
||||
return redirect(reverse('passbook_tfa:tfa-verify') + args)
|
||||
return redirect(reverse('passbook_totp:totp-verify') + args)
|
||||
|
||||
response = get_response(request)
|
||||
return response
|
|
@ -1,4 +1,4 @@
|
|||
"""passbook 2FA Settings"""
|
||||
"""passbook TOTP Settings"""
|
||||
|
||||
OTP_LOGIN_URL = 'passbook_tfa:tfa-verify'
|
||||
OTP_TOTP_ISSUER = 'passbook'
|
|
@ -31,9 +31,9 @@
|
|||
</p>
|
||||
<p>
|
||||
{% if not state %}
|
||||
<a href="{% url 'passbook_tfa:tfa-enable' %}" class="btn btn-success btn-sm">{% trans "Enable 2FA" %}</a>
|
||||
<a href="{% url 'passbook_tfa:tfa-enable' %}" class="btn btn-success btn-sm">{% trans "Enable TOTP" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'passbook_tfa:tfa-disable' %}" class="btn btn-danger btn-sm">{% trans "Disable 2FA" %}</a>
|
||||
<a href="{% url 'passbook_tfa:tfa-disable' %}" class="btn btn-danger btn-sm">{% trans "Disable TOTP" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
|
@ -7,7 +7,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<label for="">Keep these tokens somewhere safe. These are to be used if you loose your primary 2FA device.</label>
|
||||
<label for="">Keep these tokens somewhere safe. These are to be used if you loose your primary TOTP device.</label>
|
||||
{% for field in wizard.form %}
|
||||
{% if field.field.widget|fieldtype == 'SelectMultiple' %}
|
||||
<ul class="list">
|
|
@ -1,4 +1,4 @@
|
|||
"""passbook Mod 2FA Middleware Test"""
|
||||
"""passbook TOTP Middleware Test"""
|
||||
|
||||
import os
|
||||
|
||||
|
@ -7,19 +7,19 @@ from django.test import RequestFactory, TestCase
|
|||
from django.urls import reverse
|
||||
|
||||
from passbook.core.views import overview
|
||||
from passbook.tfa.middleware import tfa_force_verify
|
||||
from passbook.totp.middleware import totp_force_verify
|
||||
|
||||
|
||||
class TestMiddleware(TestCase):
|
||||
"""passbook 2FA Middleware Test"""
|
||||
"""passbook TOTP Middleware Test"""
|
||||
|
||||
def setUp(self):
|
||||
os.environ['RECAPTCHA_TESTING'] = 'True'
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_tfa_force_verify_anon(self):
|
||||
def test_totp_force_verify_anon(self):
|
||||
"""Test Anonymous TFA Force"""
|
||||
request = self.factory.get(reverse('passbook_core:overview'))
|
||||
request.user = AnonymousUser()
|
||||
response = tfa_force_verify(overview.OverviewView.as_view())(request)
|
||||
response = totp_force_verify(overview.OverviewView.as_view())(request)
|
||||
self.assertEqual(response.status_code, 302)
|
14
passbook/totp/urls.py
Normal file
14
passbook/totp/urls.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
"""passbook TOTP Urls"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from passbook.totp import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='totp-index'),
|
||||
path('qr/', views.qr_code, name='totp-qr'),
|
||||
path('verify/', views.verify, name='totp-verify'),
|
||||
# path('enable/', views.TFASetupView.as_view(), name='totp-enable'),
|
||||
path('disable/', views.disable, name='totp-disable'),
|
||||
path('user_settings/', views.user_settings, name='totp-user_settings'),
|
||||
]
|
|
@ -1,4 +1,4 @@
|
|||
"""passbook Mod 2FA Utils"""
|
||||
"""passbook Mod TOTP Utils"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.http import urlencode
|
|
@ -1,4 +1,4 @@
|
|||
"""passbook 2FA Views"""
|
||||
"""passbook TOTP Views"""
|
||||
# from base64 import b32encode
|
||||
# from binascii import unhexlify
|
||||
|
||||
|
@ -19,8 +19,8 @@ from qrcode.image.svg import SvgPathImage
|
|||
from passbook.lib.decorators import reauth_required
|
||||
# from passbook.core.models import Event
|
||||
# from passbook.core.views.wizards import BaseWizardView
|
||||
from passbook.tfa.forms import TFAVerifyForm
|
||||
from passbook.tfa.utils import otpauth_url
|
||||
from passbook.totp.forms import TOTPVerifyForm
|
||||
from passbook.totp.utils import otpauth_url
|
||||
|
||||
TFA_SESSION_KEY = 'passbook_2fa_key'
|
||||
|
||||
|
@ -30,22 +30,22 @@ TFA_SESSION_KEY = 'passbook_2fa_key'
|
|||
def index(request: HttpRequest) -> HttpResponse:
|
||||
"""Show empty index page"""
|
||||
return render(request, 'core/generic.html', {
|
||||
'text': 'Test 2FA passed'
|
||||
'text': 'Test TOTP passed'
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def verify(request: HttpRequest) -> HttpResponse:
|
||||
"""Verify 2FA Token"""
|
||||
"""Verify TOTP Token"""
|
||||
if not user_has_device(request.user):
|
||||
messages.error(request, _("You don't have 2-Factor Authentication set up."))
|
||||
if request.method == 'POST':
|
||||
form = TFAVerifyForm(request.POST)
|
||||
form = TOTPVerifyForm(request.POST)
|
||||
if form.is_valid():
|
||||
device = match_token(request.user, form.cleaned_data.get('code'))
|
||||
if device:
|
||||
login(request, device)
|
||||
messages.success(request, _('Successfully validated 2FA Token.'))
|
||||
messages.success(request, _('Successfully validated TOTP Token.'))
|
||||
# Check if there is a next GET parameter and redirect to that
|
||||
if 'next' in request.GET:
|
||||
return redirect(request.GET.get('next'))
|
||||
|
@ -53,7 +53,7 @@ def verify(request: HttpRequest) -> HttpResponse:
|
|||
return redirect(reverse('common-index'))
|
||||
messages.error(request, _('Invalid 2-Factor Token.'))
|
||||
else:
|
||||
form = TFAVerifyForm()
|
||||
form = TOTPVerifyForm()
|
||||
|
||||
return render(request, 'generic/form_login.html', {
|
||||
'form': form,
|
||||
|
@ -67,13 +67,13 @@ def verify(request: HttpRequest) -> HttpResponse:
|
|||
|
||||
@login_required
|
||||
def user_settings(request: HttpRequest) -> HttpResponse:
|
||||
"""View for user settings to control 2FA"""
|
||||
"""View for user settings to control TOTP"""
|
||||
static = get_object_or_404(StaticDevice, user=request.user, confirmed=True)
|
||||
static_tokens = StaticToken.objects.filter(device=static).order_by('token')
|
||||
finished_totp_devices = TOTPDevice.objects.filter(user=request.user, confirmed=True)
|
||||
finished_static_devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
|
||||
state = finished_totp_devices.exists() and finished_static_devices.exists()
|
||||
return render(request, 'tfa/user_settings.html', {
|
||||
return render(request, 'totp/user_settings.html', {
|
||||
'static_tokens': static_tokens,
|
||||
'state': state,
|
||||
})
|
||||
|
@ -83,7 +83,7 @@ def user_settings(request: HttpRequest) -> HttpResponse:
|
|||
@reauth_required
|
||||
@otp_required
|
||||
def disable(request: HttpRequest) -> HttpResponse:
|
||||
"""Disable 2FA for user"""
|
||||
"""Disable TOTP for user"""
|
||||
# Delete all the devices for user
|
||||
static = get_object_or_404(StaticDevice, user=request.user, confirmed=True)
|
||||
static_tokens = StaticToken.objects.filter(device=static).order_by('token')
|
||||
|
@ -92,11 +92,11 @@ def disable(request: HttpRequest) -> HttpResponse:
|
|||
totp.delete()
|
||||
for token in static_tokens:
|
||||
token.delete()
|
||||
messages.success(request, 'Successfully disabled 2FA')
|
||||
messages.success(request, 'Successfully disabled TOTP')
|
||||
# Create event with email notification
|
||||
# Event.create(
|
||||
# user=request.user,
|
||||
# message=_('You disabled 2FA.'),
|
||||
# message=_('You disabled TOTP.'),
|
||||
# current=True,
|
||||
# request=request,
|
||||
# send_notification=True)
|
||||
|
@ -108,7 +108,7 @@ def disable(request: HttpRequest) -> HttpResponse:
|
|||
# class TFASetupView(BaseWizardView):
|
||||
# """Wizard to create a Mail Account"""
|
||||
|
||||
# title = _('Set up 2FA')
|
||||
# title = _('Set up TOTP')
|
||||
# form_list = [TFASetupInitForm, TFASetupStaticForm]
|
||||
|
||||
# totp_device = None
|
||||
|
@ -117,15 +117,15 @@ def disable(request: HttpRequest) -> HttpResponse:
|
|||
|
||||
# def get_template_names(self):
|
||||
# if self.steps.current == '1':
|
||||
# return 'tfa/wizard_setup_static.html'
|
||||
# return 'totp/wizard_setup_static.html'
|
||||
# return self.template_name
|
||||
|
||||
# def handle_request(self, request: HttpRequest):
|
||||
# # Check if user has 2FA setup already
|
||||
# # Check if user has TOTP setup already
|
||||
# finished_totp_devices = TOTPDevice.objects.filter(user=request.user, confirmed=True)
|
||||
# finished_static_devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
|
||||
# if finished_totp_devices.exists() or finished_static_devices.exists():
|
||||
# messages.error(request, _('You already have 2FA enabled!'))
|
||||
# messages.error(request, _('You already have TOTP enabled!'))
|
||||
# return redirect(reverse('common-index'))
|
||||
# # Check if there's an unconfirmed device left to set up
|
||||
# totp_devices = TOTPDevice.objects.filter(user=request.user, confirmed=False)
|
||||
|
@ -182,7 +182,7 @@ def disable(request: HttpRequest) -> HttpResponse:
|
|||
# # Create event with email notification
|
||||
# Event.create(
|
||||
# user=self.request.user,
|
||||
# message=_('You activated 2FA.'),
|
||||
# message=_('You activated TOTP.'),
|
||||
# current=True,
|
||||
# request=self.request,
|
||||
# send_notification=True)
|
Reference in a new issue