From 19563fe2b8205db61ac697d9888c3044cb6d3496 Mon Sep 17 00:00:00 2001 From: Mohamed ElKalioby Date: Fri, 26 Feb 2021 13:55:49 +0300 Subject: [PATCH] Fixes #37 --- CHANGELOG.md | 5 ++++ mfa/Email.py | 4 +-- mfa/FIDO2.py | 76 ++++++++++++++++++++++++++++++--------------------- mfa/U2F.py | 4 +-- mfa/models.py | 4 +++ mfa/totp.py | 4 +-- setup.py | 6 ++-- 7 files changed, 63 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adf5841..824bf25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 2.1.2 + * Fixed: Getting timestamp on Python 3.7 as ("%s") is raising an exception + * Upgraded to FIDO 0.9.1 + + ## 2.1.1 * Fixed: FIDO2 version in requirments.txt file. diff --git a/mfa/Email.py b/mfa/Email.py index a2c3119..9533c5d 100644 --- a/mfa/Email.py +++ b/mfa/Email.py @@ -46,8 +46,8 @@ def auth(request): uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email") mfa = {"verified": True, "method": "Email","id":uk.id} if getattr(settings, "MFA_RECHECK", False): - mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta( - seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) + mfa["next_check"] = datetime.datetime.timestamp(datetime.datetime.now() + datetime.timedelta( + seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))) request.session["mfa"] = mfa from django.utils import timezone diff --git a/mfa/FIDO2.py b/mfa/FIDO2.py index 75fa32a..addda1c 100644 --- a/mfa/FIDO2.py +++ b/mfa/FIDO2.py @@ -4,28 +4,31 @@ from fido2.ctap2 import AttestationObject, AuthenticatorData from django.template.context_processors import csrf from django.views.decorators.csrf import csrf_exempt from django.shortcuts import render -#from django.template.context import RequestContext +# from django.template.context import RequestContext import simplejson from fido2 import cbor from django.http import HttpResponse from django.conf import settings from .models import * -from fido2.utils import websafe_decode,websafe_encode +from fido2.utils import websafe_decode, websafe_encode from fido2.ctap2 import AttestedCredentialData -from .views import login,reset_cookie +from .views import login, reset_cookie import datetime from django.utils import timezone + def recheck(request): context = csrf(request) - context["mode"]="recheck" - request.session["mfa_recheck"]=True - return render(request,"FIDO2/recheck.html", context) + context["mode"] = "recheck" + request.session["mfa_recheck"] = True + return render(request, "FIDO2/recheck.html", context) def getServer(): rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME) return Fido2Server(rp) + + def begin_registeration(request): server = getServer() registration_data, state = server.register_begin({ @@ -35,7 +38,9 @@ def begin_registeration(request): }, getUserCredentials(request.user.username)) request.session['fido_state'] = state - return HttpResponse(cbor.encode(registration_data),content_type='application/octet-stream') + return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream') + + @csrf_exempt def complete_reg(request): try: @@ -50,10 +55,10 @@ def complete_reg(request): att_obj ) encoded = websafe_encode(auth_data.credential_data) - uk=User_Keys() + uk = User_Keys() uk.username = request.user.username - uk.properties = {"device":encoded,"type":att_obj.fmt,} - uk.owned_by_enterprise=getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) + uk.properties = {"device": encoded, "type": att_obj.fmt, } + uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False) uk.key_type = "FIDO2" uk.save() return HttpResponse(simplejson.dumps({'status': 'OK'})) @@ -63,10 +68,13 @@ def complete_reg(request): client.captureException() except: pass - return HttpResponse(simplejson.dumps({'status': 'ERR',"message":"Error on server, please try again later"})) + return HttpResponse(simplejson.dumps({'status': 'ERR', "message": "Error on server, please try again later"})) + + def start(request): context = csrf(request) - return render(request,"FIDO2/Add.html", context) + return render(request, "FIDO2/Add.html", context) + def getUserCredentials(username): credentials = [] @@ -74,24 +82,27 @@ def getUserCredentials(username): credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"]))) return credentials + def auth(request): - context=csrf(request) - return render(request,"FIDO2/Auth.html",context) + context = csrf(request) + return render(request, "FIDO2/Auth.html", context) + def authenticate_begin(request): server = getServer() - credentials=getUserCredentials(request.session.get("base_username",request.user.username)) + credentials = getUserCredentials(request.session.get("base_username", request.user.username)) auth_data, state = server.authenticate_begin(credentials) request.session['fido_state'] = state - return HttpResponse(cbor.encode(auth_data),content_type="application/octet-stream") + return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream") + @csrf_exempt def authenticate_complete(request): try: credentials = [] - username=request.session.get("base_username",request.user.username) - server=getServer() - credentials=getUserCredentials(username) + username = request.session.get("base_username", request.user.username) + server = getServer() + credentials = getUserCredentials(username) data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) @@ -107,7 +118,8 @@ def authenticate_complete(request): signature ) except ValueError: - return HttpResponse(simplejson.dumps({'status': "ERR", "message": "Wrong challenge received, make sure that this is your security and try again."}), + return HttpResponse(simplejson.dumps({'status': "ERR", + "message": "Wrong challenge received, make sure that this is your security and try again."}), content_type = "application/json") except Exception as excep: try: @@ -119,32 +131,34 @@ def authenticate_complete(request): "message": excep.message}), content_type = "application/json") - if request.session.get("mfa_recheck",False): + if request.session.get("mfa_recheck", False): import time - request.session["mfa"]["rechecked_at"]=time.time() + request.session["mfa"]["rechecked_at"] = time.time() return HttpResponse(simplejson.dumps({'status': "OK"}), - content_type="application/json") + content_type = "application/json") else: import random - keys = User_Keys.objects.filter(username=username, key_type="FIDO2", enabled=1) + keys = User_Keys.objects.filter(username = username, key_type = "FIDO2", enabled = 1) for k in keys: if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() - mfa = {"verified": True, "method": "FIDO2",'id':k.id} + mfa = {"verified": True, "method": "FIDO2", 'id': k.id} if getattr(settings, "MFA_RECHECK", False): - mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta( - seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) + mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now() + datetime.timedelta( + seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX)))) request.session["mfa"] = mfa try: - authenticated=request.user.is_authenticated + authenticated = request.user.is_authenticated except: authenticated = request.user.is_authenticated() if not authenticated: - res=login(request) + res = login(request) if not "location" in res: return reset_cookie(request) - return HttpResponse(simplejson.dumps({'status':"OK","redirect":res["location"]}),content_type="application/json") + return HttpResponse(simplejson.dumps({'status': "OK", "redirect": res["location"]}), + content_type = "application/json") return HttpResponse(simplejson.dumps({'status': "OK"}), content_type = "application/json") except Exception as exp: - return HttpResponse(simplejson.dumps({'status': "ERR","message":exp.message}),content_type="application/json") + return HttpResponse(simplejson.dumps({'status': "ERR", "message": exp.message}), + content_type = "application/json") diff --git a/mfa/U2F.py b/mfa/U2F.py index 938841e..f7da5dc 100644 --- a/mfa/U2F.py +++ b/mfa/U2F.py @@ -57,9 +57,9 @@ def validate(request,username): key.save() mfa = {"verified": True, "method": "U2F","id":key.id} if getattr(settings, "MFA_RECHECK", False): - mfa["next_check"] = int((datetime.datetime.now() + mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now() + datetime.timedelta( - seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) + seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX)))) request.session["mfa"] = mfa return True diff --git a/mfa/models.py b/mfa/models.py index eced0be..b97123d 100644 --- a/mfa/models.py +++ b/mfa/models.py @@ -6,6 +6,7 @@ from jsonLookup import shasLookup, hasLookup JSONField.register_lookup(shasLookup) JSONField.register_lookup(hasLookup) + class User_Keys(models.Model): username=models.CharField(max_length = 50) properties=JSONField(null = True) @@ -20,9 +21,12 @@ class User_Keys(models.Model): if self.key_type == "Trusted Device" and self.properties.get("signature","") == "": self.properties["signature"]= jwt.encode({"username": self.username, "key": self.properties["key"]}, settings.SECRET_KEY) super(User_Keys, self).save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) + def __unicode__(self): return "%s -- %s"%(self.username,self.key_type) + def __str__(self): return self.__unicode__() + class Meta: app_label='mfa' diff --git a/mfa/totp.py b/mfa/totp.py index d8e1f76..c0c008a 100644 --- a/mfa/totp.py +++ b/mfa/totp.py @@ -40,9 +40,9 @@ def auth(request): if res[0]: mfa = {"verified": True, "method": "TOTP","id":res[1]} if getattr(settings, "MFA_RECHECK", False): - mfa["next_check"] = int((datetime.datetime.now() + mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now() + datetime.timedelta( - seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) + seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX)))) request.session["mfa"] = mfa return login(request) context["invalid"]=True diff --git a/setup.py b/setup.py index 2e537ef..c17c848 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup setup( name='django-mfa2', - version='2.1.0', + version='2.1.2b1', description='Allows user to add 2FA to their accounts', long_description=open("README.md").read(), long_description_content_type="text/markdown", @@ -24,14 +24,14 @@ setup( 'ua-parser', 'user-agents', 'python-jose', - 'fido2 == 0.9', + 'fido2 == 0.9.1', 'jsonLookup' ], python_requires=">=3.5", include_package_data=True, zip_safe=False, # because we're including static files classifiers=[ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 1.11",