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