Compare commits

...

18 Commits

Author SHA1 Message Date
Mohamed ElKalioby
23377abfa6 Removed Dependability of JSONLookup 2021-03-05 00:48:01 +03:00
Mohamed El-Kalioby
ba9dfc4d36 Merge pull request #41 from BIZFactoryGmbH/AndreasDickow-patch-postgresql-support-device-register
Andreas dickow patch postgresql support device register
2021-03-02 20:03:26 +03:00
Mohamed El-Kalioby
ef8287666c Closes #37 2021-03-02 19:44:05 +03:00
BIZ Factory GmbH
600ef2421a Update TrustedDevice.py
add postgresql support
2021-03-01 08:27:38 +01:00
BIZ Factory GmbH
4fbe88b90f Update U2F.py
fix get instead of filter in function call
2021-03-01 08:25:48 +01:00
BIZ Factory GmbH
a48ae253d6 Update U2F.py 2021-03-01 07:44:17 +01:00
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
14 changed files with 92 additions and 61 deletions

View File

@@ -1,5 +1,16 @@
# 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
* 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
* **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) ,
* **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+)
* **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)`
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
@@ -36,7 +36,7 @@ Depends on
* ua-parser
* user-agents
* python-jose
* fido2==0.8.1
* fido2==0.9.0
# Installation
1. using pip

View File

@@ -141,7 +141,7 @@ MFA_RECHECK=True # Allow random rechecking of the user
MFA_RECHECK_MIN=10 # Minimum interval in seconds
MFA_RECHECK_MAX=30 # Maximum in seconds
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
MFA_HIDE_DISABLE=('FIDO2',) # Can the user disable his key (Added in 1.2.0).
MFA_HIDE_DISABLE=() # Can the user disable his key (Added in 1.2.0).
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name

View File

@@ -16,9 +16,11 @@ Including another URLconf
from django.contrib import admin
from django.urls import path,re_path,include
from . import views,auth
import mfa
urlpatterns = [
path('admin/', admin.site.urls),
path('mfa/', include('mfa.urls')),
path('devices/add', mfa.TrustedDevice.add,name="mfa_add_new_trusted_device"),
path('auth/login',auth.loginView,name="login"),
path('auth/logout',auth.logoutView,name="logout"),

View File

@@ -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

View File

@@ -16,6 +16,7 @@ from .views import login,reset_cookie
import datetime
from django.utils import timezone
def recheck(request):
context = csrf(request)
context["mode"] = "recheck"
@@ -26,6 +27,8 @@ def recheck(request):
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({
@@ -36,6 +39,8 @@ def begin_registeration(request):
request.session['fido_state'] = state
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
@csrf_exempt
def complete_reg(request):
try:
@@ -64,20 +69,25 @@ def complete_reg(request):
except:
pass
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)
def getUserCredentials(username):
credentials = []
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
return credentials
def auth(request):
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))
@@ -85,6 +95,7 @@ def authenticate_begin(request):
request.session['fido_state'] = state
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
@csrf_exempt
def authenticate_complete(request):
try:
@@ -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:
@@ -133,8 +145,8 @@ def authenticate_complete(request):
k.save()
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
@@ -143,8 +155,10 @@ def authenticate_complete(request):
if not authenticated:
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")

View File

@@ -2,7 +2,6 @@ import string
import random
from django.shortcuts import render
from django.http import HttpResponse
from django.template.context import RequestContext
from django.template.context_processors import csrf
from .models import *
import user_agents
@@ -10,7 +9,7 @@ from django.utils import timezone
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=x, key_type="Trusted Device").exists(): return x
else: return id_generator(size,chars)
def getUserAgent(request):
@@ -19,6 +18,7 @@ def getUserAgent(request):
tk=User_Keys.objects.get(id=id)
if tk.properties.get("user_agent","")!="":
ua = user_agents.parse(tk.properties["user_agent"])
print(ua.os)
res = render(None, "TrustedDevices/user-agent.html", context={"ua":ua})
return HttpResponse(res)
return HttpResponse("")
@@ -62,13 +62,14 @@ def add(request):
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__iregex=rf'{key}')
cookie=False
if trusted_keys.exists():
tk=trusted_keys[0]
request.session["td_id"]=tk.id
ua=request.META['HTTP_USER_AGENT']
agent=user_agents.parse(ua)
print(agent.os)
if agent.is_pc:
context["invalid"]="This is a PC, it can't used as a trusted device."
else:
@@ -124,7 +125,7 @@ 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__properties__iregex=rf'{json["key"]}')
if uk.enabled and uk.properties["status"] == "trusted":
uk.last_used=timezone.now()
uk.save()

View File

@@ -52,14 +52,14 @@ def validate(request,username):
challenge = request.session.pop('_u2f_challenge_')
device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])
key=User_Keys.objects.get(username=username,properties__shas="$.device.publicKey=%s"%device["publicKey"])
key = User_Keys.objects.get(username=username,key_type = "U2F", properties__iregex=rf'{device["publicKey"]}')
key.last_used=timezone.now()
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
@@ -69,7 +69,7 @@ def auth(request):
request.session["_u2f_challenge_"]=s[0]
context["token"]=s[1]
return render(request,"U2F/Auth.html")
return render(request,"U2F/Auth.html",context)
def start(request):
enroll = begin_registration(settings.U2F_APPID, [])

View File

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

View File

@@ -2,9 +2,7 @@ from django.db import models
from jsonfield import JSONField
from jose import jwt
from django.conf import settings
from jsonLookup import shasLookup, hasLookup
JSONField.register_lookup(shasLookup)
JSONField.register_lookup(hasLookup)
class User_Keys(models.Model):
username=models.CharField(max_length = 50)
@@ -20,9 +18,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'

View File

@@ -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

View File

@@ -1,10 +1,11 @@
from . import views,totp,U2F,TrustedDevice,helpers,FIDO2,Email
#app_name='mfa'
try:
from django.urls import re_path as url
except:
from django.conf.urls import url
urlpatterns = [
url(r'totp/start/', totp.start , name="start_new_otop"),
url(r'totp/getToken', totp.getToken , name="get_new_otop"),
@@ -39,6 +40,7 @@ urlpatterns = [
url(r'u2f/secure_device', TrustedDevice.getCookie, name="td_securedevice"),
url(r'^$', views.index, name="mfa_home"),
url(r'devices/add$', TrustedDevice.add,name="mfa_add_new_trusted_device"),
url(r'goto/(.*)', views.goto, name="mfa_goto"),
url(r'selct_method', views.show_methods, name="mfa_methods_list"),
url(r'recheck', helpers.recheck, name="mfa_recheck"),

View File

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

View File

@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup(
name='django-mfa2',
version='2.1.0b1',
version='2.2.0b1',
description='Allows user to add 2FA to their accounts',
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
@@ -24,15 +24,15 @@ setup(
'ua-parser',
'user-agents',
'python-jose',
# 'fido2 == 0.8.1',
'jsonLookup'
'fido2 == 0.9.1',
# 'jsonLookup'
],
dependency_links =["https://github.com/Yubico/python-fido2/tarball/master"],
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",
#"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 1.11",