Initial Import

This commit is contained in:
Mohamed El-Kalioby
2019-01-18 10:18:10 +03:00
parent 5665e46a18
commit 800f60ff53
40 changed files with 1749 additions and 0 deletions

0
mfa/ApproveLogin.py Normal file
View File

119
mfa/FIDO2.py Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
import urls

3
mfa/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

34
mfa/helpers.py Normal file
View 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
View 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

View 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)),
],
),
]

View 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),
),
]

View 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),
),
]

View 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),
),
]

View 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;")
]

View 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)),
],
),
]

View 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),
),
]

View 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),
),
]

View File

21
mfa/models.py Normal file
View 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)

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View 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 %}

View File

@@ -0,0 +1,4 @@
{% extends "login_base.html" %}
{% block form %}
{% include 'FIDO2/recheck.html' with mode='auth' %}
{% endblock %}

View 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
View 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&nbsp;<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
View 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 cant 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 %}

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

View 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 %}

View 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 %}

View 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 %}

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

View 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>&nbsp;&nbsp;<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 %}

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

View 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 %}

View File

@@ -0,0 +1,4 @@
{% extends "login_base.html" %}
{% block form %}
{% include 'U2F/recheck.html' with mode='auth' %}
{% endblock %}

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

View 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" %}

View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

70
mfa/totp.py Normal file
View 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
View 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
View 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"))