Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b19d95a7e | ||
|
|
01a2766ef5 | ||
|
|
c34efd6ba9 | ||
|
|
d48e464c16 | ||
|
|
b5b308a757 | ||
|
|
d00083a0cf | ||
|
|
9277819787 | ||
|
|
f7baa822f7 | ||
|
|
d404dc6bee | ||
|
|
56eb8821af | ||
|
|
e79411d04c | ||
|
|
d9590c0ea1 | ||
|
|
b6f8696081 | ||
|
|
c5b62ada65 | ||
|
|
55375f7002 | ||
|
|
b6992d3ced | ||
|
|
3d37d0a51f | ||
|
|
a820206a24 | ||
|
|
bc407ca39b |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
||||
# Change Log
|
||||
|
||||
## 2.0
|
||||
* Dropped support to djangp-1.8 and Python 2.7
|
||||
* Added: never-cache decorator
|
||||
* Fixes to Make Email Method More Robust
|
||||
* Addresses several structure and style issues with TOTP and Email dialogs
|
||||
* Updated to fido2 0.8.1
|
||||
|
||||
Thanks to @swainn
|
||||
|
||||
## v1.9.1
|
||||
* Fixed: is_authenticated #13
|
||||
* Fixed: is_anonymous #6
|
||||
|
||||
thanks to @d3cline,
|
||||
|
||||
## v1.7
|
||||
* Better Error Management
|
||||
* Better Token recheck
|
||||
## v 1.6.0
|
||||
* Fixed some issues for django>= 2.0
|
||||
* Added example app.
|
||||
|
||||
@@ -53,7 +53,7 @@ Depends on
|
||||
MFA_RECHECK_MAX=30 # Maximum in seconds
|
||||
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
|
||||
MFA_HIDE_DISABLE=('FIDO2',) # Can the user disable his key (Added in 1.2.0).
|
||||
MFA_OWNED_BY_ENTERPRISE = FALSE # Who ownes security keys
|
||||
MFA_OWNED_BY_ENTERPRISE = FALSE # Who owns security keys
|
||||
|
||||
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name
|
||||
|
||||
@@ -156,3 +156,5 @@ function some_func() {
|
||||
# Contributors
|
||||
* [mahmoodnasr](https://github.com/mahmoodnasr)
|
||||
* [d3cline](https://github.com/d3cline)
|
||||
* [swainn](https://github.com/swainn)
|
||||
* [unramk](https://github.com/unramk)
|
||||
|
||||
@@ -2,7 +2,10 @@ from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
def send(to,subject,body):
|
||||
From = "%s <%s>" % (settings.EMAIL_FROM, settings.EMAIL_HOST_USER)
|
||||
from_email_address = settings.EMAIL_HOST_USER
|
||||
if '@' not in from_email_address:
|
||||
from_email_address = settings.DEFAULT_FROM_EMAIL
|
||||
From = "%s <%s>" % (settings.EMAIL_FROM, from_email_address)
|
||||
email = EmailMessage(subject,body,From,to)
|
||||
email.content_subtype = "html"
|
||||
return email.send(False)
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.template.context_processors import csrf
|
||||
import datetime,random
|
||||
from random import randint
|
||||
@@ -13,8 +14,9 @@ def sendEmail(request,username,secret):
|
||||
kwargs = {key: username}
|
||||
user = User.objects.get(**kwargs)
|
||||
res=render(request,"mfa_email_token_template.html",{"request":request,"user":user,'otp':secret})
|
||||
return send([user.email],"OTP", str(res.content))
|
||||
return send([user.email],"OTP", res.content.decode())
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
context = csrf(request)
|
||||
if request.method == "POST":
|
||||
@@ -36,6 +38,7 @@ def start(request):
|
||||
if sendEmail(request, request.user.username, request.session["email_secret"]):
|
||||
context["sent"] = True
|
||||
return render(request,"Email/Add.html", context)
|
||||
@never_cache
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
if request.method=="POST":
|
||||
|
||||
10
mfa/FIDO2.py
10
mfa/FIDO2.py
@@ -1,5 +1,5 @@
|
||||
from fido2.client import ClientData
|
||||
from fido2.server import Fido2Server, RelyingParty
|
||||
from fido2.server import Fido2Server, PublicKeyCredentialRpEntity
|
||||
from fido2.ctap2 import AttestationObject, AuthenticatorData
|
||||
from django.template.context_processors import csrf
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@@ -24,7 +24,7 @@ def recheck(request):
|
||||
|
||||
|
||||
def getServer():
|
||||
rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
||||
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
||||
return Fido2Server(rp)
|
||||
def begin_registeration(request):
|
||||
server = getServer()
|
||||
@@ -136,7 +136,11 @@ def authenticate_complete(request):
|
||||
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
|
||||
if not request.user.is_authenticated():
|
||||
try:
|
||||
authenticated=request.user.is_authenticated
|
||||
except:
|
||||
authenticated = request.user.is_authenticated()
|
||||
if not authenticated:
|
||||
res=login(request)
|
||||
if not "location" in res: return reset_cookie(request)
|
||||
return HttpResponse(simplejson.dumps({'status':"OK","redirect":res["location"]}),content_type="application/json")
|
||||
|
||||
@@ -16,5 +16,5 @@ class Migration(migrations.Migration):
|
||||
name='owned_by_enterprise',
|
||||
field=models.NullBooleanField(default=None),
|
||||
),
|
||||
migrations.RunSQL("update mfa_user_keys set owned_by_enterprise = %s where key_type='FIDO2'"%(True if getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) else False ))
|
||||
migrations.RunSQL("update mfa_user_keys set owned_by_enterprise = %s where key_type='FIDO2'"%(1 if getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) else 0 ))
|
||||
]
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
{% block content %}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong> Activate Token by email</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<FORM METHOD="POST" ACTION="{% url 'start_email' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
||||
|
||||
|
||||
{% csrf_token %}
|
||||
{% if invalid %}
|
||||
<div class="alert alert-danger">
|
||||
@@ -28,10 +25,9 @@
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<p>Enter the 6-digits sent to your email.</p>
|
||||
<p>Enter the code sent to your email.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
@@ -40,16 +36,16 @@
|
||||
<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="submit" class="btn btn-lg btn-success btn-block" value="Verify">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</FORM>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -39,7 +39,7 @@
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<p>Enter the 6-digits sent to your email.</p>
|
||||
<p>Enter the code sent to your email.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
$("#modal-footer").prepend("<button id='actionBtn' class='btn btn-danger' onclick='confirmDel("+id+")'>Confirm Deletion</button>")
|
||||
$("#popUpModal").modal()
|
||||
}
|
||||
{% if not HIDE_DISABLE %}
|
||||
|
||||
function toggleKey(id) {
|
||||
$.ajax({
|
||||
url:"{% url 'toggle_key' %}?id="+id,
|
||||
@@ -34,7 +34,6 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
{% endif %}
|
||||
</script>
|
||||
<link href="{% static 'mfa/css/bootstrap-toggle.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'mfa/js/bootstrap-toggle.min.js'%}"></script>
|
||||
@@ -67,7 +66,7 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<table class="table table-striped">
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</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,
|
||||
<p>Scan the image below with the two-factor authentication app on your <a href="javascript:void(0)" onclick="showTOTP()">phone/PC</a>. If you can’t use a barcode,
|
||||
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<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="row" style="padding-top: 10px;">
|
||||
<div class="col-md-6" style="padding-left: 0px">
|
||||
<button class="btn btn-success" onclick="verify()">Enable</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.http import HttpResponse
|
||||
from .models import *
|
||||
from django.template.context_processors import csrf
|
||||
@@ -31,6 +32,7 @@ def recheck(request):
|
||||
return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
|
||||
return render(request,"TOTP/recheck.html", context)
|
||||
|
||||
@never_cache
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
if request.method=="POST":
|
||||
@@ -68,5 +70,6 @@ def verify(request):
|
||||
return HttpResponse("Success")
|
||||
else: return HttpResponse("Error")
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
return render(request,"TOTP/Add.html",{})
|
||||
|
||||
@@ -9,7 +9,10 @@ from django.template.context_processors import csrf
|
||||
from django.template.context import RequestContext
|
||||
from django.conf import settings
|
||||
from . import TrustedDevice
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from user_agents import parse
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
keys=[]
|
||||
context={"keys":User_Keys.objects.filter(username=request.user.username),"UNALLOWED_AUTHEN_METHODS":settings.MFA_UNALLOWED_METHODS
|
||||
@@ -51,6 +54,8 @@ def login(request):
|
||||
callable_func = __get_callable_function__(settings.MFA_LOGIN_CALLBACK)
|
||||
return callable_func(request,username=request.session["base_username"])
|
||||
|
||||
|
||||
@login_required
|
||||
def delKey(request):
|
||||
key=User_Keys.objects.get(id=request.GET["id"])
|
||||
if key.username == request.user.username:
|
||||
@@ -72,14 +77,18 @@ def __get_callable_function__(func_path):
|
||||
raise Exception("Module does not have requested function")
|
||||
return callable_func
|
||||
|
||||
@login_required
|
||||
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]
|
||||
if not key.key_type in settings.MFA_HIDE_DISABLE:
|
||||
key.enabled=not key.enabled
|
||||
key.save()
|
||||
return HttpResponse("OK")
|
||||
else:
|
||||
return HttpResponse("You can't change this method.")
|
||||
else:
|
||||
return HttpResponse("Error")
|
||||
|
||||
|
||||
@@ -6,6 +6,5 @@ django >= 1.7
|
||||
ua-parser
|
||||
user-agents
|
||||
python-jose
|
||||
fido2 == 0.7
|
||||
fido2 == 0.8.1
|
||||
jsonLookup
|
||||
|
||||
|
||||
16
setup.py
16
setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='django-mfa2',
|
||||
version='1.8.0',
|
||||
version='2.0.0',
|
||||
description='Allows user to add 2FA to their accounts',
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -24,32 +24,28 @@ setup(
|
||||
'ua-parser',
|
||||
'user-agents',
|
||||
'python-jose',
|
||||
'fido2 == 0.7.2',
|
||||
'fido2 == 0.8.1',
|
||||
'jsonLookup'
|
||||
],
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
python_requires=">=3.5",
|
||||
include_package_data=True,
|
||||
zip_safe=False, # because we're including static files
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 1.7",
|
||||
"Framework :: Django :: 1.8",
|
||||
"Framework :: Django :: 1.9",
|
||||
"Framework :: Django :: 1.10",
|
||||
"Framework :: Django :: 1.11",
|
||||
"Framework :: Django :: 2.0",
|
||||
"Framework :: Django :: 2.1",
|
||||
"Framework :: Django :: 2.2",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user