diff --git a/CHANGELOG.md b/CHANGELOG.md index 179ef56..f301507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ # Change Log +## 2.8.0 +* Support For Django 4.0+ JSONField +* Removed jsonfield package from requirements + +## 2.7.0 +* Fixed #70 +* Add QR Code for trusted device link +* Better formatting for trusted device start page. ## 2.6.1 * Fix: CVE-2022-42731: related to the possibility of registration replay attack. Thanks to 'SSE (Secure Systems Engineering)' diff --git a/README.md b/README.md index 89fc2a8..2cae41f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ For FIDO2, the following are supported * **android-safetynet** (Chrome 70+, Firefox 68+) * **NFC devices using PCSC** (Not Tested, but as supported in fido2) * **Soft Tokens** - * [krypt.co](https://krypt.co/): Login by a notification on your phone. + * ~~[krypt.co](https://krypt.co/): Login by a notification on your phone.~~ + +**Update**: Dec 2022, krypt.co has been killed by Google for Passkeys. In English :), It allows you to verify the user by security keys on PC, Laptops or Mobiles, Windows Hello (Fingerprint, PIN) on Windows 10 Build 1903+ (May 2019 Update) Touch/Face ID on Macbooks (Chrome, Safari), Touch/Face ID on iPhone and iPad and Fingerprint/Face/Iris/PIN on Android Phones. @@ -32,6 +34,8 @@ Trusted device is a mode for the user to add a device that doesn't support secur Package tested with Django 1.8, Django 2.2 on Python 2.7 and Python 3.5+ but it was not checked with any version in between but open for issues. +If you just need WebAuthn and Passkeys, you can use **[django-passkeys](https://github.com/mkalioby/django-passkeys)**, which is a slim-down of this app and much easier to integrate. + Depends on * pyotp @@ -43,8 +47,12 @@ Depends on # Installation 1. using pip - - `pip install django-mfa2` + * For Django >= 4.0 + + `pip install django-mfa2` + * For Django < 4.0 + + `pip install django-mfa2 jsonfield` 2. Using Conda forge `conda config --add channels conda-forge` diff --git a/example/example/urls.py b/example/example/urls.py index bd0dc4d..91c6937 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -16,12 +16,13 @@ Including another URLconf from django.contrib import admin from django.urls import path,re_path,include from . import views,auth +from mfa import TrustedDevice urlpatterns = [ path('admin/', admin.site.urls), path('mfa/', include('mfa.urls')), path('auth/login',auth.loginView,name="login"), path('auth/logout',auth.logoutView,name="logout"), - + path('devices/add/', TrustedDevice.add,name="add_trusted_device"), re_path('^$',views.home,name='home'), path('registered/',views.registered,name='registered') ] diff --git a/mfa/TrustedDevice.py b/mfa/TrustedDevice.py index 94b2136..a7664fe 100644 --- a/mfa/TrustedDevice.py +++ b/mfa/TrustedDevice.py @@ -7,10 +7,11 @@ from django.template.context_processors import csrf from .models import * import user_agents from django.utils import timezone +from django.urls import reverse def id_generator(size=6, chars=string.ascii_uppercase + string.digits): x=''.join(random.choice(chars) for _ in range(size)) - if not User_Keys.objects.filter(properties__shas="$.key="+x).exists(): return x + if not User_Keys.objects.filter(properties__icontains='"key": "%s"'%x).exists(): return x else: return id_generator(size,chars) def getUserAgent(request): @@ -57,12 +58,13 @@ def getCookie(request): def add(request): context=csrf(request) if request.method=="GET": + context.update({"username":request.GET.get('u',''),"key":request.GET.get('k','')}) return render(request,"TrustedDevices/Add.html",context) else: key=request.POST["key"].replace("-","").replace(" ","").upper() context["username"] = request.POST["username"] context["key"] = request.POST["key"] - trusted_keys=User_Keys.objects.filter(username=request.POST["username"],properties__has="$.key="+key) + trusted_keys=User_Keys.objects.filter(username=request.POST["username"],properties__icontains='"key": "%s"'%key) cookie=False if trusted_keys.exists(): tk=trusted_keys[0] @@ -97,7 +99,7 @@ def start(request): request.session["td_id"]=td.id try: if td==None: td=User_Keys.objects.get(id=request.session["td_id"]) - context={"key":td.properties["key"]} + context={"key":td.properties["key"],"url":request.scheme+"://"+request.get_host() + reverse('add_trusted_device')} except: del request.session["td_id"] return start(request) @@ -124,12 +126,14 @@ def verify(request): json= jwt.decode(request.COOKIES.get('deviceid'),settings.SECRET_KEY) if json["username"].lower()== request.session['base_username'].lower(): try: - uk = User_Keys.objects.get(username=request.POST["username"].lower(), properties__has="$.key=" + json["key"]) + uk = User_Keys.objects.get(username=request.POST["username"].lower(), properties__icontains='"key": "%s"'%json["key"]) if uk.enabled and uk.properties["status"] == "trusted": uk.last_used=timezone.now() uk.save() request.session["mfa"] = {"verified": True, "method": "Trusted Device","id":uk.id} return True except: + import traceback + print(traceback.format_exc()) return False return False diff --git a/mfa/migrations/0005_auto_20181115_2014.py b/mfa/migrations/0005_auto_20181115_2014.py index 8f9b76d..3f65a26 100644 --- a/mfa/migrations/0005_auto_20181115_2014.py +++ b/mfa/migrations/0005_auto_20181115_2014.py @@ -2,7 +2,14 @@ from __future__ import unicode_literals from django.db import models, migrations -import jsonfield.fields +try: + from django.db.models import JSONField +except ImportError: + try: + from jsonfield.fields import JSONField + except ImportError: + raise ImportError("Can't find a JSONField implementation, please install jsonfield if django < 4.0") + def modify_json(apps, schema_editor): @@ -24,7 +31,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user_keys', name='properties', - field=jsonfield.fields.JSONField(null=True), + field=JSONField(null=True), ), migrations.RunPython(modify_json) ] diff --git a/mfa/models.py b/mfa/models.py index d4eff59..9d0ac12 100644 --- a/mfa/models.py +++ b/mfa/models.py @@ -1,5 +1,12 @@ from django.db import models -from jsonfield import JSONField +try: + from django.db.models import JSONField +except ModuleNotFoundError: + try: + from jsonfield import JSONField + except ModuleNotFoundError: + raise ModuleNotFoundError("Can't find a JSONField implementation, please install jsonfield if django < 4.0") + from jose import jwt from django.conf import settings #from jsonLookup import shasLookup, hasLookup diff --git a/mfa/templates/MFA.html b/mfa/templates/MFA.html index afcc59d..50ef658 100644 --- a/mfa/templates/MFA.html +++ b/mfa/templates/MFA.html @@ -116,7 +116,7 @@ {% endif %} {% else %} - You didn't have any keys yet. + You don't have any keys yet. {% endif %} diff --git a/mfa/templates/TrustedDevices/start.html b/mfa/templates/TrustedDevices/start.html index dd208b3..54eab2d 100644 --- a/mfa/templates/TrustedDevices/start.html +++ b/mfa/templates/TrustedDevices/start.html @@ -1,5 +1,7 @@ {% extends "base.html" %} +{% load static %} {% block head %} +