Initial Import
This commit is contained in:
0
mfa/ApproveLogin.py
Normal file
0
mfa/ApproveLogin.py
Normal file
119
mfa/FIDO2.py
Normal file
119
mfa/FIDO2.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from fido2.client import ClientData
|
||||
from fido2.server import Fido2Server, RelyingParty
|
||||
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_to_response
|
||||
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.ctap2 import AttestedCredentialData
|
||||
from views import login
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
def recheck(request):
|
||||
context = csrf(request)
|
||||
context["mode"]="recheck"
|
||||
return render_to_response("FIDO2/recheck.html", context, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def getServer():
|
||||
rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
||||
return Fido2Server(rp)
|
||||
def begin_registeration(request):
|
||||
server = getServer()
|
||||
registration_data, state = server.register_begin({
|
||||
u'id': request.user.username.encode("utf8"),
|
||||
u'name': (request.user.first_name + " " + request.user.last_name),
|
||||
u'displayName': request.user.username,
|
||||
}, getUserCredentials(request.user.username))
|
||||
request.session['fido_state'] = state
|
||||
|
||||
return HttpResponse(cbor.dumps(registration_data),content_type='application/octet-stream')
|
||||
@csrf_exempt
|
||||
def complete_reg(request):
|
||||
try:
|
||||
data = cbor.loads(request.body)[0]
|
||||
|
||||
client_data = ClientData(data['clientDataJSON'])
|
||||
att_obj = AttestationObject((data['attestationObject']))
|
||||
server = getServer()
|
||||
auth_data = server.register_complete(
|
||||
request.session['fido_state'],
|
||||
client_data,
|
||||
att_obj
|
||||
)
|
||||
print att_obj.fmt
|
||||
encoded = websafe_encode(auth_data.credential_data)
|
||||
uk=User_Keys()
|
||||
uk.username = request.user.username
|
||||
uk.properties = {"device":encoded,"type":att_obj.fmt,}
|
||||
uk.key_type = "FIDO2"
|
||||
uk.save()
|
||||
return HttpResponse(simplejson.dumps({'status': 'OK'}))
|
||||
except Exception as exp:
|
||||
from raven.contrib.django.raven_compat.models import client
|
||||
import traceback
|
||||
client.captureException()
|
||||
print traceback.format_exc()
|
||||
return HttpResponse(simplejson.dumps({'status': 'ERR',"message":"Error on server, please try again later"}))
|
||||
def start(request):
|
||||
context = csrf(request)
|
||||
return render_to_response("FIDO2/Add.html", context, RequestContext(request))
|
||||
|
||||
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_to_response("FIDO2/Auth.html",context,context_instance=RequestContext(request))
|
||||
|
||||
def authenticate_begin(request):
|
||||
server = getServer()
|
||||
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.dumps(auth_data),content_type="application/octet-stream")
|
||||
|
||||
@csrf_exempt
|
||||
def authenticate_complete(request):
|
||||
credentials = []
|
||||
username=request.session.get("base_username",request.user.username)
|
||||
server=getServer()
|
||||
credentials=getUserCredentials(username)
|
||||
data = cbor.loads(request.body)[0]
|
||||
credential_id = data['credentialId']
|
||||
client_data = ClientData(data['clientDataJSON'])
|
||||
auth_data = AuthenticatorData(data['authenticatorData'])
|
||||
signature = data['signature']
|
||||
|
||||
cred = server.authenticate_complete(
|
||||
request.session.pop('fido_state'),
|
||||
credentials,
|
||||
credential_id,
|
||||
client_data,
|
||||
auth_data,
|
||||
signature
|
||||
)
|
||||
keys = User_Keys.objects.filter(username=username, key_type="FIDO2",enabled=1)
|
||||
import random
|
||||
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"}
|
||||
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"))
|
||||
request.session["mfa"] = mfa
|
||||
login(request)
|
||||
return HttpResponse(simplejson.dumps({'status':"OK","redirect":settings.FIDO_LOGIN_URL}),content_type="application/json")
|
||||
return HttpResponse(simplejson.dumps({'status': "err"}),content_type="application/json")
|
||||
130
mfa/TrustedDevice.py
Normal file
130
mfa/TrustedDevice.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import string
|
||||
import random
|
||||
from django.shortcuts import render_to_response,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
|
||||
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
|
||||
else: return id_generator(size,chars)
|
||||
|
||||
def getUserAgent(request):
|
||||
id=id=request.session.get("td_id",None)
|
||||
if id:
|
||||
tk=User_Keys.objects.get(id=id)
|
||||
if tk.properties.get("user_agent","")!="":
|
||||
ua = user_agents.parse(tk.properties["user_agent"])
|
||||
res = render(None, "TrustedDevices/user-agent.html", context={"ua":ua})
|
||||
return HttpResponse(res)
|
||||
return HttpResponse("")
|
||||
|
||||
def trust_device(request):
|
||||
tk = User_Keys.objects.get(id=request.session["td_id"])
|
||||
tk.properties["status"]="trusted"
|
||||
tk.save()
|
||||
del request.session["td_id"]
|
||||
return HttpResponse("OK")
|
||||
|
||||
def checkTrusted(request):
|
||||
res = ""
|
||||
id=request.session.get("td_id","")
|
||||
if id!="":
|
||||
try:
|
||||
tk = User_Keys.objects.get(id=id)
|
||||
if tk.properties["status"] == "trusted": res = "OK"
|
||||
except:
|
||||
pass
|
||||
return HttpResponse(res)
|
||||
|
||||
def getCookie(request):
|
||||
tk = User_Keys.objects.get(id=request.session["td_id"])
|
||||
|
||||
if tk.properties["status"] == "trusted":
|
||||
context={"added":True}
|
||||
response = render_to_response("TrustedDevices/Done.html", context, context_instance=RequestContext(request))
|
||||
from datetime import datetime, timedelta
|
||||
expires = datetime.now() + timedelta(days=180)
|
||||
tk.expires=expires
|
||||
tk.save()
|
||||
response.set_cookie("deviceid", tk.properties["signature"], expires=expires)
|
||||
return response
|
||||
|
||||
def add(request):
|
||||
context=csrf(request)
|
||||
if request.method=="GET":
|
||||
return render_to_response("TrustedDevices/Add.html",context,context_instance=RequestContext(request))
|
||||
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)
|
||||
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)
|
||||
if agent.is_pc:
|
||||
context["invalid"]="This is a PC, it can't used as a trusted device."
|
||||
else:
|
||||
tk.properties["user_agent"]=ua
|
||||
tk.save()
|
||||
context["success"]=True
|
||||
# tk.properties["user_agent"]=ua
|
||||
# tk.save()
|
||||
# context["success"]=True
|
||||
|
||||
else:
|
||||
context["invalid"]="The username or key is wrong, please check and try again."
|
||||
|
||||
return render_to_response("TrustedDevices/Add.html", context, context_instance=RequestContext(request))
|
||||
|
||||
def start(request):
|
||||
if User_Keys.objects.filter(username=request.user.username,key_type="Trusted Device").count()>= 2:
|
||||
return render_to_response("TrustedDevices/start.html",{"not_allowed":True},context_instance=RequestContext(request))
|
||||
td=None
|
||||
if not request.session.get("td_id",None):
|
||||
td=User_Keys()
|
||||
td.username=request.user.username
|
||||
td.properties={"key":id_generator(),"status":"adding"}
|
||||
td.key_type="Trusted Device"
|
||||
td.save()
|
||||
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"]}
|
||||
except:
|
||||
del request.session["td_id"]
|
||||
return start(request)
|
||||
return render_to_response("TrustedDevices/start.html",context,context_instance=RequestContext(request))
|
||||
|
||||
def send_email(request):
|
||||
body=render(request,"TrustedDevices/email.html",{}).content
|
||||
from Registry_app.Common import send
|
||||
if send(request.user.email,"Add Trusted Device Link",body,delay=False):
|
||||
res="Sent Successfully"
|
||||
else:
|
||||
res="Error occured, please try again later."
|
||||
return HttpResponse(res)
|
||||
|
||||
|
||||
def verify(request):
|
||||
if request.COOKIES.get('deviceid',None):
|
||||
from jose import jwt
|
||||
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"])
|
||||
if uk.enabled and uk.properties["status"] == "trusted":
|
||||
uk.last_used=timezone.now()
|
||||
uk.save()
|
||||
request.session["mfa"] = {"verified": True, "method": "Trusted Device"}
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
return False
|
||||
106
mfa/U2F.py
Normal file
106
mfa/U2F.py
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
from u2flib_server.u2f import (begin_registration, begin_authentication,
|
||||
complete_registration, complete_authentication)
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from django.shortcuts import render_to_response
|
||||
import simplejson
|
||||
from django.template.context import RequestContext
|
||||
from django.template.context_processors import csrf
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from.models import *
|
||||
from .views import login
|
||||
from django.utils import timezone
|
||||
|
||||
def recheck(request):
|
||||
context = csrf(request)
|
||||
context["mode"]="recheck"
|
||||
s = sign(request.user.username)
|
||||
request.session["_u2f_challenge_"] = s[0]
|
||||
context["token"] = s[1]
|
||||
request.session["mfa_recheck"]=True
|
||||
return render_to_response("U2F/recheck.html", context, context_instance=RequestContext(request))
|
||||
|
||||
def process_recheck(request):
|
||||
x=validate(request,request.user.username)
|
||||
if x==True:
|
||||
return HttpResponse(simplejson.dumps({"recheck":True}),content_type="application/json")
|
||||
return x
|
||||
|
||||
def check_errors(request, data):
|
||||
if "errorCode" in data:
|
||||
if data["errorCode"] == 0: return True
|
||||
if data["errorCode"] == 4:
|
||||
return HttpResponse("Invalid Security Key")
|
||||
if data["errorCode"] == 1:
|
||||
return auth(request)
|
||||
return True
|
||||
def validate(request,username):
|
||||
import datetime, random
|
||||
|
||||
data = simplejson.loads(request.POST["response"])
|
||||
print "Checking Errors"
|
||||
res= check_errors(request,data)
|
||||
if res!=True:
|
||||
return res
|
||||
print "Checking Challenge"
|
||||
challenge = request.session.pop('_u2f_challenge_')
|
||||
device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])
|
||||
print device
|
||||
key=User_Keys.objects.get(username=username,properties__shas="$.device.publicKey=%s"%device["publicKey"])
|
||||
key.last_used=timezone.now()
|
||||
key.save()
|
||||
mfa = {"verified": True, "method": "U2F"}
|
||||
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"))
|
||||
request.session["mfa"] = mfa
|
||||
return True
|
||||
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
s=sign(request.session["base_username"])
|
||||
request.session["_u2f_challenge_"]=s[0]
|
||||
context["token"]=s[1]
|
||||
|
||||
return render_to_response("U2F/Auth.html",context,context_instance = RequestContext(request))
|
||||
|
||||
def start(request):
|
||||
enroll = begin_registration(settings.U2F_APPID, [])
|
||||
request.session['_u2f_enroll_'] = enroll.json
|
||||
context=csrf(request)
|
||||
context["token"]=simplejson.dumps(enroll.data_for_client)
|
||||
return render_to_response("U2F/Add.html",context,RequestContext(request))
|
||||
|
||||
|
||||
def bind(request):
|
||||
import hashlib
|
||||
enroll = request.session['_u2f_enroll_']
|
||||
data=simplejson.loads(request.POST["response"])
|
||||
device, cert = complete_registration(enroll, data, [settings.U2F_APPID])
|
||||
cert = x509.load_der_x509_certificate(cert, default_backend())
|
||||
cert_hash=hashlib.md5(cert.public_bytes(Encoding.PEM)).hexdigest()
|
||||
q=User_Keys.objects.filter(key_type="U2F", properties__icontains= cert_hash)
|
||||
if q.exists():
|
||||
return HttpResponse("This key is registered before, it can't be registered again.")
|
||||
User_Keys.objects.filter(username=request.user.username,key_type="U2F").delete()
|
||||
uk = User_Keys()
|
||||
uk.username = request.user.username
|
||||
uk.properties = {"device":simplejson.loads(device.json),"cert":cert_hash}
|
||||
uk.key_type = "U2F"
|
||||
uk.save()
|
||||
return HttpResponse("OK")
|
||||
|
||||
def sign(username):
|
||||
u2f_devices=[d.properties["device"] for d in User_Keys.objects.filter(username=username,key_type="U2F")]
|
||||
challenge = begin_authentication(settings.U2F_APPID, u2f_devices)
|
||||
return [challenge.json,simplejson.dumps(challenge.data_for_client)]
|
||||
|
||||
def verify(request):
|
||||
x= validate(request,request.session["base_username"])
|
||||
if x==True:
|
||||
return login(request)
|
||||
else: return x
|
||||
1
mfa/__init__.py
Normal file
1
mfa/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
import urls
|
||||
3
mfa/admin.py
Normal file
3
mfa/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
34
mfa/helpers.py
Normal file
34
mfa/helpers.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pyotp
|
||||
from .models import *
|
||||
import TrustedDevice
|
||||
import U2F, FIDO2
|
||||
import totp
|
||||
import simplejson
|
||||
from django.shortcuts import HttpResponse
|
||||
from mfa.views import verify,goto
|
||||
def has_mfa(request,username):
|
||||
if User_Keys.objects.filter(username=username,enabled=1).count()>0:
|
||||
return verify(request, username)
|
||||
return False
|
||||
|
||||
def is_mfa(request,ignore_methods=[]):
|
||||
if request.session.get("mfa",{}).get("verified",False):
|
||||
if not request.session.get("mfa",{}).get("method",None) in ignore_methods:
|
||||
return True
|
||||
return False
|
||||
|
||||
def recheck(request):
|
||||
method=request.session.get("mfa",{}).get("method",None)
|
||||
if not method:
|
||||
return HttpResponse(simplejson.dumps({"res":False}),content_type="application/json")
|
||||
if method=="Trusted Device":
|
||||
return HttpResponse(simplejson.dumps({"res":TrustedDevice.verify(request)}),content_type="application/json")
|
||||
elif method=="U2F":
|
||||
return HttpResponse(simplejson.dumps({"html": U2F.recheck(request).content}), content_type="application/json")
|
||||
elif method == "FIDO2":
|
||||
return HttpResponse(simplejson.dumps({"html": FIDO2.recheck(request).content}), content_type="application/json")
|
||||
elif method=="TOTP":
|
||||
return HttpResponse(simplejson.dumps({"html": totp.recheck(request).content}), content_type="application/json")
|
||||
|
||||
|
||||
|
||||
13
mfa/middleware.py
Normal file
13
mfa/middleware.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import time
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
def process(request):
|
||||
next_check=request.session.get('mfa',{}).get("next_check",False)
|
||||
if not next_check: return None
|
||||
now=int(time.time())
|
||||
if now >= next_check:
|
||||
method=request.session["mfa"]["method"]
|
||||
path = request.META["PATH_INFO"]
|
||||
return HttpResponseRedirect(reverse(method+"_auth")+"?next=%s"%(settings.BASE_URL + path).replace("//", "/"))
|
||||
return None
|
||||
22
mfa/migrations/0001_initial.py
Normal file
22
mfa/migrations/0001_initial.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User_Keys',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('username', models.CharField(max_length=50)),
|
||||
('secret_key', models.CharField(max_length=15)),
|
||||
('added_on', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
19
mfa/migrations/0002_user_keys_key_type.py
Normal file
19
mfa/migrations/0002_user_keys_key_type.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='key_type',
|
||||
field=models.CharField(default=b'TOTP', max_length=25),
|
||||
),
|
||||
]
|
||||
19
mfa/migrations/0003_auto_20181114_2159.py
Normal file
19
mfa/migrations/0003_auto_20181114_2159.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0002_user_keys_key_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user_keys',
|
||||
name='secret_key',
|
||||
field=models.CharField(max_length=32),
|
||||
),
|
||||
]
|
||||
19
mfa/migrations/0004_user_keys_enabled.py
Normal file
19
mfa/migrations/0004_user_keys_enabled.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0003_auto_20181114_2159'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='enabled',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
25
mfa/migrations/0005_auto_20181115_2014.py
Normal file
25
mfa/migrations/0005_auto_20181115_2014.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0004_user_keys_enabled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user_keys',
|
||||
name='secret_key',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='properties',
|
||||
field=jsonfield.fields.JSONField(null=True),
|
||||
),
|
||||
migrations.RunSQL("alter table mfa_user_keys modify column properties json;")
|
||||
]
|
||||
27
mfa/migrations/0006_trusted_devices.py
Normal file
27
mfa/migrations/0006_trusted_devices.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0005_auto_20181115_2014'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Trusted_Devices',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('signature', models.CharField(max_length=255)),
|
||||
('key', models.CharField(max_length=6)),
|
||||
('username', models.CharField(max_length=50)),
|
||||
('user_agent', models.CharField(max_length=255)),
|
||||
('status', models.CharField(default=b'adding', max_length=255)),
|
||||
('added_on', models.DateTimeField(auto_now_add=True)),
|
||||
('last_used', models.DateTimeField(default=None, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
22
mfa/migrations/0007_auto_20181230_1549.py
Normal file
22
mfa/migrations/0007_auto_20181230_1549.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0006_trusted_devices'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Trusted_Devices',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='expires',
|
||||
field=models.DateTimeField(default=None, null=True, blank=True),
|
||||
),
|
||||
]
|
||||
19
mfa/migrations/0008_user_keys_last_used.py
Normal file
19
mfa/migrations/0008_user_keys_last_used.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0007_auto_20181230_1549'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='last_used',
|
||||
field=models.DateTimeField(default=None, null=True, blank=True),
|
||||
),
|
||||
]
|
||||
0
mfa/migrations/__init__.py
Normal file
0
mfa/migrations/__init__.py
Normal file
21
mfa/models.py
Normal file
21
mfa/models.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
from jsonfield import JSONField
|
||||
from jose import jwt
|
||||
from django.conf import settings
|
||||
from jsonLookup import hasLookup,shasLookup
|
||||
JSONField.register_lookup(hasLookup)
|
||||
JSONField.register_lookup(shasLookup)
|
||||
|
||||
class User_Keys(models.Model):
|
||||
username=models.CharField(max_length = 50)
|
||||
properties=JSONField(null = True)
|
||||
added_on=models.DateTimeField(auto_now_add = True)
|
||||
key_type=models.CharField(max_length = 25,default = "TOTP")
|
||||
enabled=models.BooleanField(default=True)
|
||||
expires=models.DateTimeField(null=True,default=None,blank=True)
|
||||
last_used=models.DateTimeField(null=True,default=None,blank=True)
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
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)
|
||||
|
||||
10
mfa/templates/ApproveLogin/Add.html
Normal file
10
mfa/templates/ApproveLogin/Add.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
62
mfa/templates/FIDO2/Add.html
Normal file
62
mfa/templates/FIDO2/Add.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<script type="application/javascript" src="{{ STATIC_URL }}js/cbor.js"></script>
|
||||
<script type="application/javascript">
|
||||
function begin_reg(){
|
||||
fetch('{% url 'fido2_begin_reg' %}',{}).then(function(response) {
|
||||
if(response.ok)
|
||||
{
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
throw new Error('Error getting registration data!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
options.publicKey.attestation="direct"
|
||||
console.log(options)
|
||||
|
||||
return navigator.credentials.create(options);
|
||||
}).then(function(attestation) {
|
||||
return fetch('{% url 'fido2_complete_reg' %}', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body: CBOR.encode({
|
||||
"attestationObject": new Uint8Array(attestation.response.attestationObject),
|
||||
"clientDataJSON": new Uint8Array(attestation.response.clientDataJSON),
|
||||
})
|
||||
});
|
||||
}).then(function(response) {
|
||||
|
||||
var stat = response.ok ? 'successful' : 'unsuccessful';
|
||||
return response.json()
|
||||
}).then(function (res)
|
||||
{
|
||||
if (res["status"] =='OK')
|
||||
$("#res").html("<div class='alert alert-success'>Registered Successfully, <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
else
|
||||
$("#res").html("<div class='alert alert-danger'>Registeration Failed as " + res["message"] + ", <a href='javascript:void(0)' onclick='begin_reg()'> try again or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
|
||||
|
||||
}, function(reason) {
|
||||
$("#res").html("<div class='alert alert-danger'>Registeration Failed as " +reason +", <a href='javascript:void(0)' onclick='begin_reg()'> try again </a> or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
})
|
||||
}
|
||||
$(document).ready(setTimeout(begin_reg,500))
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> FIDO2 Security Key</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
|
||||
<div class="row alert alert-pr" id="res">
|
||||
<p style="color: green">Your broswer should ask you to confirm you indentity.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
4
mfa/templates/FIDO2/Auth.html
Normal file
4
mfa/templates/FIDO2/Auth.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block form %}
|
||||
{% include 'FIDO2/recheck.html' with mode='auth' %}
|
||||
{% endblock %}
|
||||
103
mfa/templates/FIDO2/recheck.html
Normal file
103
mfa/templates/FIDO2/recheck.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<script type="application/javascript" src="{{ STATIC_URL }}js/cbor.js"></script>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Security Key</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row">
|
||||
<div style="padding-left: 15px" class="col-md-10 col-md-offset-1" id="main_paragraph" align="center">
|
||||
{% if mode == "auth" %}
|
||||
Welcome back <img src="{% url 'getUserImage' request.session.base_username %}" title="{{ request.session.base_username }}" style="padding: 3px;height: 50px" class="img-circle"/> {{ request.session.base_username }}<br/>
|
||||
<a href="{% url 'mfa_reset_cookie' %}">Not me</a>
|
||||
<br/>
|
||||
|
||||
{% endif %}
|
||||
<p style="color: green">please press the button on your security key to prove it is you.</p>
|
||||
{% if mode == "auth" %}
|
||||
<form id="u2f_login" action="{% url 'fido2_complete_auth' %}" method="post" enctype="multipart/form-data">
|
||||
{% elif mode == "recheck" %}
|
||||
<form id="u2f_login" action="{% url 'u2f_recheck' %}" method="post">
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="response" id="response" value=""/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div style="padding-left: 15px">
|
||||
|
||||
{% if request.session.mfa_methods|length > 1 %}
|
||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function authen()
|
||||
{
|
||||
fetch('{% url 'fido2_begin_auth' %}', {
|
||||
method: 'GET',
|
||||
}).then(function(response) {
|
||||
if(response.ok) return response.arrayBuffer();
|
||||
throw new Error('No credential available to authenticate!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
console.log(options)
|
||||
return navigator.credentials.get(options);
|
||||
}).then(function(assertion) {
|
||||
res=CBOR.encode({
|
||||
"credentialId": new Uint8Array(assertion.rawId),
|
||||
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
|
||||
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
|
||||
"signature": new Uint8Array(assertion.response.signature)
|
||||
});
|
||||
|
||||
return fetch('{% url 'fido2_complete_auth' %}', {
|
||||
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body:res,
|
||||
|
||||
}).then(function (response) {if (response.ok) return res = response.json()}).then(function (res) {
|
||||
if (res.status=="OK")
|
||||
{
|
||||
{% if mode == "auth" %}
|
||||
window.location.href=res.redirect;
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_success_function();
|
||||
{% endif %}
|
||||
}
|
||||
else {
|
||||
{% if mode == "auth" %}
|
||||
alert("Error occured, please try again")
|
||||
login()
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_failed_function();
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:') {
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("FIDO2 must work under secure context")
|
||||
} else {
|
||||
authen()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
95
mfa/templates/MFA.html
Normal file
95
mfa/templates/MFA.html
Normal file
@@ -0,0 +1,95 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<script src="{{ STATIC_URL }}js/qrious.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
function confirmDel(id) {
|
||||
$.ajax({
|
||||
url:"{% url 'mfa_delKey' %}",
|
||||
data:{"id":id},
|
||||
success:function (data) {
|
||||
alert(data)
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
}
|
||||
function deleteKey(id,name)
|
||||
{
|
||||
$("#modal-title").html("Confirm Delete")
|
||||
$("#modal-body").html("Are you sure you want to delete '"+name+"'? you may lose access to your system if this your only 2FA.");
|
||||
$("#actionBtn").remove()
|
||||
$("#modal-footer").prepend("<button id='actionBtn' class='btn btn-danger' onclick='confirmDel("+id+")'>Confirm Deletion</button>")
|
||||
$("#popUpModal").modal()
|
||||
}
|
||||
function toggleKey(id) {
|
||||
$.ajax({
|
||||
url:"{% url 'toggle_key' %}?id="+id,
|
||||
success:function (data) {
|
||||
if (data == "Error")
|
||||
$("#toggle_"+id).toggle()
|
||||
|
||||
},
|
||||
error:function (data) {
|
||||
$("#toggle_"+id).toggle()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<link href="{{ STATIC_URL }}css/bootstrap-toggle.min.css" rel="stylesheet">
|
||||
<script src="{{ STATIC_URL }}js/bootstrap-toggle.min.js"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div align="center">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown">
|
||||
Add Method <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if not 'TOTP' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a href="{% url 'start_new_otop' %}">Authenticator app</a></li>
|
||||
{% endif %}
|
||||
{% if not 'U2F' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a href="{% url 'start_u2f' %}">Security Key</a></li>
|
||||
{% endif %}
|
||||
{% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a href="{% url 'start_fido2' %}">FIDO2 Security Key</a></li>
|
||||
{% endif %}
|
||||
{% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a href="{% url 'start_td' %}">Trusted Device</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
|
||||
<th>Type</th>
|
||||
<th>Date Added</th>
|
||||
<th>Expires On</th>
|
||||
<th>Device</th>
|
||||
<th>Last Used</th>
|
||||
<th>Status</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for key in keys %}
|
||||
<tr>
|
||||
|
||||
<td>{{ key.key_type }}</td>
|
||||
<td>{{ key.added_on }}</td>
|
||||
<td>{{ key.expires }}</td>
|
||||
<td>{% if key.device %}{{ key.device }}{% endif %}</td>
|
||||
<td>{{ key.last_used }}</td>
|
||||
<td><input type="checkbox" id="toggle_{{ key.id }}" {% if key.enabled %}checked{% endif %} data-onstyle="success" data-offstyle="danger" onchange="toggleKey({{ key.id }})" data-toggle="toggle"></td>
|
||||
<td><a href="javascript:void(0)" onclick="deleteKey({{ key.id }},'{{ key.key_type }}')"> <span class="fa fa-trash"></span></a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7" align="center">You didn't have any keys yet.</td> </tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
105
mfa/templates/TOTP/Add.html
Normal file
105
mfa/templates/TOTP/Add.html
Normal file
@@ -0,0 +1,105 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
#two-factor-steps {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
}
|
||||
.row{
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
<script src="{{ STATIC_URL }}js/qrious.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var key="";
|
||||
$(document).ready(function addToken() {
|
||||
$.ajax({
|
||||
"url":"{% url 'get_new_otop' %}",dataType:"JSON",
|
||||
success:function (data) {
|
||||
window.key=data.secret_key;
|
||||
var qr = new QRious({
|
||||
element: document.getElementById('qr'),
|
||||
value: data.qr
|
||||
});
|
||||
$("#second_step").show()
|
||||
}
|
||||
})
|
||||
});
|
||||
function showKey() {
|
||||
$("#modal-title").html("Your Secret Key")
|
||||
$("#modal-body").html("<pre>"+window.key+"</pre")
|
||||
$("#popUpModal").modal('show')
|
||||
}
|
||||
function verify() {
|
||||
answer=$("#answer").val()
|
||||
$.ajax({
|
||||
"url":"{% url 'verify_otop' %}?key="+key+ "&answer="+answer,
|
||||
success:function (data) {
|
||||
if (data == "Error")
|
||||
alert("You entered wrong numbers, please try again")
|
||||
else
|
||||
{
|
||||
alert("Your authenticator is added successfully.")
|
||||
window.location.href="{% url 'mfa_home' %}"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function showTOTP() {
|
||||
$("#modal-title").html("One Time Password Apps")
|
||||
html="<div class='row'><ul>"+
|
||||
"<li>Android: <a href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2' target='_blank'>Google Authenticator</a> | <a href='https://play.google.com/store/apps/details?id=com.authy.authy' target='_blank'>Authy</a></li>"
|
||||
html+="<li>iPhone/iPad: <a href='https://itunes.apple.com/us/app/authy/id494168017' target='_blank'>Authy</a></li> "
|
||||
html+="<li>Chrome: <a href='https://chrome.google.com/webstore/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai?hl=en'>Google Authenticator</a> | <a href='https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=en' target='_blank'>Authy</a></li>"
|
||||
html+="</ul></div>"
|
||||
$("#modal-body").html(html)
|
||||
$('#popUpModal').modal('show')
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
<div class="row" align="center">
|
||||
<h4>Adding Authenticator</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<p>Scan the image below with the two-factor authentication app on your <a href="javascript:void(0)" onclick="showTOTP()">phone/PC</a> phone/PC. If you can’t use a barcode,
|
||||
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div align="center" style="display: none" id="second_step">
|
||||
|
||||
<img id="qr"/>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<p><b>Enter the six-digit code from the application</b></p>
|
||||
<p style="color: #333333;font-size: 10px">After scanning the barcode image, the app will display a six-digit code that you can enter below. </p>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
|
||||
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="padding-left: 0px">
|
||||
<button class="btn btn-success" onclick="verify()">Enable</button>
|
||||
</div>
|
||||
<div class="col-md-6" align="right" style="padding-right: 30px">
|
||||
<a href="{% url 'mfa_home' %}"><button class="btn btn-default">Cancel</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
76
mfa/templates/TOTP/recheck.html
Normal file
76
mfa/templates/TOTP/recheck.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<script type="application/javascript">
|
||||
function send_totp() {
|
||||
$.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
|
||||
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
|
||||
success:function (data) {
|
||||
if (data["recheck"])
|
||||
mfa_success_function();
|
||||
else {
|
||||
mfa_failed_function();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
</script>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> One Time Password</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<FORM METHOD="POST" ACTION="{% url 'totp_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
||||
|
||||
|
||||
{% csrf_token %}
|
||||
{% if invalid %}
|
||||
<div class="alert alert-danger">
|
||||
Sorry, The provided token is not valid.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if quota %}
|
||||
<div class="alert alert-warning">
|
||||
{{ quota }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<p>Enter the 6-digits on your authenticator.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<i class="glyphicon glyphicon-lock"></i>
|
||||
</span>
|
||||
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
<input type="{% if mode == "auth" %}submit{% elif mode == 'recheck' %}button{% endif %}" {% if mode == "recheck" %}onclick="send_totp()" {% endif %} class="btn btn-lg btn-success btn-block" value="Sign in">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</FORM>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="padding-left: 25px">
|
||||
{% if request.session.mfa_methods|length > 1 %}
|
||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
13
mfa/templates/TOTP/verify.html
Normal file
13
mfa/templates/TOTP/verify.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.row{
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block form %}
|
||||
|
||||
{% include "TOTP/recheck.html" with mode='auth' %}
|
||||
|
||||
{% endblock %}
|
||||
123
mfa/templates/TrustedDevices/Add.html
Normal file
123
mfa/templates/TrustedDevices/Add.html
Normal file
@@ -0,0 +1,123 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block head %}
|
||||
<script type="application/javascript">
|
||||
function checkFlag() {
|
||||
if ($("#agree").is(":checked"))
|
||||
return true;
|
||||
else
|
||||
alert("Please agree to the statement first");
|
||||
return false;
|
||||
}
|
||||
function checkTrusted() {
|
||||
$.ajax({
|
||||
url:"{% url 'td_checkTrusted' %}",
|
||||
success:function (data) {
|
||||
if (data == "OK")
|
||||
window.location.href="{% url 'td_securedevice' %}";
|
||||
else
|
||||
setTimeout('checkTrusted()',2000)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(checkTrusted())
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block form %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Add Trusted Device</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if success %}
|
||||
<div class="alert alert-warning">
|
||||
Please check your PC window, to continue the process.
|
||||
</div>
|
||||
{% elif added %}
|
||||
<div class="alert alert-success">
|
||||
Your device is now trusted, please try to <a href="{% url 'login' %}"> login</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="alert alert-warning">Please make sure you are not in private (incognito) mode <i class="fal fa-user-secret"></i></div>
|
||||
<FORM METHOD="POST" ACTION="{% url 'add_td' %}" Id="formLogin" onSubmit="return checkFlag()" name="FrontPage_Form1">
|
||||
{% csrf_token %}
|
||||
{% if invalid %}
|
||||
<div class="alert alert-danger">
|
||||
{{ invalid }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if quota %}
|
||||
<div class="alert alert-warning">
|
||||
{{ quota }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
{# <img class="profile-img" src="{{ STATIC_URL }}img/users.png" alt="">#}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<i class="glyphicon glyphicon-user"></i>
|
||||
</span>
|
||||
<input class="form-control" id="username" size="30" MaxLength="30" placeholder="Username" name="username" value="{{ username }}" type="text" autofocus autocomplete="on">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-key"></i>
|
||||
</span>
|
||||
<input class="form-control" placeholder="e.g GAK-Y2M" id='key' style="text-transform: uppercase" name="key" type="text" size="9" MaxLength="9" value="{{ key }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="input-group">
|
||||
<input id='agree' name="agree" type="checkbox"><span style="color: red"> I confirm that this device is mine and it is only used by me.</span>
|
||||
|
||||
</div>
|
||||
{% comment %}
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
|
||||
|
||||
<select size="1" name="Institution_Code" style="font-size: 10pt; font-family: Calibri; height: 34px;width: 230px">
|
||||
{% for ins in institutes %}
|
||||
<option value="{{ ins.institution_code }}">{{ ins.alias }}</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-lg btn-success btn-block" value="Trust Device">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel-footer ">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
27
mfa/templates/TrustedDevices/Done.html
Normal file
27
mfa/templates/TrustedDevices/Done.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% block form %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Add Trusted Device</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-success">
|
||||
Your device is now trusted, please try to <a href="{{ HOST }}{{ BASE_URL }}accounts/login/"> login</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel-footer ">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
4
mfa/templates/TrustedDevices/email.html
Normal file
4
mfa/templates/TrustedDevices/email.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<p>Dear {{ request.user.last_name }}, {{ request.user.first_name }}</p>
|
||||
<p>You requested the link to add a new trusted device, please follow the link below<br/>
|
||||
<a href="{{ HOST }}{% url 'mfa_add_new_trusted_device' %}">{{ HOST }}{% url 'mfa_add_new_trusted_device' %}</a>
|
||||
</p>
|
||||
100
mfa/templates/TrustedDevices/start.html
Normal file
100
mfa/templates/TrustedDevices/start.html
Normal file
@@ -0,0 +1,100 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
#two-factor-steps {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
}
|
||||
.row{
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function sendEmail() {
|
||||
$("#modal-title").html("Send Link")
|
||||
$("#modal-body").html("Sending Email, Please wait....");
|
||||
$("#popUpModal").modal();
|
||||
$.ajax({
|
||||
"url":"{% url 'td_sendemail' %}",
|
||||
success:function (data) {
|
||||
alert(data);
|
||||
$("#popUpModal").modal('toggle')
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
function failedMFA() {
|
||||
$("#modal-body").html("<div class='alert alert-danger'>Failed to validate you, please <a href='javascript:void(0)' onclick='getUserAgent()'>try again</a></div>")
|
||||
}
|
||||
function checkMFA() {
|
||||
recheck_mfa(trustDevice,failedMFA,true)
|
||||
}
|
||||
function trustDevice() {
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
|
||||
"url":"{% url 'td_trust_device' %}",
|
||||
success: function (data) {
|
||||
if (data == "OK")
|
||||
{
|
||||
alert("Your are done, your device should show final confirmation")
|
||||
window.location.href="{% url 'mfa_home' %}"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
function getUserAgent() {
|
||||
$.ajax({
|
||||
"url":"{% url 'td_get_useragent' %}",success: function(data)
|
||||
{
|
||||
if (data == "")
|
||||
setTimeout('getUserAgent()',5000)
|
||||
else
|
||||
{
|
||||
$("#modal-title").html("Confirm Trusted Device")
|
||||
$("#actionBtn").remove();
|
||||
$("#modal-footer").prepend("<button id='actionBtn' class='btn btn-success' onclick='checkMFA()'>Trust Device</button>")
|
||||
$("#modal-body").html(data)
|
||||
$("#popUpModal").modal()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(getUserAgent())
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
<div class="row" align="center">
|
||||
<h4>Add Trusted Device</h4>
|
||||
</div>
|
||||
|
||||
<div class="row" >
|
||||
{% if not_allowed %}
|
||||
<div class="alert alert-danger">You can't add any more devices, you need to remove previously trusted devices first.</div>
|
||||
{% else %}
|
||||
<p style="color: green">Allow access from mobile phone and tables.</p>
|
||||
<h5>Steps:</h5>
|
||||
<ol>
|
||||
<li>Using your mobile/table, open Chrome/Firefox.</li>
|
||||
<li>Go to <b>{{ HOST }}{{ BASE_URL }}devices/add</b> <a href="javascript:void(0)" onclick="sendEmail()" title="Send to my email"><i class="fas fa-paper-plane"></i></a></li>
|
||||
<li>Enter your username & following 6 digits<br/>
|
||||
<span style="font-size: 16px;font-weight: bold; margin-left: 50px">{{ key|slice:":3" }} - {{ key|slice:"3:" }}</span>
|
||||
</li>
|
||||
<li>This window will ask to confirm the device.</li>
|
||||
|
||||
</ol>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% include 'mfa_check.html' %}
|
||||
{% endblock %}
|
||||
14
mfa/templates/TrustedDevices/user-agent.html
Normal file
14
mfa/templates/TrustedDevices/user-agent.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>Browser: </th>
|
||||
<td>{{ ua.browser.family }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Version: </th>
|
||||
<td>{{ ua.browser.version_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Device: </th>
|
||||
<td>{{ ua.device.brand }} / {{ ua.device.model }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
48
mfa/templates/U2F/Add.html
Normal file
48
mfa/templates/U2F/Add.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
#two-factor-steps {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
}
|
||||
.row{
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
<script src="{{ STATIC_URL }}js/u2f-api.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function addToken() {
|
||||
data=JSON.parse('{{ token|safe }}')
|
||||
console.log(data)
|
||||
u2f.register(data.appId,data.registerRequests,data.registeredKeys,function (response) {
|
||||
$.ajax({
|
||||
"url":"{% url 'bind_u2f' %}",method:"POST",
|
||||
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","response":JSON.stringify(response)},
|
||||
success:function (data) {
|
||||
if (data == "OK")
|
||||
{
|
||||
alert("Your device is added successfully.")
|
||||
window.location.href="{% url 'mfa_home' %}"
|
||||
}
|
||||
}
|
||||
})
|
||||
},5000)
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
<div class="row" align="center">
|
||||
<h4>Adding Security Key</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p style="color: green">Your secure Key should be flashing now, please press on button.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
4
mfa/templates/U2F/Auth.html
Normal file
4
mfa/templates/U2F/Auth.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block form %}
|
||||
{% include 'U2F/recheck.html' with mode='auth' %}
|
||||
{% endblock %}
|
||||
97
mfa/templates/U2F/recheck.html
Normal file
97
mfa/templates/U2F/recheck.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Security Key</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row">
|
||||
<div style="padding-left: 15px" class="col-md-10 col-md-offset-1" id="main_paragraph" align="center">
|
||||
<p style="color: green">Your key should be flashing now, please press the button.</p>
|
||||
{% if mode == "auth" %}
|
||||
<form id="u2f_login" action="{% url 'u2f_verify' %}" method="post">
|
||||
{% elif mode == "recheck" %}
|
||||
<form id="u2f_login" action="{% url 'u2f_recheck' %}" method="post">
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="response" id="response" value=""/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div style="padding-left: 15px">
|
||||
|
||||
{% if request.session.mfa_methods|length > 1 %}
|
||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ STATIC_URL }}js/u2f-api.js" type="text/javascript"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:')
|
||||
{
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("U2F must work under secure context")
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
data = JSON.parse('{{ token|safe }}')
|
||||
console.log(data)
|
||||
u2f.sign(data.appId, data.challenge, data.registeredKeys, function (response) {
|
||||
console.log(response)
|
||||
if (response.hasOwnProperty("errorCode") && response.errorCode != 0 )
|
||||
{
|
||||
if (response.errorCode == 4)
|
||||
{
|
||||
alert("Invalid Security Key, this security isn't linked to your account")
|
||||
}
|
||||
else if (response.errorCode == 5)
|
||||
{
|
||||
alert("Verification Timeout, please refresh the page to try again")
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("Unspecified error, please try again later or try another browser.")
|
||||
}
|
||||
}
|
||||
{% if mode == "auth" %}
|
||||
else {
|
||||
$("#response").val(JSON.stringify(response))
|
||||
$("#u2f_login").submit();
|
||||
}
|
||||
{% elif mode == "recheck" %}
|
||||
else {
|
||||
$.ajax({
|
||||
"url":"{% url 'u2f_recheck' %}",
|
||||
method: "POST",
|
||||
data: {"csrfmiddlewaretoken":"{{ csrf_token }}","response":JSON.stringify(response)},
|
||||
success:function (data) {
|
||||
if (data["recheck"]) {
|
||||
mfa_success_function();
|
||||
}
|
||||
else {
|
||||
mfa_failed_function();
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
{% endif %}
|
||||
}, 5000)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
36
mfa/templates/mfa_check.html
Normal file
36
mfa/templates/mfa_check.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<script type="application/javascript">
|
||||
mfa_success_function=null;
|
||||
mfa_failed_function=null;
|
||||
function is_mfa() {
|
||||
{% if request.session.mfa.verified %}
|
||||
return true;
|
||||
{% else %}
|
||||
return false;
|
||||
{% endif %}
|
||||
}
|
||||
function recheck_mfa(success_func,fail_func,must_mfa) {
|
||||
if (!must_mfa) success_func()
|
||||
window.mfa_success_function=success_func;
|
||||
window.mfa_failed_function=fail_func;
|
||||
$.ajax({
|
||||
"url":"{% url 'mfa_recheck' %}",
|
||||
success:function (data) {
|
||||
if (data.hasOwnProperty("res")) {
|
||||
if (data["res"])
|
||||
success_func();
|
||||
else fail_func();
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#modal-title").html("Recheck Indentity")
|
||||
$("#modal-body").html(data["html"])
|
||||
$("#popUpModal").modal()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
{% include "modal.html" %}
|
||||
26
mfa/templates/select_mfa_method.html
Normal file
26
mfa/templates/select_mfa_method.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends "login_base.html" %}
|
||||
{% block form %}
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Select Second Verification Method</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul>
|
||||
{% for method in request.session.mfa_methods %}
|
||||
|
||||
<li><a href="{% url "mfa_goto" method %}">
|
||||
{% if method == "TOTP" %}Authenticator App
|
||||
{% elif method == "U2F" %}Secure Key
|
||||
{% elif method == "FIDO2" %}FIDO2 Secure Key
|
||||
{% endif %}
|
||||
</a> </li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
mfa/tests.py
Normal file
3
mfa/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
70
mfa/totp.py
Normal file
70
mfa/totp.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from django.shortcuts import render,render_to_response
|
||||
from django.http import HttpResponse
|
||||
from .models import *
|
||||
from django.template.context_processors import csrf
|
||||
import simplejson
|
||||
from django.template.context import RequestContext
|
||||
from django.conf import settings
|
||||
import pyotp
|
||||
from .views import login
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
import random
|
||||
def verify_login(request,username,token):
|
||||
for key in User_Keys.objects.filter(username=username,key_type = "TOTP"):
|
||||
totp = pyotp.TOTP(key.properties["secret_key"])
|
||||
if totp.verify(token,valid_window = 30):
|
||||
key.last_used=timezone.now()
|
||||
key.save()
|
||||
mfa = {"verified": True, "method": "TOTP"}
|
||||
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"))
|
||||
request.session["mfa"] = mfa
|
||||
return True
|
||||
return False
|
||||
|
||||
def recheck(request):
|
||||
context = csrf(request)
|
||||
context["mode"]="recheck"
|
||||
if request.method == "POST":
|
||||
if verify_login(request,request.user.username, token=request.POST["otp"]):
|
||||
return HttpResponse(simplejson.dumps({"recheck": True}), content_type="application/json")
|
||||
else:
|
||||
return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
|
||||
return render_to_response("TOTP/recheck.html", context, context_instance=RequestContext(request))
|
||||
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
if request.method=="POST":
|
||||
if verify_login(request,request.session["base_username"],token = request.POST["otp"]):
|
||||
return login(request)
|
||||
context["invalid"]=True
|
||||
return render_to_response("TOTP/verify.html", context, context_instance = RequestContext(request))
|
||||
|
||||
|
||||
|
||||
def getToken(request):
|
||||
secret_key=pyotp.random_base32()
|
||||
totp = pyotp.TOTP(secret_key)
|
||||
print "Answer is", totp.now()
|
||||
request.session["new_mfa_answer"]=totp.now()
|
||||
return HttpResponse(simplejson.dumps({"qr":pyotp.totp.TOTP(secret_key).provisioning_uri(str(request.user.username), issuer_name = settings.TOKEN_ISSUER_NAME),
|
||||
"secret_key": secret_key}))
|
||||
def verify(request):
|
||||
answer=request.GET["answer"]
|
||||
secret_key=request.GET["key"]
|
||||
totp = pyotp.TOTP(secret_key)
|
||||
if totp.verify(answer,valid_window = 60):
|
||||
uk=User_Keys()
|
||||
uk.username=request.user.username
|
||||
uk.properties={"secret_key":secret_key}
|
||||
#uk.name="Authenticatior #%s"%User_Keys.objects.filter(username=user.username,type="TOTP")
|
||||
uk.key_type="TOTP"
|
||||
uk.save()
|
||||
return HttpResponse("Success")
|
||||
else: return HttpResponse("Error")
|
||||
|
||||
def start(request):
|
||||
return render_to_response("TOTP/Add.html",{},context_instance = RequestContext(request ))
|
||||
45
mfa/urls.py
Normal file
45
mfa/urls.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.conf.urls import url
|
||||
import views,totp,U2F,TrustedDevice,helpers,FIDO2
|
||||
|
||||
urlpatterns = [
|
||||
url(r'totp/start/', totp.start , name="start_new_otop"),
|
||||
url(r'totp/getToken', totp.getToken , name="get_new_otop"),
|
||||
url(r'totp/verify', totp.verify, name="verify_otop"),
|
||||
url(r'totp/auth', totp.auth, name="totp_auth"),
|
||||
url(r'totp/recheck', totp.recheck, name="totp_recheck"),
|
||||
|
||||
url(r'u2f/$', U2F.start, name="start_u2f"),
|
||||
url(r'u2f/bind', U2F.bind, name="bind_u2f"),
|
||||
url(r'u2f/auth', U2F.auth, name="u2f_auth"),
|
||||
url(r'u2f/process_recheck', U2F.process_recheck, name="u2f_recheck"),
|
||||
url(r'u2f/verify', U2F.verify, name="u2f_verify"),
|
||||
|
||||
url(r'fido2/$', FIDO2.start, name="start_fido2"),
|
||||
url(r'fido2/auth', FIDO2.auth, name="fido2_auth"),
|
||||
url(r'fido2/begin_auth', FIDO2.authenticate_begin, name="fido2_begin_auth"),
|
||||
url(r'fido2/complete_auth', FIDO2.authenticate_complete, name="fido2_complete_auth"),
|
||||
url(r'fido2/begin_reg', FIDO2.begin_registeration, name="fido2_begin_reg"),
|
||||
url(r'fido2/complete_reg', FIDO2.complete_reg, name="fido2_complete_reg"),
|
||||
url(r'u2f/bind', U2F.bind, name="bind_u2f"),
|
||||
url(r'u2f/auth', U2F.auth, name="u2f_auth"),
|
||||
url(r'u2f/process_recheck', U2F.process_recheck, name="u2f_recheck"),
|
||||
url(r'u2f/verify', U2F.verify, name="u2f_verify"),
|
||||
|
||||
|
||||
url(r'td/$', TrustedDevice.start, name="start_td"),
|
||||
url(r'td/add', TrustedDevice.add, name="add_td"),
|
||||
url(r'td/send_link', TrustedDevice.send_email, name="td_sendemail"),
|
||||
url(r'td/get-ua', TrustedDevice.getUserAgent, name="td_get_useragent"),
|
||||
url(r'td/trust', TrustedDevice.trust_device, name="td_trust_device"),
|
||||
url(r'u2f/checkTrusted', TrustedDevice.checkTrusted, name="td_checkTrusted"),
|
||||
url(r'u2f/secure_device', TrustedDevice.getCookie, name="td_securedevice"),
|
||||
|
||||
url(r'^$', views.index, name="mfa_home"),
|
||||
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"),
|
||||
url(r'toggleKey', views.toggleKey, name="toggle_key"),
|
||||
url(r'delete', views.delKey, name="mfa_delKey"),
|
||||
url(r'reset', views.reset_cookie, name="mfa_reset_cookie"),
|
||||
|
||||
]
|
||||
85
mfa/views.py
Normal file
85
mfa/views.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from django.shortcuts import render,render_to_response
|
||||
from django.http import HttpResponse,HttpResponseRedirect
|
||||
from .models import *
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.context_processors import csrf
|
||||
from django.template.context import RequestContext
|
||||
from django.conf import settings
|
||||
import TrustedDevice
|
||||
from user_agents import parse
|
||||
def index(request):
|
||||
keys=[]
|
||||
context={"keys":User_Keys.objects.filter(username=request.user.username),"UNALLOWED_AUTHEN_METHODS":settings.MFA_UNALLOWED_METHODS}
|
||||
for k in context["keys"]:
|
||||
if k.key_type =="Trusted Device" :
|
||||
setattr(k,"device",parse(k.properties.get("user_agent","-----")))
|
||||
elif k.key_type == "FIDO2":
|
||||
setattr(k,"device",k.properties.get("type","----"))
|
||||
keys.append(k)
|
||||
context["keys"]=keys
|
||||
return render_to_response("MFA.html",context,context_instance=RequestContext(request))
|
||||
|
||||
def verify(request,username):
|
||||
request.session["base_username"] = username
|
||||
#request.session["base_password"] = password
|
||||
keys=User_Keys.objects.filter(username=username,enabled=1)
|
||||
methods=list(set([k.key_type for k in keys]))
|
||||
print methods
|
||||
if "Trusted Device" in methods and not request.session.get("checked_trusted_device",False):
|
||||
if TrustedDevice.verify(request):
|
||||
return login(request)
|
||||
methods.remove("Trusted Device")
|
||||
request.session["mfa_methods"] = methods
|
||||
if len(methods)==1:
|
||||
return HttpResponseRedirect(reverse(methods[0].lower()+"_auth"))
|
||||
return show_methods(request)
|
||||
|
||||
def show_methods(request):
|
||||
return render_to_response("select_mfa_method.html", {}, context_instance = RequestContext(request))
|
||||
|
||||
def reset_cookie(request):
|
||||
response=HttpResponseRedirect(settings.BASE_URL)
|
||||
response.delete_cookie("base_username")
|
||||
return response
|
||||
def login(request):
|
||||
from django.contrib import auth
|
||||
from django.conf import settings
|
||||
callable_func = __get_callable_function__(settings.MFA_LOGIN_CALLBACK)
|
||||
return callable_func(request,username=request.session["base_username"])
|
||||
|
||||
def delKey(request):
|
||||
key=User_Keys.objects.get(id=request.GET["id"])
|
||||
if key.username == request.user.username:
|
||||
key.delete()
|
||||
return HttpResponse("Deleted Successfully")
|
||||
else:
|
||||
return HttpResponse("Error: You own this token so you can't delete it")
|
||||
|
||||
def __get_callable_function__(func_path):
|
||||
import importlib
|
||||
if not '.' in func_path:
|
||||
raise Exception("class Name should include modulename.classname")
|
||||
|
||||
parsed_str = func_path.split(".")
|
||||
module_name , func_name = ".".join(parsed_str[:-1]) , parsed_str[-1]
|
||||
imported_module = importlib.import_module(module_name)
|
||||
callable_func = getattr(imported_module,func_name)
|
||||
if not callable_func:
|
||||
raise Exception("Module does not have requested function")
|
||||
return callable_func
|
||||
|
||||
def toggleKey(request):
|
||||
id=request.GET["id"]
|
||||
q=User_Keys.objects.filter(username=request.user.username, id=id)
|
||||
if q.count()==1:
|
||||
key=q[0]
|
||||
key.enabled=not key.enabled
|
||||
key.save()
|
||||
return HttpResponse("OK")
|
||||
else:
|
||||
return HttpResponse("Error")
|
||||
|
||||
def goto(request,method):
|
||||
return HttpResponseRedirect(reverse(method.lower()+"_auth"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user