Compare commits

..

12 Commits

Author SHA1 Message Date
Mohamed ElKalioby
19563fe2b8 Fixes #37 2021-02-26 13:55:49 +03:00
Mohamed ElKalioby
0e7c0911ca Merge branch 'master' of https://github.com/mkalioby/django-mfa2 2021-02-26 13:26:31 +03:00
Mohamed ElKalioby
ebfdaf7504 Merge branch 'TouchID' 2021-02-26 13:25:56 +03:00
Mohamed El-Kalioby
c78e600b37 Update CHANGELOG.md 2021-01-21 09:17:28 +03:00
Mohamed El-Kalioby
e1e931285f Update requirements.txt
Fix FIDO2 version in the requirements file
2021-01-21 09:13:52 +03:00
Mohamed El-Kalioby
2709e70f88 Removed Touch ID Beta statment 2021-01-20 17:11:08 +03:00
Mohamed El-Kalioby
25fe80d76d Merge branch 'master' of github.com:mkalioby/django-mfa2 2021-01-20 17:10:08 +03:00
Mohamed El-Kalioby
ba76f842bb Fixed README 2021-01-20 17:08:43 +03:00
Mohamed ElKalioby
87e83b3bbe Updated Changed Log 2021-01-20 16:06:34 +03:00
Mohamed ElKalioby
a94ad50b93 Adding Latest mfa 2021-01-20 16:05:08 +03:00
Mohamed El-Kalioby
75cf1e6130 Update README.md 2021-01-18 19:57:54 +03:00
Mohamed El-Kalioby
911be9e106 Update README.md 2021-01-18 19:56:19 +03:00
10 changed files with 77 additions and 49 deletions

View File

@@ -1,5 +1,16 @@
# Change Log # 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.
## 2.1.0
* Added Support for Touch ID for Mac OSx and iOS 14 on Safari
## 2.0.5 ## 2.0.5
* Fixed issue in __version__ * Fixed issue in __version__

View File

@@ -17,17 +17,17 @@ Web Authencation API (WebAuthn) is state-of-the art techology that is expected t
For FIDO2, the following are supported For FIDO2, the following are supported
* **security keys** (Firefox 60+, Chrome 67+, Edge 18+, Safari 13 on Mac OS, Chrome on Andriod, Safari on iOS 13.3+), * **security keys** (Firefox 60+, Chrome 67+, Edge 18+, Safari 13 on Mac OS, Chrome on Andriod, Safari on iOS 13.3+),
* **Windows Hello** (Firefox 67+, Chrome 72+ , Edge) , * **Windows Hello** (Firefox 67+, Chrome 72+ , Edge) ,
* **Apple's Touch ID** (Chrome 70+ on Mac OS X ), * **Apple's Touch ID/Face ID** (Chrome 70+ on Mac OS X, Safari on macOS Big Sur, Safari on iOS 14.0+ ),
* **android-safetynet** (Chrome 70+, Firefox 68+) * **android-safetynet** (Chrome 70+, Firefox 68+)
* **NFC devices using PCSC** (Not Tested, but as supported in fido2) * **NFC devices using PCSC** (Not Tested, but as supported in fido2)
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 ID on Macbooks (Chrome) and Fingerprint/Face/Iris/PIN on Andriod Phones. 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.
Trusted device is a mode for the user to add a device that doesn't support security keys like iOS and andriod without fingerprints or NFC. Trusted device is a mode for the user to add a device that doesn't support security keys like Android without fingerprints or NFC.
**Note**: `U2F and FIDO2 can only be served under secure context (https)` **Note**: `U2F and FIDO2 can only be served under secure context (https)`
Package tested with Django 1.8, Django 2.1 on Python 2.7 and Python 3.5+ but it was not checked with any version in between but open for issues. 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.
Depends on Depends on
@@ -36,7 +36,7 @@ Depends on
* ua-parser * ua-parser
* user-agents * user-agents
* python-jose * python-jose
* fido2==0.8.1 * fido2==0.9.0
# Installation # Installation
1. using pip 1. using pip

View File

@@ -46,8 +46,8 @@ def auth(request):
uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email") uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email")
mfa = {"verified": True, "method": "Email","id":uk.id} mfa = {"verified": True, "method": "Email","id":uk.id}
if getattr(settings, "MFA_RECHECK", False): if getattr(settings, "MFA_RECHECK", False):
mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta( 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 request.session["mfa"] = mfa
from django.utils import timezone from django.utils import timezone

View File

@@ -16,6 +16,7 @@ from .views import login,reset_cookie
import datetime import datetime
from django.utils import timezone from django.utils import timezone
def recheck(request): def recheck(request):
context = csrf(request) context = csrf(request)
context["mode"] = "recheck" context["mode"] = "recheck"
@@ -26,6 +27,8 @@ def recheck(request):
def getServer(): def getServer():
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME) rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
return Fido2Server(rp) return Fido2Server(rp)
def begin_registeration(request): def begin_registeration(request):
server = getServer() server = getServer()
registration_data, state = server.register_begin({ registration_data, state = server.register_begin({
@@ -36,6 +39,8 @@ def begin_registeration(request):
request.session['fido_state'] = state 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 @csrf_exempt
def complete_reg(request): def complete_reg(request):
try: try:
@@ -64,20 +69,25 @@ def complete_reg(request):
except: except:
pass 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): def start(request):
context = csrf(request) context = csrf(request)
return render(request, "FIDO2/Add.html", context) return render(request, "FIDO2/Add.html", context)
def getUserCredentials(username): def getUserCredentials(username):
credentials = [] credentials = []
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"): for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"]))) credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
return credentials return credentials
def auth(request): def auth(request):
context = csrf(request) context = csrf(request)
return render(request, "FIDO2/Auth.html", context) return render(request, "FIDO2/Auth.html", context)
def authenticate_begin(request): def authenticate_begin(request):
server = getServer() server = getServer()
credentials = getUserCredentials(request.session.get("base_username", request.user.username)) credentials = getUserCredentials(request.session.get("base_username", request.user.username))
@@ -85,6 +95,7 @@ def authenticate_begin(request):
request.session['fido_state'] = state 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 @csrf_exempt
def authenticate_complete(request): def authenticate_complete(request):
try: try:
@@ -107,7 +118,8 @@ def authenticate_complete(request):
signature signature
) )
except ValueError: 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") content_type = "application/json")
except Exception as excep: except Exception as excep:
try: try:
@@ -133,8 +145,8 @@ def authenticate_complete(request):
k.save() k.save()
mfa = {"verified": True, "method": "FIDO2", 'id': k.id} mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
if getattr(settings, "MFA_RECHECK", False): if getattr(settings, "MFA_RECHECK", False):
mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta( 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 request.session["mfa"] = mfa
try: try:
authenticated = request.user.is_authenticated authenticated = request.user.is_authenticated
@@ -143,8 +155,10 @@ def authenticate_complete(request):
if not authenticated: if not authenticated:
res = login(request) res = login(request)
if not "location" in res: return reset_cookie(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"}), return HttpResponse(simplejson.dumps({'status': "OK"}),
content_type = "application/json") content_type = "application/json")
except Exception as exp: 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")

View File

@@ -57,9 +57,9 @@ def validate(request,username):
key.save() key.save()
mfa = {"verified": True, "method": "U2F","id":key.id} mfa = {"verified": True, "method": "U2F","id":key.id}
if getattr(settings, "MFA_RECHECK", False): if getattr(settings, "MFA_RECHECK", False):
mfa["next_check"] = int((datetime.datetime.now() mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
+ datetime.timedelta( + 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 request.session["mfa"] = mfa
return True return True

View File

@@ -1 +1 @@
__version__="2.1.0b1" __version__="2.1.0"

View File

@@ -6,6 +6,7 @@ from jsonLookup import shasLookup, hasLookup
JSONField.register_lookup(shasLookup) JSONField.register_lookup(shasLookup)
JSONField.register_lookup(hasLookup) JSONField.register_lookup(hasLookup)
class User_Keys(models.Model): class User_Keys(models.Model):
username=models.CharField(max_length = 50) username=models.CharField(max_length = 50)
properties=JSONField(null = True) properties=JSONField(null = True)
@@ -20,9 +21,12 @@ class User_Keys(models.Model):
if self.key_type == "Trusted Device" and self.properties.get("signature","") == "": 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) 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) super(User_Keys, self).save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
def __unicode__(self): def __unicode__(self):
return "%s -- %s"%(self.username,self.key_type) return "%s -- %s"%(self.username,self.key_type)
def __str__(self): def __str__(self):
return self.__unicode__() return self.__unicode__()
class Meta: class Meta:
app_label='mfa' app_label='mfa'

View File

@@ -40,9 +40,9 @@ def auth(request):
if res[0]: if res[0]:
mfa = {"verified": True, "method": "TOTP","id":res[1]} mfa = {"verified": True, "method": "TOTP","id":res[1]}
if getattr(settings, "MFA_RECHECK", False): if getattr(settings, "MFA_RECHECK", False):
mfa["next_check"] = int((datetime.datetime.now() mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
+ datetime.timedelta( + 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 request.session["mfa"] = mfa
return login(request) return login(request)
context["invalid"]=True context["invalid"]=True

View File

@@ -6,5 +6,5 @@ python-u2flib-server
ua-parser ua-parser
user-agents user-agents
python-jose python-jose
fido2 == 0.8.1 fido2 == 0.9.0
jsonLookup jsonLookup

View File

@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup( setup(
name='django-mfa2', name='django-mfa2',
version='2.1.0b1', version='2.1.2b1',
description='Allows user to add 2FA to their accounts', description='Allows user to add 2FA to their accounts',
long_description=open("README.md").read(), long_description=open("README.md").read(),
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@@ -24,15 +24,14 @@ setup(
'ua-parser', 'ua-parser',
'user-agents', 'user-agents',
'python-jose', 'python-jose',
# 'fido2 == 0.8.1', 'fido2 == 0.9.1',
'jsonLookup' 'jsonLookup'
], ],
dependency_links =["https://github.com/Yubico/python-fido2/tarball/master"],
python_requires=">=3.5", python_requires=">=3.5",
include_package_data=True, include_package_data=True,
zip_safe=False, # because we're including static files zip_safe=False, # because we're including static files
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 4 - Beta",
"Environment :: Web Environment", "Environment :: Web Environment",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 1.11", "Framework :: Django :: 1.11",