Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa0fd3172c | |||
| fcfc6904b6 | |||
| ace60d7343 | |||
| aa76553edb | |||
| 33938ac6e8 | |||
| 5c799f15e9 | |||
| f33631ab48 | |||
| 11079528cb | |||
| b4ca28479a | |||
| e1d2bfa330 | |||
| a9ac8e7709 | |||
| 97ba9eeb44 | |||
| 71a0378ef7 | |||
| 7055a7da02 | |||
| e63e315f29 | |||
| b381a43927 | |||
| a31ecff6ef | |||
| 3eab9cbd46 | |||
| fc301a5a06 | |||
| c71c6d7cab | |||
| 79e9e906dd | |||
| fe1e2abd6f | |||
| 5db1bdca81 | |||
| 1c15c74444 | |||
| bc59406713 | |||
| 3ea974f750 | |||
| 37184edfb9 | |||
| 62a240ac28 | |||
| aa1d417c08 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
.pyre/
|
|
||||||
example/venv
|
example/venv
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
repos:
|
|
||||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
|
||||||
rev: 23.12.1
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
# It is recommended to specify the latest version of Python
|
|
||||||
# supported by your project here, or alternatively use
|
|
||||||
# pre-commit's default_language_version, see
|
|
||||||
# https://pre-commit.com/#top_level-default_language_version
|
|
||||||
language_version: python3.11
|
|
||||||
|
|
||||||
- repo: https://github.com/fsouza/pre-commit-pyre-check
|
|
||||||
rev: '199dc59' # Use the sha / tag you want to point at
|
|
||||||
hooks:
|
|
||||||
- id: pyre-check
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"source_directories": [
|
|
||||||
"."
|
|
||||||
],
|
|
||||||
"search_path": [
|
|
||||||
"env/lib/python3.11/site-packages/"
|
|
||||||
],
|
|
||||||
"ignore_all_errors":[
|
|
||||||
"*env*/*",
|
|
||||||
"example/venv/*",
|
|
||||||
"build/*",
|
|
||||||
"example/*"
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,6 @@
|
|||||||
A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Email Tokens , Trusted Devices and backup codes.
|
A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Email Tokens , Trusted Devices and backup codes.
|
||||||
|
|
||||||
[](https://fidoalliance.org/passkeys/)
|
[](https://fidoalliance.org/passkeys/)
|
||||||
|
|
||||||
[](https://github.com/psf/black)
|
|
||||||
[](https://pyre-check.org/)
|
|
||||||
### Pip Stats
|
### Pip Stats
|
||||||
[](https://badge.fury.io/py/django-mfa2)
|
[](https://badge.fury.io/py/django-mfa2)
|
||||||
[](https://pepy.tech/project/django-mfa2)
|
[](https://pepy.tech/project/django-mfa2)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ from django.http import HttpResponseRedirect
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import authenticate,login,logout
|
from django.contrib.auth import authenticate,login,logout
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
def loginView(request):
|
def loginView(request):
|
||||||
context={}
|
context={}
|
||||||
if request.method=="POST":
|
if request.method=="POST":
|
||||||
@@ -13,22 +11,18 @@ def loginView(request):
|
|||||||
user=authenticate(username=username,password=password)
|
user=authenticate(username=username,password=password)
|
||||||
if user:
|
if user:
|
||||||
from mfa.helpers import has_mfa
|
from mfa.helpers import has_mfa
|
||||||
|
res = has_mfa(username = username, request = request) # has_mfa returns false or HttpResponseRedirect
|
||||||
res = has_mfa(
|
|
||||||
username=username, request=request
|
|
||||||
) # has_mfa returns false or HttpResponseRedirect
|
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
return create_session(request,user.username)
|
return create_session(request,user.username)
|
||||||
context["invalid"]=True
|
context["invalid"]=True
|
||||||
return render(request, "login.html", context)
|
return render(request, "login.html", context)
|
||||||
|
|
||||||
|
|
||||||
def create_session(request,username):
|
def create_session(request,username):
|
||||||
user=User.objects.get(username=username)
|
user=User.objects.get(username=username)
|
||||||
user.backend = "django.contrib.auth.backends.ModelBackend"
|
user.backend='django.contrib.auth.backends.ModelBackend'
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return HttpResponseRedirect(reverse("home"))
|
return HttpResponseRedirect(reverse('home'))
|
||||||
|
|
||||||
|
|
||||||
def logoutView(request):
|
def logoutView(request):
|
||||||
|
|||||||
@@ -21,65 +21,65 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = "#9)q!_i3@pr-^3oda(e^3$x!kq3b4f33#5l@+=+&vuz+p6gb3g"
|
SECRET_KEY = '#9)q!_i3@pr-^3oda(e^3$x!kq3b4f33#5l@+=+&vuz+p6gb3g'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
'django.contrib.admin',
|
||||||
"django.contrib.auth",
|
'django.contrib.auth',
|
||||||
"django.contrib.contenttypes",
|
'django.contrib.contenttypes',
|
||||||
"django.contrib.sessions",
|
'django.contrib.sessions',
|
||||||
"django.contrib.messages",
|
'django.contrib.messages',
|
||||||
"django.contrib.staticfiles",
|
'django.contrib.staticfiles',
|
||||||
"mfa",
|
'mfa',
|
||||||
"sslserver",
|
'sslserver'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
'django.middleware.security.SecurityMiddleware',
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
"django.middleware.common.CommonMiddleware",
|
'django.middleware.common.CommonMiddleware',
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "example.urls"
|
ROOT_URLCONF = 'example.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
"DIRS": [os.path.join(BASE_DIR, "example", "templates")],
|
'DIRS': [os.path.join(BASE_DIR ,'example','templates' )],
|
||||||
"APP_DIRS": True,
|
'APP_DIRS': True,
|
||||||
"OPTIONS": {
|
'OPTIONS': {
|
||||||
"context_processors": [
|
'context_processors': [
|
||||||
"django.template.context_processors.debug",
|
'django.template.context_processors.debug',
|
||||||
"django.template.context_processors.request",
|
'django.template.context_processors.request',
|
||||||
"django.contrib.auth.context_processors.auth",
|
'django.contrib.auth.context_processors.auth',
|
||||||
"django.contrib.messages.context_processors.messages",
|
'django.contrib.messages.context_processors.messages',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = "example.wsgi.application"
|
WSGI_APPLICATION = 'example.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
'default': {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
"NAME": "test_db",
|
'NAME': 'test_db',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +89,16 @@ DATABASES = {
|
|||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -106,9 +106,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
@@ -120,38 +120,37 @@ USE_TZ = True
|
|||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = '/static/'
|
||||||
#STATIC_ROOT=(os.path.join(BASE_DIR,'static'))
|
#STATIC_ROOT=(os.path.join(BASE_DIR,'static'))
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
STATICFILES_DIRS=[os.path.join(BASE_DIR,'static')]
|
||||||
LOGIN_URL="/auth/login"
|
LOGIN_URL="/auth/login"
|
||||||
|
|
||||||
EMAIL_FROM = "Test App"
|
EMAIL_FROM='Test App'
|
||||||
EMAIL_HOST="smtp.gmail.com"
|
EMAIL_HOST="smtp.gmail.com"
|
||||||
EMAIL_PORT=587
|
EMAIL_PORT=587
|
||||||
EMAIL_HOST_USER=""
|
EMAIL_HOST_USER=""
|
||||||
EMAIL_HOST_PASSWORD = ""
|
EMAIL_HOST_PASSWORD=''
|
||||||
EMAIL_USE_TLS=True
|
EMAIL_USE_TLS=True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MFA_UNALLOWED_METHODS=() # Methods that shouldn't be allowed for the user
|
MFA_UNALLOWED_METHODS=() # Methods that shouldn't be allowed for the user
|
||||||
MFA_LOGIN_CALLBACK="example.auth.create_session" # A function that should be called by username to login the user in session
|
MFA_LOGIN_CALLBACK="example.auth.create_session" # A function that should be called by username to login the user in session
|
||||||
MFA_RECHECK=True # Allow random rechecking of the user
|
MFA_RECHECK=True # Allow random rechecking of the user
|
||||||
MFA_RECHECK_MIN=10 # Minimum interval in seconds
|
MFA_RECHECK_MIN=10 # Minimum interval in seconds
|
||||||
MFA_RECHECK_MAX=30 # Maximum in seconds
|
MFA_RECHECK_MAX=30 # Maximum in seconds
|
||||||
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
|
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
|
||||||
MFA_HIDE_DISABLE = ("",) # Can the user disable his key (Added in 1.2.0).
|
MFA_HIDE_DISABLE=('',) # Can the user disable his key (Added in 1.2.0).
|
||||||
MFA_REDIRECT_AFTER_REGISTRATION="registered"
|
MFA_REDIRECT_AFTER_REGISTRATION="registered"
|
||||||
MFA_SUCCESS_REGISTRATION_MSG="Go to Home"
|
MFA_SUCCESS_REGISTRATION_MSG="Go to Home"
|
||||||
MFA_ALWAYS_GO_TO_LAST_METHOD = True
|
MFA_ALWAYS_GO_TO_LAST_METHOD = True
|
||||||
MFA_ENFORCE_RECOVERY_METHOD = True
|
MFA_ENFORCE_RECOVERY_METHOD = True
|
||||||
MFA_RENAME_METHODS = {"RECOVERY":"Backup Codes","FIDO2":"Biometric Authentication"}
|
MFA_RENAME_METHODS = {"RECOVERY":"Backup Codes","FIDO2":"Biometric Authentication"}
|
||||||
PASSWORD_HASHERS = DEFAULT_PASSWORD_HASHERS #Comment if PASSWORD_HASHER already set
|
PASSWORD_HASHERS = DEFAULT_PASSWORD_HASHERS #Comment if PASSWORD_HASHER already set
|
||||||
PASSWORD_HASHERS += ["mfa.recovery.Hash"]
|
PASSWORD_HASHERS += ['mfa.recovery.Hash']
|
||||||
RECOVERY_ITERATION = 1 #Number of iteration for recovery code, higher is more secure, but uses more resources for generation and check...
|
RECOVERY_ITERATION = 1 #Number of iteration for recovery code, higher is more secure, but uses more resources for generation and check...
|
||||||
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name
|
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name
|
||||||
|
|
||||||
U2F_APPID="https://localhost:9000" #URL For U2F
|
U2F_APPID="https://localhost:9000" #URL For U2F
|
||||||
FIDO_SERVER_ID = (
|
FIDO_SERVER_ID="localhost" # Server rp id for FIDO2, it the full domain of your project
|
||||||
"localhost" # Server rp id for FIDO2, it the full domain of your project
|
|
||||||
)
|
|
||||||
FIDO_SERVER_NAME="TestApp"
|
FIDO_SERVER_NAME="TestApp"
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ from django.contrib import admin
|
|||||||
from django.urls import path,re_path,include
|
from django.urls import path,re_path,include
|
||||||
from . import views,auth
|
from . import views,auth
|
||||||
from mfa import TrustedDevice
|
from mfa import TrustedDevice
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path("mfa/", include("mfa.urls")),
|
path('mfa/', include('mfa.urls')),
|
||||||
path("auth/login", auth.loginView, name="login"),
|
path('auth/login',auth.loginView,name="login"),
|
||||||
path("auth/logout", auth.logoutView, name="logout"),
|
path('auth/logout',auth.logoutView,name="logout"),
|
||||||
path("devices/add/", TrustedDevice.add, name="add_trusted_device"),
|
path('devices/add/', TrustedDevice.add,name="add_trusted_device"),
|
||||||
re_path("^$", views.home, name="home"),
|
re_path('^$',views.home,name='home'),
|
||||||
path("registered/", views.registered, name="registered"),
|
path('registered/',views.registered,name='registered')
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from django.contrib.auth.decorators import login_required
|
|||||||
def home(request):
|
def home(request):
|
||||||
return render(request,"home.html",{})
|
return render(request,"home.html",{})
|
||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def registered(request):
|
def registered(request):
|
||||||
return render(request,"home.html",{"registered":True})
|
return render(request,"home.html",{"registered":True})
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
except ImportError:
|
except:
|
||||||
from django.core.urlresolver import reverse # pyre-ignore[21]
|
from django.core.urlresolver import reverse
|
||||||
|
|
||||||
|
|
||||||
def send(to,subject,body):
|
def send(to,subject,body):
|
||||||
from_email_address = settings.EMAIL_HOST_USER
|
from_email_address = settings.EMAIL_HOST_USER
|
||||||
if "@" not in from_email_address:
|
if '@' not in from_email_address:
|
||||||
from_email_address = settings.DEFAULT_FROM_EMAIL
|
from_email_address = settings.DEFAULT_FROM_EMAIL
|
||||||
From = "%s <%s>" % (settings.EMAIL_FROM, from_email_address)
|
From = "%s <%s>" % (settings.EMAIL_FROM, from_email_address)
|
||||||
email = EmailMessage(subject,body,From,to)
|
email = EmailMessage(subject,body,From,to)
|
||||||
email.content_subtype = "html"
|
email.content_subtype = "html"
|
||||||
return email.send(False)
|
return email.send(False)
|
||||||
|
|
||||||
|
|
||||||
def get_redirect_url():
|
def get_redirect_url():
|
||||||
return {
|
return {"redirect_html": reverse(getattr(settings, 'MFA_REDIRECT_AFTER_REGISTRATION', 'mfa_home')),
|
||||||
"redirect_html": reverse(
|
"reg_success_msg":getattr(settings,"MFA_SUCCESS_REGISTRATION_MSG")}
|
||||||
getattr(settings, "MFA_REDIRECT_AFTER_REGISTRATION", "mfa_home")
|
|
||||||
),
|
|
||||||
"reg_success_msg": getattr(settings, "MFA_SUCCESS_REGISTRATION_MSG"),
|
|
||||||
}
|
|
||||||
|
|||||||
63
mfa/Email.py
63
mfa/Email.py
@@ -4,27 +4,20 @@ from django.template.context_processors import csrf
|
|||||||
import datetime,random
|
import datetime,random
|
||||||
from random import randint
|
from random import randint
|
||||||
from .models import *
|
from .models import *
|
||||||
|
#from django.template.context import RequestContext
|
||||||
from .views import login
|
from .views import login
|
||||||
from .Common import send
|
from .Common import send
|
||||||
|
|
||||||
|
|
||||||
def sendEmail(request,username,secret):
|
def sendEmail(request,username,secret):
|
||||||
"""Send Email to the user after rendering `mfa_email_token_template`"""
|
"""Send Email to the user after rendering `mfa_email_token_template`"""
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
key = getattr(User, "USERNAME_FIELD", "username")
|
key = getattr(User, 'USERNAME_FIELD', 'username')
|
||||||
kwargs = {key: username}
|
kwargs = {key: username}
|
||||||
user = User.objects.get(**kwargs)
|
user = User.objects.get(**kwargs)
|
||||||
res = render(
|
res=render(request,"mfa_email_token_template.html",{"request":request,"user":user,'otp':secret})
|
||||||
request,
|
|
||||||
"mfa_email_token_template.html",
|
|
||||||
{"request": request, "user": user, "otp": secret},
|
|
||||||
)
|
|
||||||
return send([user.email],"OTP", res.content.decode())
|
return send([user.email],"OTP", res.content.decode())
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def start(request):
|
def start(request):
|
||||||
"""Start adding email as a 2nd factor"""
|
"""Start adding email as a 2nd factor"""
|
||||||
@@ -37,71 +30,43 @@ def start(request):
|
|||||||
uk.enabled=1
|
uk.enabled=1
|
||||||
uk.save()
|
uk.save()
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.core.urlresolvers import reverse # pyre-ignore[21]
|
from django.core.urlresolvers import reverse
|
||||||
except:
|
except:
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
if (
|
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(
|
||||||
getattr(settings, "MFA_ENFORCE_RECOVERY_METHOD", False)
|
key_type="RECOVERY", username=request.user.username).exists():
|
||||||
and not User_Keys.objects.filter(
|
request.session["mfa_reg"] = {"method": "Email",
|
||||||
key_type="RECOVERY", username=request.user.username
|
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("Email", "Email")}
|
||||||
).exists()
|
|
||||||
):
|
|
||||||
request.session["mfa_reg"] = {
|
|
||||||
"method": "Email",
|
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"Email", "Email"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(reverse(getattr(settings,'MFA_REDIRECT_AFTER_REGISTRATION','mfa_home')))
|
||||||
reverse(
|
|
||||||
getattr(settings, "MFA_REDIRECT_AFTER_REGISTRATION", "mfa_home")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
context["invalid"] = True
|
context["invalid"] = True
|
||||||
else:
|
else:
|
||||||
request.session["email_secret"] = str(
|
request.session["email_secret"] = str(randint(0,100000)) #generate a random integer
|
||||||
randint(0, 100000)
|
|
||||||
) # generate a random integer
|
|
||||||
|
|
||||||
if sendEmail(request, request.user.username, request.session["email_secret"]):
|
if sendEmail(request, request.user.username, request.session["email_secret"]):
|
||||||
context["sent"] = True
|
context["sent"] = True
|
||||||
return render(request,"Email/Add.html", context)
|
return render(request,"Email/Add.html", context)
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def auth(request):
|
def auth(request):
|
||||||
"""Authenticating the user by email."""
|
"""Authenticating the user by email."""
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
if request.method=="POST":
|
if request.method=="POST":
|
||||||
if request.session["email_secret"]==request.POST["otp"].strip():
|
if request.session["email_secret"]==request.POST["otp"].strip():
|
||||||
uk = User_Keys.objects.get(
|
uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email")
|
||||||
username=request.session["base_username"], key_type="Email"
|
|
||||||
)
|
|
||||||
mfa = {"verified": True, "method": "Email","id":uk.id}
|
mfa = {"verified": True, "method": "Email","id":uk.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = datetime.datetime.timestamp(
|
mfa["next_check"] = datetime.datetime.timestamp(datetime.datetime.now() + datetime.timedelta(
|
||||||
datetime.datetime.now()
|
seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX)))
|
||||||
+ datetime.timedelta(
|
|
||||||
seconds=random.randint(
|
|
||||||
settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
request.session["mfa"] = mfa
|
request.session["mfa"] = mfa
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
uk.last_used=timezone.now()
|
uk.last_used=timezone.now()
|
||||||
uk.save()
|
uk.save()
|
||||||
return login(request)
|
return login(request)
|
||||||
context["invalid"]=True
|
context["invalid"]=True
|
||||||
else:
|
else:
|
||||||
request.session["email_secret"] = str(randint(0, 100000))
|
request.session["email_secret"] = str(randint(0, 100000))
|
||||||
if sendEmail(
|
if sendEmail(request, request.session["base_username"], request.session["email_secret"]):
|
||||||
request, request.session["base_username"], request.session["email_secret"]
|
|
||||||
):
|
|
||||||
context["sent"] = True
|
context["sent"] = True
|
||||||
return render(request,"Email/Auth.html", context)
|
return render(request,"Email/Auth.html", context)
|
||||||
|
|||||||
181
mfa/FIDO2.py
181
mfa/FIDO2.py
@@ -1,14 +1,15 @@
|
|||||||
|
from fido2.client import Fido2Client
|
||||||
from fido2.server import Fido2Server, PublicKeyCredentialRpEntity
|
from fido2.server import Fido2Server, PublicKeyCredentialRpEntity
|
||||||
from fido2.webauthn import AttestationObject, AuthenticatorData, CollectedClientData
|
from fido2.webauthn import AttestationObject, AuthenticatorData, CollectedClientData
|
||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
# from django.template.context import RequestContext
|
||||||
import simplejson
|
import simplejson
|
||||||
from fido2 import cbor
|
from fido2 import cbor
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from .models import User_Keys
|
from .models import *
|
||||||
from fido2.utils import websafe_decode, websafe_encode
|
from fido2.utils import websafe_decode, websafe_encode
|
||||||
from fido2.webauthn import AttestedCredentialData
|
from fido2.webauthn import AttestedCredentialData
|
||||||
from .views import login, reset_cookie
|
from .views import login, reset_cookie
|
||||||
@@ -17,7 +18,6 @@ from .Common import get_redirect_url
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
"""Starts FIDO2 recheck"""
|
"""Starts FIDO2 recheck"""
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
@@ -28,28 +28,21 @@ def recheck(request):
|
|||||||
|
|
||||||
def getServer():
|
def getServer():
|
||||||
"""Get Server Info from settings and returns a Fido2Server"""
|
"""Get Server Info from settings and returns a Fido2Server"""
|
||||||
rp = PublicKeyCredentialRpEntity(
|
rp = PublicKeyCredentialRpEntity(id=settings.FIDO_SERVER_ID, name=settings.FIDO_SERVER_NAME)
|
||||||
id=settings.FIDO_SERVER_ID, name=settings.FIDO_SERVER_NAME
|
|
||||||
)
|
|
||||||
return Fido2Server(rp)
|
return Fido2Server(rp)
|
||||||
|
|
||||||
|
|
||||||
def begin_registeration(request):
|
def begin_registeration(request):
|
||||||
"""Starts registering a new FIDO Device, called from API"""
|
"""Starts registering a new FIDO Device, called from API"""
|
||||||
server = getServer()
|
server = getServer()
|
||||||
registration_data, state = server.register_begin(
|
registration_data, state = server.register_begin({
|
||||||
{
|
u'id': request.user.username.encode("utf8"),
|
||||||
"id": request.user.username.encode("utf8"),
|
u'name': (request.user.first_name + " " + request.user.last_name),
|
||||||
"name": (request.user.first_name + " " + request.user.last_name),
|
u'displayName': request.user.username,
|
||||||
"displayName": request.user.username,
|
}, getUserCredentials(request.user.username))
|
||||||
},
|
request.session['fido_state'] = state
|
||||||
getUserCredentials(request.user.username),
|
|
||||||
)
|
|
||||||
request.session["fido_state"] = state
|
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
|
||||||
cbor.encode(registration_data), content_type="application/octet-stream"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@@ -57,81 +50,53 @@ def complete_reg(request):
|
|||||||
"""Completes the registeration, called by API"""
|
"""Completes the registeration, called by API"""
|
||||||
try:
|
try:
|
||||||
if not "fido_state" in request.session:
|
if not "fido_state" in request.session:
|
||||||
return JsonResponse(
|
return JsonResponse({'status': 'ERR', "message": "FIDO Status can't be found, please try again"})
|
||||||
{
|
|
||||||
"status": "ERR",
|
|
||||||
"message": "FIDO Status can't be found, please try again",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
data = cbor.decode(request.body)
|
data = cbor.decode(request.body)
|
||||||
|
|
||||||
client_data = CollectedClientData(data["clientDataJSON"])
|
client_data = CollectedClientData(data['clientDataJSON'])
|
||||||
att_obj = AttestationObject((data["attestationObject"]))
|
att_obj = AttestationObject((data['attestationObject']))
|
||||||
server = getServer()
|
server = getServer()
|
||||||
auth_data = server.register_complete(
|
auth_data = server.register_complete(
|
||||||
request.session.pop("fido_state"), client_data, att_obj
|
request.session.pop('fido_state'),
|
||||||
|
client_data,
|
||||||
|
att_obj
|
||||||
)
|
)
|
||||||
encoded = websafe_encode(auth_data.credential_data)
|
encoded = websafe_encode(auth_data.credential_data)
|
||||||
uk = User_Keys()
|
uk = User_Keys()
|
||||||
uk.username = request.user.username
|
uk.username = request.user.username
|
||||||
uk.properties = {
|
uk.properties = {"device": encoded, "type": att_obj.fmt, }
|
||||||
"device": encoded,
|
|
||||||
"type": att_obj.fmt,
|
|
||||||
}
|
|
||||||
uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
|
uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
|
||||||
uk.key_type = "FIDO2"
|
uk.key_type = "FIDO2"
|
||||||
uk.save()
|
uk.save()
|
||||||
if (
|
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type = "RECOVERY", username=request.user.username).exists():
|
||||||
getattr(settings, "MFA_ENFORCE_RECOVERY_METHOD", False)
|
request.session["mfa_reg"] = {"method":"FIDO2","name": getattr(settings, "MFA_RENAME_METHODS", {}).get("FIDO2", "FIDO2")}
|
||||||
and not User_Keys.objects.filter(
|
return HttpResponse(simplejson.dumps({'status': 'RECOVERY'}))
|
||||||
key_type="RECOVERY", username=request.user.username
|
|
||||||
).exists()
|
|
||||||
):
|
|
||||||
request.session["mfa_reg"] = {
|
|
||||||
"method": "FIDO2",
|
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"FIDO2", "FIDO2"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
return HttpResponse(simplejson.dumps({"status": "RECOVERY"}))
|
|
||||||
else:
|
else:
|
||||||
return HttpResponse(simplejson.dumps({"status": "OK"}))
|
return HttpResponse(simplejson.dumps({'status': 'OK'}))
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
try:
|
try:
|
||||||
from raven.contrib.django.raven_compat.models import client
|
from raven.contrib.django.raven_compat.models import client
|
||||||
|
|
||||||
client.captureException()
|
client.captureException()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return JsonResponse(
|
return JsonResponse({'status': 'ERR', "message": "Error on server, please try again later"})
|
||||||
{"status": "ERR", "message": "Error on server, please try again later"}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def start(request):
|
def start(request):
|
||||||
"""Start Registration a new FIDO Token"""
|
"""Start Registration a new FIDO Token"""
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
context.update(get_redirect_url())
|
context.update(get_redirect_url())
|
||||||
context["method"] = {
|
context["method"] = {"name":getattr(settings,"MFA_RENAME_METHODS",{}).get("FIDO2","FIDO2 Security Key")}
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
context["RECOVERY_METHOD"]=getattr(settings,"MFA_RENAME_METHODS",{}).get("RECOVERY","Recovery codes")
|
||||||
"FIDO2", "FIDO2 Security Key"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"RECOVERY", "Recovery codes"
|
|
||||||
)
|
|
||||||
return render(request, "FIDO2/Add.html", context)
|
return render(request, "FIDO2/Add.html", context)
|
||||||
|
|
||||||
|
|
||||||
def getUserCredentials(username):
|
def getUserCredentials(username):
|
||||||
credentials = []
|
credentials = []
|
||||||
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
|
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
|
||||||
credentials.append(
|
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
|
||||||
AttestedCredentialData(websafe_decode(uk.properties["device"]))
|
|
||||||
)
|
|
||||||
return credentials
|
return credentials
|
||||||
|
|
||||||
|
|
||||||
@@ -142,11 +107,9 @@ def auth(request):
|
|||||||
|
|
||||||
def authenticate_begin(request):
|
def authenticate_begin(request):
|
||||||
server = getServer()
|
server = getServer()
|
||||||
credentials = getUserCredentials(
|
credentials = getUserCredentials(request.session.get("base_username", request.user.username))
|
||||||
request.session.get("base_username", request.user.username)
|
|
||||||
)
|
|
||||||
auth_data, state = server.authenticate_begin(credentials)
|
auth_data, state = server.authenticate_begin(credentials)
|
||||||
request.session["fido_state"] = state
|
request.session['fido_state'] = state
|
||||||
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
|
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
|
||||||
|
|
||||||
|
|
||||||
@@ -158,76 +121,49 @@ def authenticate_complete(request):
|
|||||||
server = getServer()
|
server = getServer()
|
||||||
credentials = getUserCredentials(username)
|
credentials = getUserCredentials(username)
|
||||||
data = cbor.decode(request.body)
|
data = cbor.decode(request.body)
|
||||||
credential_id = data["credentialId"]
|
credential_id = data['credentialId']
|
||||||
client_data = CollectedClientData(data["clientDataJSON"])
|
client_data = CollectedClientData(data['clientDataJSON'])
|
||||||
auth_data = AuthenticatorData(data["authenticatorData"])
|
auth_data = AuthenticatorData(data['authenticatorData'])
|
||||||
signature = data["signature"]
|
signature = data['signature']
|
||||||
try:
|
try:
|
||||||
cred = server.authenticate_complete(
|
cred = server.authenticate_complete(
|
||||||
request.session.pop("fido_state"),
|
request.session.pop('fido_state'),
|
||||||
credentials,
|
credentials,
|
||||||
credential_id,
|
credential_id,
|
||||||
client_data,
|
client_data,
|
||||||
auth_data,
|
auth_data,
|
||||||
signature,
|
signature
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({'status': "ERR",
|
||||||
simplejson.dumps(
|
"message": "Wrong challenge received, make sure that this is your security and try again."}),
|
||||||
{
|
content_type = "application/json")
|
||||||
"status": "ERR",
|
|
||||||
"message": "Wrong challenge received, make sure that this is your security and try again.",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
except Exception as excep:
|
except Exception as excep:
|
||||||
try:
|
try:
|
||||||
from raven.contrib.django.raven_compat.models import client
|
from raven.contrib.django.raven_compat.models import client
|
||||||
|
|
||||||
client.captureException()
|
client.captureException()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({'status': "ERR",
|
||||||
simplejson.dumps({"status": "ERR", "message": str(excep)}),
|
"message": str(excep)}),
|
||||||
content_type="application/json",
|
content_type = "application/json")
|
||||||
)
|
|
||||||
|
|
||||||
if request.session.get("mfa_recheck", False):
|
if request.session.get("mfa_recheck", False):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
request.session["mfa"]["rechecked_at"] = time.time()
|
request.session["mfa"]["rechecked_at"] = time.time()
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({'status': "OK"}),
|
||||||
simplejson.dumps({"status": "OK"}), content_type="application/json"
|
content_type = "application/json")
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
import random
|
import random
|
||||||
|
keys = User_Keys.objects.filter(username = username, key_type = "FIDO2", enabled = 1)
|
||||||
keys = User_Keys.objects.filter(
|
|
||||||
username=username, key_type="FIDO2", enabled=1
|
|
||||||
)
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
if (
|
if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id:
|
||||||
AttestedCredentialData(
|
|
||||||
websafe_decode(k.properties["device"])
|
|
||||||
).credential_id
|
|
||||||
== cred.credential_id
|
|
||||||
):
|
|
||||||
k.last_used = timezone.now()
|
k.last_used = timezone.now()
|
||||||
k.save()
|
k.save()
|
||||||
mfa = {"verified": True, "method": "FIDO2", "id": k.id}
|
mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = datetime.datetime.timestamp(
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now() + datetime.timedelta(
|
||||||
(
|
seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||||
datetime.datetime.now()
|
|
||||||
+ datetime.timedelta(
|
|
||||||
seconds=random.randint(
|
|
||||||
settings.MFA_RECHECK_MIN,
|
|
||||||
settings.MFA_RECHECK_MAX,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
request.session["mfa"] = mfa
|
request.session["mfa"] = mfa
|
||||||
try:
|
try:
|
||||||
authenticated = request.user.is_authenticated
|
authenticated = request.user.is_authenticated
|
||||||
@@ -235,20 +171,11 @@ def authenticate_complete(request):
|
|||||||
authenticated = request.user.is_authenticated()
|
authenticated = request.user.is_authenticated()
|
||||||
if not authenticated:
|
if not authenticated:
|
||||||
res = login(request)
|
res = login(request)
|
||||||
if not "location" in res:
|
if not "location" in res: return reset_cookie(request)
|
||||||
return reset_cookie(request)
|
return HttpResponse(simplejson.dumps({'status': "OK", "redirect": res["location"]}),
|
||||||
return HttpResponse(
|
content_type = "application/json")
|
||||||
simplejson.dumps(
|
return HttpResponse(simplejson.dumps({'status': "OK"}),
|
||||||
{"status": "OK", "redirect": res["location"]}
|
content_type = "application/json")
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
return HttpResponse(
|
|
||||||
simplejson.dumps({"status": "OK"}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
except Exception as exp:
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({'status': "ERR", "message": exp.message}),
|
||||||
simplejson.dumps({"status": "ERR", "message": exp.message}),
|
content_type = "application/json")
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,31 +2,27 @@ import string
|
|||||||
import random
|
import random
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.template.context import RequestContext
|
||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
|
from .models import *
|
||||||
import user_agents
|
import user_agents
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .models import User_Keys
|
|
||||||
|
|
||||||
|
|
||||||
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
||||||
x = "".join(random.choice(chars) for _ in range(size))
|
x=''.join(random.choice(chars) for _ in range(size))
|
||||||
if not User_Keys.objects.filter(properties__icontains='"key": "%s"' % x).exists():
|
if not User_Keys.objects.filter(properties__icontains='"key": "%s"'%x).exists(): return x
|
||||||
return x
|
else: return id_generator(size,chars)
|
||||||
return id_generator(size, chars)
|
|
||||||
|
|
||||||
|
|
||||||
def getUserAgent(request):
|
def getUserAgent(request):
|
||||||
device_id = request.session.get("td_id", None)
|
id=id=request.session.get("td_id",None)
|
||||||
if device_id:
|
if id:
|
||||||
tk = User_Keys.objects.get(id=device_id)
|
tk=User_Keys.objects.get(id=id)
|
||||||
if tk.properties.get("user_agent","")!="":
|
if tk.properties.get("user_agent","")!="":
|
||||||
ua = user_agents.parse(tk.properties["user_agent"])
|
ua = user_agents.parse(tk.properties["user_agent"])
|
||||||
res = render(None, "TrustedDevices/user-agent.html", context={"ua":ua})
|
res = render(None, "TrustedDevices/user-agent.html", context={"ua":ua})
|
||||||
return HttpResponse(res)
|
return HttpResponse(res)
|
||||||
return HttpResponse("No Device provide", status=401)
|
return HttpResponse("")
|
||||||
|
|
||||||
|
|
||||||
def trust_device(request):
|
def trust_device(request):
|
||||||
tk = User_Keys.objects.get(id=request.session["td_id"])
|
tk = User_Keys.objects.get(id=request.session["td_id"])
|
||||||
@@ -35,20 +31,17 @@ def trust_device(request):
|
|||||||
del request.session["td_id"]
|
del request.session["td_id"]
|
||||||
return HttpResponse("OK")
|
return HttpResponse("OK")
|
||||||
|
|
||||||
|
|
||||||
def checkTrusted(request):
|
def checkTrusted(request):
|
||||||
res = ""
|
res = ""
|
||||||
id=request.session.get("td_id","")
|
id=request.session.get("td_id","")
|
||||||
if id!="":
|
if id!="":
|
||||||
try:
|
try:
|
||||||
tk = User_Keys.objects.get(id=id)
|
tk = User_Keys.objects.get(id=id)
|
||||||
if tk.properties["status"] == "trusted":
|
if tk.properties["status"] == "trusted": res = "OK"
|
||||||
res = "OK"
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return HttpResponse(res)
|
return HttpResponse(res)
|
||||||
|
|
||||||
|
|
||||||
def getCookie(request):
|
def getCookie(request):
|
||||||
tk = User_Keys.objects.get(id=request.session["td_id"])
|
tk = User_Keys.objects.get(id=request.session["td_id"])
|
||||||
|
|
||||||
@@ -56,33 +49,27 @@ def getCookie(request):
|
|||||||
context={"added":True}
|
context={"added":True}
|
||||||
response = render(request,"TrustedDevices/Done.html", context)
|
response = render(request,"TrustedDevices/Done.html", context)
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
expires = datetime.now() + timedelta(days=180)
|
expires = datetime.now() + timedelta(days=180)
|
||||||
tk.expires=expires
|
tk.expires=expires
|
||||||
tk.save()
|
tk.save()
|
||||||
response.set_cookie("deviceid", tk.properties["signature"], expires=expires)
|
response.set_cookie("deviceid", tk.properties["signature"], expires=expires)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def add(request):
|
def add(request):
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
if request.method=="GET":
|
if request.method=="GET":
|
||||||
context.update(
|
context.update({"username":request.GET.get('u',''),"key":request.GET.get('k','')})
|
||||||
{"username": request.GET.get("u", ""), "key": request.GET.get("k", "")}
|
|
||||||
)
|
|
||||||
return render(request,"TrustedDevices/Add.html",context)
|
return render(request,"TrustedDevices/Add.html",context)
|
||||||
else:
|
else:
|
||||||
key=request.POST["key"].replace("-","").replace(" ","").upper()
|
key=request.POST["key"].replace("-","").replace(" ","").upper()
|
||||||
context["username"] = request.POST["username"]
|
context["username"] = request.POST["username"]
|
||||||
context["key"] = request.POST["key"]
|
context["key"] = request.POST["key"]
|
||||||
trusted_keys = User_Keys.objects.filter(
|
trusted_keys=User_Keys.objects.filter(username=request.POST["username"],properties__icontains='"key": "%s"'%key)
|
||||||
username=request.POST["username"], properties__icontains='"key": "%s"' % key
|
|
||||||
)
|
|
||||||
cookie=False
|
cookie=False
|
||||||
if trusted_keys.exists():
|
if trusted_keys.exists():
|
||||||
tk=trusted_keys[0]
|
tk=trusted_keys[0]
|
||||||
request.session["td_id"]=tk.id
|
request.session["td_id"]=tk.id
|
||||||
ua = request.META["HTTP_USER_AGENT"]
|
ua=request.META['HTTP_USER_AGENT']
|
||||||
agent=user_agents.parse(ua)
|
agent=user_agents.parse(ua)
|
||||||
if agent.is_pc:
|
if agent.is_pc:
|
||||||
context["invalid"]="This is a PC, it can't used as a trusted device."
|
context["invalid"]="This is a PC, it can't used as a trusted device."
|
||||||
@@ -95,20 +82,12 @@ def add(request):
|
|||||||
# context["success"]=True
|
# context["success"]=True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
context[
|
context["invalid"]="The username or key is wrong, please check and try again."
|
||||||
"invalid"
|
|
||||||
] = "The username or key is wrong, please check and try again."
|
|
||||||
|
|
||||||
return render(request,"TrustedDevices/Add.html", context)
|
return render(request,"TrustedDevices/Add.html", context)
|
||||||
|
|
||||||
|
|
||||||
def start(request):
|
def start(request):
|
||||||
if (
|
if User_Keys.objects.filter(username=request.user.username,key_type="Trusted Device").count()>= 2:
|
||||||
User_Keys.objects.filter(
|
|
||||||
username=request.user.username, key_type="Trusted Device"
|
|
||||||
).count()
|
|
||||||
>= 2
|
|
||||||
):
|
|
||||||
return render(request,"TrustedDevices/start.html",{"not_allowed":True})
|
return render(request,"TrustedDevices/start.html",{"not_allowed":True})
|
||||||
td=None
|
td=None
|
||||||
if not request.session.get("td_id",None):
|
if not request.session.get("td_id",None):
|
||||||
@@ -119,25 +98,16 @@ def start(request):
|
|||||||
td.save()
|
td.save()
|
||||||
request.session["td_id"]=td.id
|
request.session["td_id"]=td.id
|
||||||
try:
|
try:
|
||||||
if td == None:
|
if td==None: td=User_Keys.objects.get(id=request.session["td_id"])
|
||||||
td = User_Keys.objects.get(id=request.session["td_id"])
|
context={"key":td.properties["key"],"url":request.scheme+"://"+request.get_host() + reverse('add_trusted_device')}
|
||||||
context = {
|
|
||||||
"key": td.properties["key"],
|
|
||||||
"url": request.scheme
|
|
||||||
+ "://"
|
|
||||||
+ request.get_host()
|
|
||||||
+ reverse("add_trusted_device"),
|
|
||||||
}
|
|
||||||
except:
|
except:
|
||||||
del request.session["td_id"]
|
del request.session["td_id"]
|
||||||
return start(request)
|
return start(request)
|
||||||
return render(request,"TrustedDevices/start.html",context)
|
return render(request,"TrustedDevices/start.html",context)
|
||||||
|
|
||||||
|
|
||||||
def send_email(request):
|
def send_email(request):
|
||||||
body=render(request,"TrustedDevices/email.html",{}).content
|
body=render(request,"TrustedDevices/email.html",{}).content
|
||||||
from .Common import send
|
from .Common import send
|
||||||
|
|
||||||
e=request.user.email
|
e=request.user.email
|
||||||
if e=="":
|
if e=="":
|
||||||
e=request.session.get("user",{}).get("email","")
|
e=request.session.get("user",{}).get("email","")
|
||||||
@@ -151,28 +121,19 @@ def send_email(request):
|
|||||||
|
|
||||||
|
|
||||||
def verify(request):
|
def verify(request):
|
||||||
if request.COOKIES.get("deviceid", None):
|
if request.COOKIES.get('deviceid',None):
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
|
json= jwt.decode(request.COOKIES.get('deviceid'),settings.SECRET_KEY)
|
||||||
json = jwt.decode(request.COOKIES.get("deviceid"), settings.SECRET_KEY)
|
if json["username"].lower()== request.session['base_username'].lower():
|
||||||
if json["username"].lower() == request.session["base_username"].lower():
|
|
||||||
try:
|
try:
|
||||||
uk = User_Keys.objects.get(
|
uk = User_Keys.objects.get(username=request.POST["username"].lower(), properties__icontains='"key": "%s"'%json["key"])
|
||||||
username=request.POST["username"].lower(),
|
|
||||||
properties__icontains='"key": "%s"' % json["key"],
|
|
||||||
)
|
|
||||||
if uk.enabled and uk.properties["status"] == "trusted":
|
if uk.enabled and uk.properties["status"] == "trusted":
|
||||||
uk.last_used=timezone.now()
|
uk.last_used=timezone.now()
|
||||||
uk.save()
|
uk.save()
|
||||||
request.session["mfa"] = {
|
request.session["mfa"] = {"verified": True, "method": "Trusted Device","id":uk.id}
|
||||||
"verified": True,
|
|
||||||
"method": "Trusted Device",
|
|
||||||
"id": uk.id,
|
|
||||||
}
|
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|||||||
102
mfa/U2F.py
102
mfa/U2F.py
@@ -1,24 +1,21 @@
|
|||||||
from u2flib_server.u2f import (
|
|
||||||
begin_registration,
|
from u2flib_server.u2f import (begin_registration, begin_authentication,
|
||||||
begin_authentication,
|
complete_registration, complete_authentication)
|
||||||
complete_registration,
|
|
||||||
complete_authentication,
|
|
||||||
)
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding
|
from cryptography.hazmat.primitives.serialization import Encoding
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
import simplejson
|
import simplejson
|
||||||
|
#from django.template.context import RequestContext
|
||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from .models import User_Keys
|
from django.http import HttpResponse
|
||||||
|
from .models import *
|
||||||
from .views import login
|
from .views import login
|
||||||
from .Common import get_redirect_url
|
from .Common import get_redirect_url
|
||||||
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
context["mode"]="recheck"
|
context["mode"]="recheck"
|
||||||
@@ -28,30 +25,22 @@ def recheck(request):
|
|||||||
request.session["mfa_recheck"]=True
|
request.session["mfa_recheck"]=True
|
||||||
return render(request,"U2F/recheck.html", context)
|
return render(request,"U2F/recheck.html", context)
|
||||||
|
|
||||||
|
|
||||||
def process_recheck(request):
|
def process_recheck(request):
|
||||||
x=validate(request,request.user.username)
|
x=validate(request,request.user.username)
|
||||||
if x==True:
|
if x==True:
|
||||||
import time
|
import time
|
||||||
|
|
||||||
request.session["mfa"]["rechecked_at"] = time.time()
|
request.session["mfa"]["rechecked_at"] = time.time()
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"recheck":True}),content_type="application/json")
|
||||||
simplejson.dumps({"recheck": True}), content_type="application/json"
|
|
||||||
)
|
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
def check_errors(request, data):
|
def check_errors(request, data):
|
||||||
if "errorCode" in data:
|
if "errorCode" in data:
|
||||||
if data["errorCode"] == 0:
|
if data["errorCode"] == 0: return True
|
||||||
return True
|
|
||||||
if data["errorCode"] == 4:
|
if data["errorCode"] == 4:
|
||||||
return HttpResponse("Invalid Security Key")
|
return HttpResponse("Invalid Security Key")
|
||||||
if data["errorCode"] == 1:
|
if data["errorCode"] == 1:
|
||||||
return auth(request)
|
return auth(request)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def validate(request,username):
|
def validate(request,username):
|
||||||
import datetime, random
|
import datetime, random
|
||||||
|
|
||||||
@@ -61,76 +50,53 @@ def validate(request, username):
|
|||||||
if res!=True:
|
if res!=True:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
challenge = request.session.pop("_u2f_challenge_")
|
challenge = request.session.pop('_u2f_challenge_')
|
||||||
device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])
|
device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])
|
||||||
try:
|
try:
|
||||||
key = User_Keys.objects.get(
|
key=User_Keys.objects.get(username=username,properties__icontains='"publicKey": "%s"'%device["publicKey"])
|
||||||
username=username,
|
|
||||||
properties__icontains='"publicKey": "%s"' % device["publicKey"],
|
|
||||||
)
|
|
||||||
key.last_used=timezone.now()
|
key.last_used=timezone.now()
|
||||||
key.save()
|
key.save()
|
||||||
mfa = {"verified": True, "method": "U2F","id":key.id}
|
mfa = {"verified": True, "method": "U2F","id":key.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = datetime.datetime.timestamp(
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
(
|
|
||||||
datetime.datetime.now()
|
|
||||||
+ datetime.timedelta(
|
+ datetime.timedelta(
|
||||||
seconds=random.randint(
|
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||||
settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
request.session["mfa"] = mfa
|
request.session["mfa"] = mfa
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def auth(request):
|
def auth(request):
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
s=sign(request.session["base_username"])
|
s=sign(request.session["base_username"])
|
||||||
request.session["_u2f_challenge_"]=s[0]
|
request.session["_u2f_challenge_"]=s[0]
|
||||||
context["token"]=s[1]
|
context["token"]=s[1]
|
||||||
context["method"] = {
|
context["method"] = {"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"U2F", "Classical Security Key"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return render(request,"U2F/Auth.html",context)
|
return render(request,"U2F/Auth.html",context)
|
||||||
|
|
||||||
|
|
||||||
def start(request):
|
def start(request):
|
||||||
enroll = begin_registration(settings.U2F_APPID, [])
|
enroll = begin_registration(settings.U2F_APPID, [])
|
||||||
request.session["_u2f_enroll_"] = enroll.json
|
request.session['_u2f_enroll_'] = enroll.json
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
context["token"]=simplejson.dumps(enroll.data_for_client)
|
context["token"]=simplejson.dumps(enroll.data_for_client)
|
||||||
context.update(get_redirect_url())
|
context.update(get_redirect_url())
|
||||||
context["method"] = {
|
context["method"] = {"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get("RECOVERY", "Recovery codes")
|
||||||
"U2F", "Classical Security Key"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"RECOVERY", "Recovery codes"
|
|
||||||
)
|
|
||||||
return render(request,"U2F/Add.html",context)
|
return render(request,"U2F/Add.html",context)
|
||||||
|
|
||||||
|
|
||||||
def bind(request):
|
def bind(request):
|
||||||
import hashlib
|
import hashlib
|
||||||
|
enroll = request.session['_u2f_enroll_']
|
||||||
enroll = request.session["_u2f_enroll_"]
|
|
||||||
data=simplejson.loads(request.POST["response"])
|
data=simplejson.loads(request.POST["response"])
|
||||||
device, cert = complete_registration(enroll, data, [settings.U2F_APPID])
|
device, cert = complete_registration(enroll, data, [settings.U2F_APPID])
|
||||||
cert = x509.load_der_x509_certificate(cert, default_backend())
|
cert = x509.load_der_x509_certificate(cert, default_backend())
|
||||||
cert_hash=hashlib.md5(cert.public_bytes(Encoding.PEM)).hexdigest()
|
cert_hash=hashlib.md5(cert.public_bytes(Encoding.PEM)).hexdigest()
|
||||||
q=User_Keys.objects.filter(key_type="U2F", properties__icontains= cert_hash)
|
q=User_Keys.objects.filter(key_type="U2F", properties__icontains= cert_hash)
|
||||||
if q.exists():
|
if q.exists():
|
||||||
return HttpResponse(
|
return HttpResponse("This key is registered before, it can't be registered again.")
|
||||||
"This key is registered before, it can't be registered again."
|
|
||||||
)
|
|
||||||
User_Keys.objects.filter(username=request.user.username,key_type="U2F").delete()
|
User_Keys.objects.filter(username=request.user.username,key_type="U2F").delete()
|
||||||
uk = User_Keys()
|
uk = User_Keys()
|
||||||
uk.username = request.user.username
|
uk.username = request.user.username
|
||||||
@@ -138,34 +104,20 @@ def bind(request):
|
|||||||
uk.properties = {"device":simplejson.loads(device.json),"cert":cert_hash}
|
uk.properties = {"device":simplejson.loads(device.json),"cert":cert_hash}
|
||||||
uk.key_type = "U2F"
|
uk.key_type = "U2F"
|
||||||
uk.save()
|
uk.save()
|
||||||
if (
|
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type="RECOVERY",
|
||||||
getattr(settings, "MFA_ENFORCE_RECOVERY_METHOD", False)
|
username=request.user.username).exists():
|
||||||
and not User_Keys.objects.filter(
|
request.session["mfa_reg"] = {"method": "U2F",
|
||||||
key_type="RECOVERY", username=request.user.username
|
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||||
).exists()
|
return HttpResponse('RECOVERY')
|
||||||
):
|
|
||||||
request.session["mfa_reg"] = {
|
|
||||||
"method": "U2F",
|
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
|
||||||
"U2F", "Classical Security Key"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
return HttpResponse("RECOVERY")
|
|
||||||
return HttpResponse("OK")
|
return HttpResponse("OK")
|
||||||
|
|
||||||
|
|
||||||
def sign(username):
|
def sign(username):
|
||||||
u2f_devices = [
|
u2f_devices=[d.properties["device"] for d in User_Keys.objects.filter(username=username,key_type="U2F")]
|
||||||
d.properties["device"]
|
|
||||||
for d in User_Keys.objects.filter(username=username, key_type="U2F")
|
|
||||||
]
|
|
||||||
challenge = begin_authentication(settings.U2F_APPID, u2f_devices)
|
challenge = begin_authentication(settings.U2F_APPID, u2f_devices)
|
||||||
return [challenge.json,simplejson.dumps(challenge.data_for_client)]
|
return [challenge.json,simplejson.dumps(challenge.data_for_client)]
|
||||||
|
|
||||||
|
|
||||||
def verify(request):
|
def verify(request):
|
||||||
x= validate(request,request.session["base_username"])
|
x= validate(request,request.session["base_username"])
|
||||||
if x==True:
|
if x==True:
|
||||||
return login(request)
|
return login(request)
|
||||||
else:
|
else: return x
|
||||||
return x
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class myAppNameConfig(AppConfig):
|
class myAppNameConfig(AppConfig):
|
||||||
name = "mfa"
|
name = 'mfa'
|
||||||
verbose_name = "A Much Better Name"
|
verbose_name = 'A Much Better Name'
|
||||||
@@ -1,46 +1,38 @@
|
|||||||
from .models import User_Keys
|
import pyotp
|
||||||
|
from .models import *
|
||||||
from . import TrustedDevice, U2F, FIDO2, totp
|
from . import TrustedDevice, U2F, FIDO2, totp
|
||||||
import simplejson
|
import simplejson
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
from mfa.views import verify
|
from mfa.views import verify,goto
|
||||||
|
from mfa.recovery import delTokens
|
||||||
|
|
||||||
def has_mfa(request,username):
|
def has_mfa(request,username):
|
||||||
if User_Keys.objects.filter(username=username, enabled=1).count() > 0:
|
uk = User_Keys.objects.filter(username=username,enabled=1)
|
||||||
|
if uk.count()==1:
|
||||||
|
for u in uk:
|
||||||
|
if u.key_type == "RECOVERY":
|
||||||
|
delTokens(request)
|
||||||
|
elif uk.count()>0:
|
||||||
return verify(request, username)
|
return verify(request, username)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_mfa(request,ignore_methods=[]):
|
def is_mfa(request,ignore_methods=[]):
|
||||||
if request.session.get("mfa",{}).get("verified",False):
|
if request.session.get("mfa",{}).get("verified",False):
|
||||||
if not request.session.get("mfa",{}).get("method",None) in ignore_methods:
|
if not request.session.get("mfa",{}).get("method",None) in ignore_methods:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
method=request.session.get("mfa",{}).get("method",None)
|
method=request.session.get("mfa",{}).get("method",None)
|
||||||
if not method:
|
if not method:
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"res":False}),content_type="application/json")
|
||||||
simplejson.dumps({"res": False}), content_type="application/json"
|
|
||||||
)
|
|
||||||
if method=="Trusted Device":
|
if method=="Trusted Device":
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"res":TrustedDevice.verify(request)}),content_type="application/json")
|
||||||
simplejson.dumps({"res": TrustedDevice.verify(request)}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
elif method=="U2F":
|
elif method=="U2F":
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"html": U2F.recheck(request).content}), content_type="application/json")
|
||||||
simplejson.dumps({"html": U2F.recheck(request).content}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
elif method == "FIDO2":
|
elif method == "FIDO2":
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"html": FIDO2.recheck(request).content}), content_type="application/json")
|
||||||
simplejson.dumps({"html": FIDO2.recheck(request).content}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
elif method=="TOTP":
|
elif method=="TOTP":
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"html": totp.recheck(request).content}), content_type="application/json")
|
||||||
simplejson.dumps({"html": totp.recheck(request).content}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import time
|
import time
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
try:
|
|
||||||
from django.urls import reverse
|
|
||||||
except ImportError:
|
|
||||||
from django.core.urlresolvers import reverse # pyre-ignore[21]
|
|
||||||
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
def process(request):
|
def process(request):
|
||||||
next_check = request.session.get("mfa", {}).get("next_check", False)
|
next_check=request.session.get('mfa',{}).get("next_check",False)
|
||||||
if not next_check:
|
if not next_check: return None
|
||||||
return None
|
|
||||||
now=int(time.time())
|
now=int(time.time())
|
||||||
if now >= next_check:
|
if now >= next_check:
|
||||||
method=request.session["mfa"]["method"]
|
method=request.session["mfa"]["method"]
|
||||||
path = request.META["PATH_INFO"]
|
path = request.META["PATH_INFO"]
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(reverse(method+"_auth")+"?next=%s"%(settings.BASE_URL + path).replace("//", "/"))
|
||||||
reverse(method + "_auth")
|
|
||||||
+ "?next=%s" % (settings.BASE_URL + path).replace("//", "/")
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
@@ -5,24 +5,18 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = []
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="User_Keys",
|
name='User_Keys',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
"id",
|
('username', models.CharField(max_length=50)),
|
||||||
models.AutoField(
|
('secret_key', models.CharField(max_length=15)),
|
||||||
verbose_name="ID",
|
('added_on', models.DateTimeField(auto_now_add=True)),
|
||||||
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)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0001_initial"),
|
('mfa', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="key_type",
|
name='key_type',
|
||||||
field=models.CharField(default=b"TOTP", max_length=25),
|
field=models.CharField(default=b'TOTP', max_length=25),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0002_user_keys_key_type"),
|
('mfa', '0002_user_keys_key_type'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="secret_key",
|
name='secret_key',
|
||||||
field=models.CharField(max_length=32),
|
field=models.CharField(max_length=32),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0003_auto_20181114_2159"),
|
('mfa', '0003_auto_20181114_2159'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="enabled",
|
name='enabled',
|
||||||
field=models.BooleanField(default=True),
|
field=models.BooleanField(default=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,39 +2,36 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
from jsonfield.fields import JSONField # pyre-ignore[21]
|
from jsonfield.fields import JSONField
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError("Can't find a JSONField implementation, please install jsonfield if django < 4.0")
|
||||||
"Can't find a JSONField implementation, please install jsonfield if django < 4.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def modify_json(apps, schema_editor):
|
def modify_json(apps, schema_editor):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
if "mysql" in settings.DATABASES.get("default", {}).get("engine", ""):
|
if "mysql" in settings.DATABASES.get("default", {}).get("engine", ""):
|
||||||
migrations.RunSQL("alter table mfa_user_keys modify column properties json;")
|
migrations.RunSQL("alter table mfa_user_keys modify column properties json;")
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0004_user_keys_enabled"),
|
('mfa', '0004_user_keys_enabled'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="secret_key",
|
name='secret_key',
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="properties",
|
name='properties',
|
||||||
field=JSONField(null=True),
|
field=JSONField(null=True),
|
||||||
),
|
),
|
||||||
migrations.RunPython(modify_json),
|
migrations.RunPython(modify_json)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,30 +5,23 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0005_auto_20181115_2014"),
|
('mfa', '0005_auto_20181115_2014'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Trusted_Devices",
|
name='Trusted_Devices',
|
||||||
fields=[
|
fields=[
|
||||||
(
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
"id",
|
('signature', models.CharField(max_length=255)),
|
||||||
models.AutoField(
|
('key', models.CharField(max_length=6)),
|
||||||
verbose_name="ID",
|
('username', models.CharField(max_length=50)),
|
||||||
serialize=False,
|
('user_agent', models.CharField(max_length=255)),
|
||||||
auto_created=True,
|
('status', models.CharField(default=b'adding', max_length=255)),
|
||||||
primary_key=True,
|
('added_on', models.DateTimeField(auto_now_add=True)),
|
||||||
),
|
('last_used', models.DateTimeField(default=None, null=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)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0006_trusted_devices"),
|
('mfa', '0006_trusted_devices'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name="Trusted_Devices",
|
name='Trusted_Devices',
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="expires",
|
name='expires',
|
||||||
field=models.DateTimeField(default=None, null=True, blank=True),
|
field=models.DateTimeField(default=None, null=True, blank=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from django.db import models, migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0007_auto_20181230_1549"),
|
('mfa', '0007_auto_20181230_1549'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="last_used",
|
name='last_used',
|
||||||
field=models.DateTimeField(default=None, null=True, blank=True),
|
field=models.DateTimeField(default=None, null=True, blank=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,22 +6,21 @@ from django.conf import settings
|
|||||||
|
|
||||||
|
|
||||||
def update_owned_by_enterprise(apps, schema_editor):
|
def update_owned_by_enterprise(apps, schema_editor):
|
||||||
user_keys = apps.get_model("mfa", "user_keys")
|
user_keys = apps.get_model('mfa', 'user_keys')
|
||||||
user_keys.objects.filter(key_type="FIDO2").update(
|
user_keys.objects.filter(key_type='FIDO2').update(owned_by_enterprise=getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False))
|
||||||
owned_by_enterprise=getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0008_user_keys_last_used"),
|
('mfa', '0008_user_keys_last_used'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="owned_by_enterprise",
|
name='owned_by_enterprise',
|
||||||
field=models.NullBooleanField(default=None),
|
field=models.NullBooleanField(default=None),
|
||||||
),
|
),
|
||||||
migrations.RunPython(update_owned_by_enterprise),
|
migrations.RunPython(update_owned_by_enterprise)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0009_user_keys_owned_by_enterprise"),
|
('mfa', '0009_user_keys_owned_by_enterprise'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="key_type",
|
name='key_type',
|
||||||
field=models.CharField(default="TOTP", max_length=25),
|
field=models.CharField(default='TOTP', max_length=25),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("mfa", "0010_auto_20201110_0557"),
|
('mfa', '0010_auto_20201110_0557'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="user_keys",
|
model_name='user_keys',
|
||||||
name="owned_by_enterprise",
|
name='owned_by_enterprise',
|
||||||
field=models.BooleanField(blank=True, default=None, null=True),
|
field=models.BooleanField(blank=True, default=None, null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
try:
|
try:
|
||||||
from jsonfield import JSONField # pyre-ignore[21]
|
from jsonfield import JSONField
|
||||||
except ModuleNotFoundError as exc:
|
except ModuleNotFoundError:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError("Can't find a JSONField implementation, please install jsonfield if django < 4.0")
|
||||||
"Can't find a JSONField implementation, please install jsonfield if django < 4.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
#from jsonLookup import shasLookup, hasLookup
|
||||||
|
# JSONField.register_lookup(shasLookup)
|
||||||
|
# JSONField.register_lookup(hasLookup)
|
||||||
|
|
||||||
|
|
||||||
class User_Keys(models.Model):
|
class User_Keys(models.Model):
|
||||||
@@ -24,23 +24,10 @@ class User_Keys(models.Model):
|
|||||||
last_used=models.DateTimeField(null=True,default=None,blank=True)
|
last_used=models.DateTimeField(null=True,default=None,blank=True)
|
||||||
owned_by_enterprise=models.BooleanField(default=None,null=True,blank=True)
|
owned_by_enterprise=models.BooleanField(default=None,null=True,blank=True)
|
||||||
|
|
||||||
def save(
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
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)
|
||||||
if (
|
super(User_Keys, self).save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||||
self.key_type == "Trusted Device"
|
|
||||||
and self.properties.get("signature", "") == ""
|
|
||||||
):
|
|
||||||
self.properties["signature"] = jwt.encode(
|
|
||||||
{"username": self.username, "key": self.properties["key"]},
|
|
||||||
settings.SECRET_KEY,
|
|
||||||
)
|
|
||||||
super(User_Keys, self).save(
|
|
||||||
force_insert=force_insert,
|
|
||||||
force_update=force_update,
|
|
||||||
using=using,
|
|
||||||
update_fields=update_fields,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s -- %s"%(self.username,self.key_type)
|
return "%s -- %s"%(self.username,self.key_type)
|
||||||
@@ -49,4 +36,4 @@ class User_Keys(models.Model):
|
|||||||
return self.__unicode__()
|
return self.__unicode__()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "mfa"
|
app_label='mfa'
|
||||||
|
|||||||
@@ -3,38 +3,29 @@ from django.views.decorators.cache import never_cache
|
|||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
from django.contrib.auth.hashers import make_password, PBKDF2PasswordHasher
|
from django.contrib.auth.hashers import make_password, PBKDF2PasswordHasher
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.conf import settings
|
|
||||||
from .Common import get_redirect_url
|
from .Common import get_redirect_url
|
||||||
from .models import User_Keys
|
from .models import *
|
||||||
import simplejson
|
import simplejson
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
USER_FRIENDLY_NAME = "Recovery Codes"
|
USER_FRIENDLY_NAME = "Recovery Codes"
|
||||||
|
|
||||||
|
|
||||||
class Hash(PBKDF2PasswordHasher):
|
class Hash(PBKDF2PasswordHasher):
|
||||||
algorithm = "pbkdf2_sha256_custom"
|
algorithm = 'pbkdf2_sha256_custom'
|
||||||
iterations = getattr(settings,"RECOVERY_ITERATION",1)
|
iterations = getattr(settings,"RECOVERY_ITERATION",1)
|
||||||
|
|
||||||
|
|
||||||
def delTokens(request):
|
def delTokens(request):
|
||||||
#Only when all MFA have been deactivated, or to generate new !
|
#Only when all MFA have been deactivated, or to generate new !
|
||||||
#We iterate only to clean if any error happend and multiple entry of RECOVERY created for one user
|
#We iterate only to clean if any error happend and multiple entry of RECOVERY created for one user
|
||||||
for key in User_Keys.objects.filter(
|
for key in User_Keys.objects.filter(username=request.user.username, key_type = "RECOVERY"):
|
||||||
username=request.user.username, key_type="RECOVERY"
|
|
||||||
):
|
|
||||||
if key.username == request.user.username:
|
if key.username == request.user.username:
|
||||||
key.delete()
|
key.delete()
|
||||||
|
|
||||||
|
|
||||||
def randomGen(n):
|
def randomGen(n):
|
||||||
return "".join(
|
return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(n))
|
||||||
random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
|
|
||||||
for _ in range(n)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def genTokens(request):
|
def genTokens(request):
|
||||||
@@ -46,7 +37,7 @@ def genTokens(request):
|
|||||||
clearKeys = []
|
clearKeys = []
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
token = randomGen(5) + "-" + randomGen(5)
|
token = randomGen(5) + "-" + randomGen(5)
|
||||||
hashedToken = make_password(token, salt, "pbkdf2_sha256_custom")
|
hashedToken = make_password(token, salt, 'pbkdf2_sha256_custom')
|
||||||
hashedKeys.append(hashedToken)
|
hashedKeys.append(hashedToken)
|
||||||
clearKeys.append(token)
|
clearKeys.append(token)
|
||||||
uk=User_Keys()
|
uk=User_Keys()
|
||||||
@@ -73,7 +64,6 @@ def verify_login(request, username, token):
|
|||||||
return [True, key.id, len(secret_keys) == 0]
|
return [True, key.id, len(secret_keys) == 0]
|
||||||
return [False]
|
return [False]
|
||||||
|
|
||||||
|
|
||||||
def getTokenLeft(request):
|
def getTokenLeft(request):
|
||||||
uk = User_Keys.objects.filter(username=request.user.username, key_type = "RECOVERY")
|
uk = User_Keys.objects.filter(username=request.user.username, key_type = "RECOVERY")
|
||||||
keyLeft=0
|
keyLeft=0
|
||||||
@@ -81,48 +71,29 @@ def getTokenLeft(request):
|
|||||||
keyLeft += len(key.properties["secret_keys"])
|
keyLeft += len(key.properties["secret_keys"])
|
||||||
return HttpResponse(simplejson.dumps({"left":keyLeft}))
|
return HttpResponse(simplejson.dumps({"left":keyLeft}))
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
context["mode"]="recheck"
|
context["mode"]="recheck"
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if verify_login(request, request.user.username, token=request.POST["recovery"])[
|
if verify_login(request,request.user.username, token=request.POST["recovery"])[0]:
|
||||||
0
|
|
||||||
]:
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
request.session["mfa"]["rechecked_at"] = time.time()
|
request.session["mfa"]["rechecked_at"] = time.time()
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"recheck": True}), content_type="application/json")
|
||||||
simplejson.dumps({"recheck": True}), content_type="application/json"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
|
||||||
simplejson.dumps({"recheck": False}), content_type="application/json"
|
|
||||||
)
|
|
||||||
return render(request,"RECOVERY/recheck.html", context)
|
return render(request,"RECOVERY/recheck.html", context)
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def auth(request):
|
def auth(request):
|
||||||
from .views import login
|
from .views import login
|
||||||
|
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
if request.method=="POST":
|
if request.method=="POST":
|
||||||
tokenLength = len(request.POST["recovery"])
|
tokenLength = len(request.POST["recovery"])
|
||||||
if tokenLength == 11 and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
|
if tokenLength == 11 and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
|
||||||
#Backup code check
|
#Backup code check
|
||||||
resBackup = verify_login(
|
resBackup=verify_login(request, request.session["base_username"], token=request.POST["recovery"])
|
||||||
request,
|
|
||||||
request.session["base_username"],
|
|
||||||
token=request.POST["recovery"],
|
|
||||||
)
|
|
||||||
if resBackup[0]:
|
if resBackup[0]:
|
||||||
mfa = {
|
mfa = {"verified": True, "method": "RECOVERY","id":resBackup[1], "lastBackup":resBackup[2]}
|
||||||
"verified": True,
|
|
||||||
"method": "RECOVERY",
|
|
||||||
"id": resBackup[1],
|
|
||||||
"lastBackup": resBackup[2],
|
|
||||||
}
|
|
||||||
# if getattr(settings, "MFA_RECHECK", False):
|
# if getattr(settings, "MFA_RECHECK", False):
|
||||||
# mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
# mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
# + datetime.timedelta(
|
# + datetime.timedelta(
|
||||||
@@ -142,7 +113,6 @@ def auth(request):
|
|||||||
|
|
||||||
return render(request,"RECOVERY/Auth.html", context)
|
return render(request,"RECOVERY/Auth.html", context)
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def start(request):
|
def start(request):
|
||||||
"""Start Managing recovery tokens"""
|
"""Start Managing recovery tokens"""
|
||||||
|
|||||||
@@ -61,20 +61,23 @@
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
<br/>
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="container">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="panel panel-default card">
|
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> Adding a New {{ method.name }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0"><strong> Adding a New {{ method.name }}</strong></h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="row alert alert-pr" id="res" align="center">
|
<div class="row alert alert-pr" id="res" align="center">
|
||||||
<p style="color: green">Your browser should ask you to confirm you identity.</p>
|
<p style="color: green">Your browser should ask you to confirm you identity.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
||||||
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
||||||
<div class="row">
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<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 offset-2 col-8">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="panel panel-default card">
|
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> Security Key</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0"><strong> Security Key</strong></h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div style="padding-left: 15px" class="col-md-10 col-md-offset-1" id="main_paragraph" align="center">
|
<div style="padding-left: 15px" class="col-md-10 col-md-offset-1" id="main_paragraph" align="center">
|
||||||
@@ -16,7 +17,6 @@
|
|||||||
Welcome back {% comment %}<img src="{% url 'getUserImage' request.session.base_username %}" title="{{ request.session.base_username }}" style="padding: 3px;height: 50px" class="img-circle"/>{% endcomment %} {{ request.session.base_username }}<br/>
|
Welcome back {% comment %}<img src="{% url 'getUserImage' request.session.base_username %}" title="{{ request.session.base_username }}" style="padding: 3px;height: 50px" class="img-circle"/>{% endcomment %} {{ request.session.base_username }}<br/>
|
||||||
<a href="{% url 'mfa_reset_cookie' %}">Not me</a>
|
<a href="{% url 'mfa_reset_cookie' %}">Not me</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="res">
|
<div id="res">
|
||||||
<p style="color: green">please press the button on your security key to prove it is you.</p>
|
<p style="color: green">please press the button on your security key to prove it is you.</p>
|
||||||
@@ -32,19 +32,13 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3" style="padding-left: 25px">
|
|
||||||
|
|
||||||
{% if request.session.mfa_methods|length > 1 %}
|
{% if request.session.mfa_methods|length > 1 %}
|
||||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|||||||
@@ -40,12 +40,15 @@
|
|||||||
<script src="{% static 'mfa/js/bootstrap-toggle.min.js'%}"></script>
|
<script src="{% static 'mfa/js/bootstrap-toggle.min.js'%}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{block.super}}
|
|
||||||
<br/>
|
|
||||||
<br/>
|
<div class="col-lg-10 col-md-10 col-12 mx-auto">
|
||||||
<div class="container">
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="row">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="offset-5 col-2" style="text-align: center">
|
</div>
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="" style="text-align: center">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown" data-bs-toggle="dropdown">
|
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown" data-bs-toggle="dropdown">
|
||||||
Add Method <span class="caret"></span>
|
Add Method <span class="caret"></span>
|
||||||
@@ -70,10 +73,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-responsive table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Date Added</th>
|
<th>Date Added</th>
|
||||||
<th>Expires On</th>
|
<th>Expires On</th>
|
||||||
@@ -85,7 +87,6 @@
|
|||||||
{% if keys %}
|
{% if keys %}
|
||||||
{% for key in keys %}
|
{% for key in keys %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<td>{{ key.name }}</td>
|
<td>{{ key.name }}</td>
|
||||||
<td>{{ key.added_on }}</td>
|
<td>{{ key.added_on }}</td>
|
||||||
<td>{% if key.expires %}{{ key.expires }}{% else %}N/A{% endif %}</td>
|
<td>{% if key.expires %}{{ key.expires }}{% else %}N/A{% endif %}</td>
|
||||||
@@ -121,5 +122,10 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
@@ -98,35 +97,25 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
<br/>
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="container d-flex justify-content-center">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<h4>Recovery Codes List</h4>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
<div class="tokenrow" id="tokens">
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
Recovery Codes List
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="tokenrow mb-4" id="tokens">
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-md-4 col-md-offset-4" style="padding-left: 0px" align="center">
|
<div class="col-md-4 col-md-offset-4" style="padding-left: 0px" align="center">
|
||||||
|
|
||||||
<button onclick="confirmRegenerateTokens()" class="btn btn-success">Regenerate</button>
|
<button onclick="confirmRegenerateTokens()" class="btn btn-success">Regenerate</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6" align="right" style="padding-right: 30px">
|
<div class="col-md-6" align="right" style="padding-right: 30px">
|
||||||
|
|
||||||
<a href="{{redirect_html}}" class="btn btn-default btn-secondary" role="button"> {{reg_success_msg}}</a>
|
<a href="{{redirect_html}}" class="btn btn-default btn-secondary" role="button"> {{reg_success_msg}}</a>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,17 +22,18 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class="row">
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-10 col-lg-offset-1">
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="panel panel-default card">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> Recovery code</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
<FORM METHOD="POST" ACTION="{% url 'recovery_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
<h6 class="mb-0">
|
||||||
|
Recovery code
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form METHOD="POST" ACTION="{% url 'recovery_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if invalid %}
|
{% if invalid %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
@@ -46,40 +47,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<p>Enter the 11-digits on your authenticator. Or input a recovery code</p>
|
<p>Enter the 11-digits on your authenticator. Or input a recovery code</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group input-group-static mb-3">
|
||||||
<span class="input-group-addon input-group-text">
|
<span class="input-group-addon input-group-text">
|
||||||
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
|
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
|
||||||
</span>
|
</span>
|
||||||
<input class="form-control" size="11" MaxLength="11" value="" placeholder="e.g abcde-fghij" name="recovery" type="text" id="recovery" autofocus>
|
<input class="form-control" size="11" MaxLength="11" value="" placeholder="e.g abcde-fghij" name="recovery" type="text" id="recovery" autofocus>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group d-grid gap-2">
|
<div class="form-group d-grid gap-2">
|
||||||
|
|
||||||
<input type="{% if mode == "auth" %}submit{% elif mode == 'recheck' %}button{% endif %}" {% if mode == "recheck" %}onclick="send_recovery()" {% endif %} class="btn btn-lg btn-success btn-block" value="Sign in">
|
<input type="{% if mode == "auth" %}submit{% elif mode == 'recheck' %}button{% endif %}" {% if mode == "recheck" %}onclick="send_recovery()" {% endif %} class="btn btn-lg btn-success btn-block" value="Sign in">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
</FORM>
|
|
||||||
</div>
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3" style="padding-left: 25px">
|
|
||||||
{% if request.session.mfa_methods|length > 1 %}
|
{% if request.session.mfa_methods|length > 1 %}
|
||||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<style>
|
||||||
#two-factor-steps {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
.row{
|
.row{
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
@@ -87,11 +82,19 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="container d-flex justify-content-center">
|
<div class="container-fluid px-2 px-md-4">
|
||||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="row" align="center">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<h4>Adding a new {{ method.name }}</h4>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100" id="two-factor-steps" >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">Adding a new {{ method.name }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3">
|
||||||
<div class="row">
|
<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>. 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,
|
||||||
@@ -111,23 +114,27 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<p><b>Enter the six-digit code from the application</b></p>
|
<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>
|
<p >After scanning the barcode image, the app will display a six-digit code that you can enter below. </p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="offset-md-4 col-md-4">
|
<div class="offset-md-4 col-md-4">
|
||||||
|
<div class="input-group input-group-static">
|
||||||
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
|
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row" style="padding-top: 10px;">
|
<div class="row" style="padding-top: 10px;">
|
||||||
<div class="col-md-4 offset-md-4" style="padding-left: 0px">
|
<div class="offset-md-4 col-md-4">
|
||||||
<button class="btn btn-success" onclick="verify()">Enable</button>
|
<button class="btn btn-success" onclick="verify()">Enable</button>
|
||||||
<a href="{% url 'mfa_home' %}" class="btn btn-default btn-secondary" role="button">Cancel</a>
|
<a href="{% url 'mfa_home' %}" class="btn btn-default btn-secondary" role="button">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{% extends "mfa_auth_base.html" %}
|
{% extends "mfa_auth_base.html" %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<!--<style>
|
||||||
.row{
|
.row{
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>-->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -1,30 +1,19 @@
|
|||||||
<script type="application/javascript">
|
{% extends "mfa_auth_base.html" %}
|
||||||
function send_totp() {
|
{% load static %}
|
||||||
$.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='container'>
|
|
||||||
<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">
|
{% block content %}
|
||||||
<div class="panel panel-default card">
|
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> One Time Password</strong>
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3 ">
|
||||||
<FORM METHOD="POST" ACTION="{% url 'totp_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
<h6 class="mb-0">One Time Password</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form METHOD="POST" ACTION="{% url 'totp_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if invalid %}
|
{% if invalid %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
@@ -37,41 +26,39 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class=" text-left">
|
||||||
<p>Enter the 6-digits on your authenticator</p>
|
<p>Enter the 6-digits on your authenticator</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group input-group-dynamic mb-3">
|
||||||
<span class="input-group-addon input-group-text">
|
<span class="input-group-addon input-group-text">
|
||||||
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
|
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
|
||||||
</span>
|
</span>
|
||||||
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
|
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group d-grid gap-2">
|
<div class="form-group d-grid gap-2">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
</fieldset>
|
||||||
</FORM>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12 mb-3" style="padding-left: 25px">
|
<div class="">
|
||||||
{% if request.session.mfa_methods|length > 1 %}
|
{% if request.session.mfa_methods|length > 1 %}
|
||||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -25,17 +25,17 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
<br/>
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<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 card">
|
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> Add Trusted Device</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<strong> Add Trusted Device</strong>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
{% if success %}
|
{% if success %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
Please check your PC window, to continue the process.
|
Please check your PC window, to continue the process.
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning">Please make sure you are not in private (incognito) mode <i class="fal fa-user-secret"></i></div>
|
<div class="alert alert-warning text-white">Please make sure you are not in private (incognito) mode <i class="fa fa-user-secret"></i></div>
|
||||||
<FORM METHOD="POST" ACTION="{% url 'add_td' %}" Id="formLogin" onSubmit="return checkFlag()" name="FrontPage_Form1">
|
<form METHOD="POST" ACTION="{% url 'add_td' %}" Id="formLogin" onSubmit="return checkFlag()" name="FrontPage_Form1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if invalid %}
|
{% if invalid %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
@@ -60,23 +60,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
<div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group input-group-static mb-3">
|
||||||
<span class="input-group-addon input-group-text">
|
<span class="input-group-addon input-group-text">
|
||||||
<i class="glyphicon glyphicon-user bi bi-person"></i>
|
<i class="glyphicon glyphicon-user bi bi-person"></i>
|
||||||
</span>
|
</span>
|
||||||
<input class="form-control" id="username" size="30" MaxLength="30" placeholder="Username" name="username" value="{{ username }}" type="text" autofocus autocomplete="on">
|
<input class="form-control" id="username" size="30" MaxLength="30" placeholder="Username" name="username" value="{{ username }}" type="text" autofocus autocomplete="on">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group input-group-static mb-3">
|
||||||
<span class="input-group-addon input-group-text">
|
<span class="input-group-addon input-group-text">
|
||||||
<i class="fa fa-key"></i>
|
<i class="fa fa-key"></i>
|
||||||
</span>
|
</span>
|
||||||
@@ -84,27 +80,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span class="input-group mb-3">
|
<span class=" form-check ">
|
||||||
<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>
|
<input class="form-check-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>
|
</div>
|
||||||
{% comment %}
|
{% comment %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<span class="input-group-addon input-group-text">
|
<span class="input-group-addon input-group-text">
|
||||||
|
|
||||||
|
|
||||||
<select size="1" name="Institution_Code" style="font-size: 10pt; font-family: Calibri; height: 34px;width: 230px">
|
<select size="1" name="Institution_Code" style="font-size: 10pt; font-family: Calibri; height: 34px;width: 230px">
|
||||||
{% for ins in institutes %}
|
{% for ins in institutes %}
|
||||||
<option value="{{ ins.institution_code }}">{{ ins.alias }}</option>
|
<option value="{{ ins.institution_code }}">{{ ins.alias }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<div class="form-group d-grid gap-2">
|
<div class="form-group d-grid gap-2 mt-2">
|
||||||
<input type="submit" class="btn btn-lg btn-success btn-block" value="Trust Device">
|
<input type="submit" class="btn btn-lg btn-success btn-block" value="Trust Device">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,13 +107,6 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-footer card-footer">
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% include "modal.html" %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -78,21 +78,22 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
<br/>
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="container">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<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>
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
<div class="row" >
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
Add Trusted Device
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
{% if not_allowed %}
|
{% if not_allowed %}
|
||||||
<div class="alert alert-danger">You can't add any more devices, you need to remove previously trusted devices first.</div>
|
<div class="alert alert-danger">You can't add any more devices, you need to remove previously trusted devices first.</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p style="color: green">Allow access from mobile phone and tables.</p><br/>
|
<p style="color: green">Allow access from mobile phone and tables.</p><br/>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h5>Steps:</h5>
|
<h5>Steps:</h5>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,8 +109,6 @@
|
|||||||
<li>Confirm the consent and submit form.</li>
|
<li>Confirm the consent and submit form.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h5>Manual</h5>
|
<h5>Manual</h5>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -122,20 +121,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8 offset-2">
|
|
||||||
This window will ask to confirm the device.
|
This window will ask to confirm the device.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<br/>
|
</div>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
{% include 'mfa_check.html' %}
|
{% include 'mfa_check.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
|
||||||
#two-factor-steps {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
.row{
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="{% static 'mfa/js/u2f-api.js' %}" type="text/javascript"></script>
|
<script src="{% static 'mfa/js/u2f-api.js' %}" type="text/javascript"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function addToken() {
|
function addToken() {
|
||||||
@@ -43,20 +34,29 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
|
</div>
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-body">
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
<div id="two-factor-steps">
|
||||||
<div id="res"></div>
|
<div id="res"></div>
|
||||||
<div class="row" align="center">
|
<div class="row" align="center">
|
||||||
<h4>Adding {{ method.name}}</h4>
|
<h4>Adding {{ method.name}}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row" align="center">
|
||||||
<p style="color: green">Your secure Key should be flashing now, please press on button.</p>
|
<p style="color: green">Your secure Key should be flashing now, please press on button.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
{% include "modal.html" %}
|
{% include "modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<div class="row">
|
< <div class="col-lg-8 col-md-8 col-12 mx-auto">
|
||||||
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<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">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<div class="panel panel-default card">
|
|
||||||
<div class="panel-heading card-header">
|
|
||||||
<strong> Verify your identity using {{ method.name }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body card-body">
|
<div class="card mx-3 mx-md-4 mt-n6 h-100 z-index-0 fadeIn3 fadeInBottom " >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">Verify your identity using {{ method.name }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div style="padding-left: 15px" class="col-md-10 col-md-offset-1" id="main_paragraph" align="center">
|
<div id="main_paragraph" align="center">
|
||||||
<p style="color: green">Your key should be flashing now, please press the button.</p>
|
<p style="color: green">Your key should be flashing now, please press the button.</p>
|
||||||
{% if mode == "auth" %}
|
{% if mode == "auth" %}
|
||||||
<form id="u2f_login" action="{% url 'u2f_verify' %}" method="post">
|
<form id="u2f_login" action="{% url 'u2f_verify' %}" method="post">
|
||||||
@@ -21,18 +22,17 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3" style="padding-left: 15px">
|
|
||||||
|
|
||||||
{% if request.session.mfa_methods|length > 1 %}
|
{% if request.session.mfa_methods|length > 1 %}
|
||||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{% static 'mfa/js/u2f-api.js' %}" type="text/javascript"></script>
|
<script src="{% static 'mfa/js/u2f-api.js' %}" type="text/javascript"></script>
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
{% extends "mfa_auth_base.html" %}
|
{% extends "mfa_auth_base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br/>
|
|
||||||
<div class='container'>
|
|
||||||
<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 offset-2 col-8">
|
<div class="container-fluid px-2 px-md-4 mt-6">
|
||||||
<div class="panel panel-default card">
|
<div class="page-header min-height-300 border-radius-xl mt-4" style="background-repeat: no-repeat; background-position: cover; background-size:contain; background-image: url('{% static 'img/keys.jpg'%}');">
|
||||||
<div class="panel-heading card-header">
|
<span class="mask bg-gradient-primary opacity-6"></span>
|
||||||
<strong> Select Second Verification Method</strong>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card mx-3 mx-md-4 mt-n6 h-100" >
|
||||||
|
<div class="card-header pb-0 p-3">
|
||||||
|
<h6 class="mb-0">Select Second Verification Method</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3">
|
||||||
<div class="panel-body card-body">
|
<div class="panel-body card-body">
|
||||||
<ul>
|
<ul>
|
||||||
{% for method in request.session.mfa_methods %}
|
{% for method in request.session.mfa_methods %}
|
||||||
|
|
||||||
<li><a href="{% url "mfa_goto" method %}">
|
<li style="list-style-type: none;"><a class="btn btn-sm btn-outline-primary" href="{% url 'mfa_goto' method %}" >
|
||||||
{% if method == "TOTP" %}{% if 'TOTP' in RENAME_METHODS %}{{ RENAME_METHODS.TOTP }}{% else %}Authenticator App{% endif %}
|
{% if method == "TOTP" %}{% if 'TOTP' in RENAME_METHODS %}{{ RENAME_METHODS.TOTP }}{% else %}Authenticator App{% endif %}
|
||||||
{% elif method == "Email" %}{% if 'Email' in RENAME_METHODS %}{{ RENAME_METHODS.Email }}{% else %}Send OTP by Email{% endif %}
|
{% elif method == "Email" %}{% if 'Email' in RENAME_METHODS %}{{ RENAME_METHODS.Email }}{% else %}Send OTP by Email{% endif %}
|
||||||
{% elif method == "U2F" %}{% if 'U2F' in RENAME_METHODS %}{{ RENAME_METHODS.U2F }}{% else %}Secure Key{% endif %}
|
{% elif method == "U2F" %}{% if 'U2F' in RENAME_METHODS %}{{ RENAME_METHODS.U2F }}{% else %}Secure Key{% endif %}
|
||||||
@@ -25,8 +26,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
67
mfa/totp.py
67
mfa/totp.py
@@ -22,25 +22,18 @@ def verify_login(request, username, token):
|
|||||||
return [True,key.id]
|
return [True,key.id]
|
||||||
return [False]
|
return [False]
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
context["mode"]="recheck"
|
context["mode"]="recheck"
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if verify_login(request,request.user.username, token=request.POST["otp"])[0]:
|
if verify_login(request,request.user.username, token=request.POST["otp"])[0]:
|
||||||
import time
|
import time
|
||||||
|
|
||||||
request.session["mfa"]["rechecked_at"] = time.time()
|
request.session["mfa"]["rechecked_at"] = time.time()
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"recheck": True}), content_type="application/json")
|
||||||
simplejson.dumps({"recheck": True}), content_type="application/json"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
|
||||||
simplejson.dumps({"recheck": False}), content_type="application/json"
|
|
||||||
)
|
|
||||||
return render(request,"TOTP/recheck.html", context)
|
return render(request,"TOTP/recheck.html", context)
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def auth(request):
|
def auth(request):
|
||||||
context=csrf(request)
|
context=csrf(request)
|
||||||
@@ -48,44 +41,26 @@ def auth(request):
|
|||||||
tokenLength = len(request.POST["otp"])
|
tokenLength = len(request.POST["otp"])
|
||||||
if tokenLength == 6:
|
if tokenLength == 6:
|
||||||
#TOTO code check
|
#TOTO code check
|
||||||
res = verify_login(
|
res=verify_login(request,request.session["base_username"],token = request.POST["otp"])
|
||||||
request, request.session["base_username"], token=request.POST["otp"]
|
|
||||||
)
|
|
||||||
if res[0]:
|
if res[0]:
|
||||||
mfa = {"verified": True, "method": "TOTP","id":res[1]}
|
mfa = {"verified": True, "method": "TOTP","id":res[1]}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = datetime.datetime.timestamp(
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
(
|
|
||||||
datetime.datetime.now()
|
|
||||||
+ datetime.timedelta(
|
+ datetime.timedelta(
|
||||||
seconds=random.randint(
|
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||||
settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
request.session["mfa"] = mfa
|
request.session["mfa"] = mfa
|
||||||
return login(request)
|
return login(request)
|
||||||
context["invalid"]=True
|
context["invalid"]=True
|
||||||
return render(request,"TOTP/Auth.html", context)
|
return render(request,"TOTP/Auth.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getToken(request):
|
def getToken(request):
|
||||||
secret_key=pyotp.random_base32()
|
secret_key=pyotp.random_base32()
|
||||||
totp = pyotp.TOTP(secret_key)
|
totp = pyotp.TOTP(secret_key)
|
||||||
request.session["new_mfa_answer"]=totp.now()
|
request.session["new_mfa_answer"]=totp.now()
|
||||||
return HttpResponse(
|
return HttpResponse(simplejson.dumps({"qr":pyotp.totp.TOTP(secret_key).provisioning_uri(str(request.user.username), issuer_name = settings.TOKEN_ISSUER_NAME),
|
||||||
simplejson.dumps(
|
"secret_key": secret_key}))
|
||||||
{
|
|
||||||
"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):
|
def verify(request):
|
||||||
answer=request.GET["answer"]
|
answer=request.GET["answer"]
|
||||||
secret_key=request.GET["key"]
|
secret_key=request.GET["key"]
|
||||||
@@ -97,31 +72,19 @@ def verify(request):
|
|||||||
#uk.name="Authenticatior #%s"%User_Keys.objects.filter(username=user.username,type="TOTP")
|
#uk.name="Authenticatior #%s"%User_Keys.objects.filter(username=user.username,type="TOTP")
|
||||||
uk.key_type="TOTP"
|
uk.key_type="TOTP"
|
||||||
uk.save()
|
uk.save()
|
||||||
if (
|
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type="RECOVERY",
|
||||||
getattr(settings, "MFA_ENFORCE_RECOVERY_METHOD", False)
|
username=request.user.username).exists():
|
||||||
and not User_Keys.objects.filter(
|
request.session["mfa_reg"] = {"method": "TOTP",
|
||||||
key_type="RECOVERY", username=request.user.username
|
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("TOTP", "TOTP")}
|
||||||
).exists()
|
|
||||||
):
|
|
||||||
request.session["mfa_reg"] = {
|
|
||||||
"method": "TOTP",
|
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("TOTP", "TOTP"),
|
|
||||||
}
|
|
||||||
return HttpResponse("RECOVERY")
|
return HttpResponse("RECOVERY")
|
||||||
else:
|
else:
|
||||||
return HttpResponse("Success")
|
return HttpResponse("Success")
|
||||||
else:
|
else: return HttpResponse("Error")
|
||||||
return HttpResponse("Error")
|
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def start(request):
|
def start(request):
|
||||||
"""Start Adding Time One Time Password (TOTP)"""
|
"""Start Adding Time One Time Password (TOTP)"""
|
||||||
context = get_redirect_url()
|
context = get_redirect_url()
|
||||||
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get(
|
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get("RECOVERY", "Recovery codes")
|
||||||
"RECOVERY", "Recovery codes"
|
context["method"] = {"name":getattr(settings,"MFA_RENAME_METHODS",{}).get("TOTP","Authenticator")}
|
||||||
)
|
|
||||||
context["method"] = {
|
|
||||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("TOTP", "Authenticator")
|
|
||||||
}
|
|
||||||
return render(request,"TOTP/Add.html",context)
|
return render(request,"TOTP/Add.html",context)
|
||||||
|
|||||||
95
mfa/urls.py
95
mfa/urls.py
@@ -1,55 +1,56 @@
|
|||||||
from . import views,totp,U2F,TrustedDevice,helpers,FIDO2,Email,recovery
|
from . import views,totp,U2F,TrustedDevice,helpers,FIDO2,Email,recovery
|
||||||
|
|
||||||
#app_name='mfa'
|
#app_name='mfa'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.urls import re_path as url
|
from django.urls import re_path as url
|
||||||
except ImportError:
|
except:
|
||||||
from django.conf.urls import url # pyre-ignore[21]
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"totp/start/", totp.start, name="start_new_otop"),
|
url(r'totp/start/', totp.start , name="start_new_otop"),
|
||||||
url(r"totp/getToken", totp.getToken, name="get_new_otop"),
|
url(r'totp/getToken', totp.getToken , name="get_new_otop"),
|
||||||
url(r"totp/verify", totp.verify, name="verify_otop"),
|
url(r'totp/verify', totp.verify, name="verify_otop"),
|
||||||
url(r"totp/auth", totp.auth, name="totp_auth"),
|
url(r'totp/auth', totp.auth, name="totp_auth"),
|
||||||
url(r"totp/recheck", totp.recheck, name="totp_recheck"),
|
url(r'totp/recheck', totp.recheck, name="totp_recheck"),
|
||||||
url(r"recovery/start", recovery.start, name="manage_recovery_codes"),
|
|
||||||
url(
|
url(r'recovery/start', recovery.start, name="manage_recovery_codes"),
|
||||||
r"recovery/getTokenLeft", recovery.getTokenLeft, name="get_recovery_token_left"
|
url(r'recovery/getTokenLeft', recovery.getTokenLeft, name="get_recovery_token_left"),
|
||||||
),
|
url(r'recovery/genTokens', recovery.genTokens, name="regen_recovery_tokens"),
|
||||||
url(r"recovery/genTokens", recovery.genTokens, name="regen_recovery_tokens"),
|
url(r'recovery/auth', recovery.auth, name="recovery_auth"),
|
||||||
url(r"recovery/auth", recovery.auth, name="recovery_auth"),
|
url(r'recovery/recheck', recovery.recheck, name="recovery_recheck"),
|
||||||
url(r"recovery/recheck", recovery.recheck, name="recovery_recheck"),
|
|
||||||
url(r"email/start/", Email.start, name="start_email"),
|
url(r'email/start/', Email.start , name="start_email"),
|
||||||
url(r"email/auth/", Email.auth, name="email_auth"),
|
url(r'email/auth/', Email.auth , name="email_auth"),
|
||||||
url(r"u2f/$", U2F.start, name="start_u2f"),
|
|
||||||
url(r"u2f/bind", U2F.bind, name="bind_u2f"),
|
url(r'u2f/$', U2F.start, name="start_u2f"),
|
||||||
url(r"u2f/auth", U2F.auth, name="u2f_auth"),
|
url(r'u2f/bind', U2F.bind, name="bind_u2f"),
|
||||||
url(r"u2f/process_recheck", U2F.process_recheck, name="u2f_recheck"),
|
url(r'u2f/auth', U2F.auth, name="u2f_auth"),
|
||||||
url(r"u2f/verify", U2F.verify, name="u2f_verify"),
|
url(r'u2f/process_recheck', U2F.process_recheck, name="u2f_recheck"),
|
||||||
url(r"fido2/$", FIDO2.start, name="start_fido2"),
|
url(r'u2f/verify', U2F.verify, name="u2f_verify"),
|
||||||
url(r"fido2/auth", FIDO2.auth, name="fido2_auth"),
|
|
||||||
url(r"fido2/begin_auth", FIDO2.authenticate_begin, name="fido2_begin_auth"),
|
url(r'fido2/$', FIDO2.start, name="start_fido2"),
|
||||||
url(
|
url(r'fido2/auth', FIDO2.auth, name="fido2_auth"),
|
||||||
r"fido2/complete_auth", FIDO2.authenticate_complete, name="fido2_complete_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/begin_reg', FIDO2.begin_registeration, name="fido2_begin_reg"),
|
||||||
url(r"fido2/complete_reg", FIDO2.complete_reg, name="fido2_complete_reg"),
|
url(r'fido2/complete_reg', FIDO2.complete_reg, name="fido2_complete_reg"),
|
||||||
url(r"fido2/recheck", FIDO2.recheck, name="fido2_recheck"),
|
url(r'fido2/recheck', FIDO2.recheck, name="fido2_recheck"),
|
||||||
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/$', TrustedDevice.start, name="start_td"),
|
||||||
url(r"td/get-ua", TrustedDevice.getUserAgent, name="td_get_useragent"),
|
url(r'td/add', TrustedDevice.add, name="add_td"),
|
||||||
url(r"td/trust", TrustedDevice.trust_device, name="td_trust_device"),
|
url(r'td/send_link', TrustedDevice.send_email, name="td_sendemail"),
|
||||||
url(r"u2f/checkTrusted", TrustedDevice.checkTrusted, name="td_checkTrusted"),
|
url(r'td/get-ua', TrustedDevice.getUserAgent, name="td_get_useragent"),
|
||||||
url(r"u2f/secure_device", TrustedDevice.getCookie, name="td_securedevice"),
|
url(r'td/trust', TrustedDevice.trust_device, name="td_trust_device"),
|
||||||
url(r"^$", views.index, name="mfa_home"),
|
url(r'u2f/checkTrusted', TrustedDevice.checkTrusted, name="td_checkTrusted"),
|
||||||
url(r"goto/(.*)", views.goto, name="mfa_goto"),
|
url(r'u2f/secure_device', TrustedDevice.getCookie, name="td_securedevice"),
|
||||||
url(r"selct_method", views.show_methods, name="mfa_methods_list"),
|
|
||||||
url(r"recheck", helpers.recheck, name="mfa_recheck"),
|
url(r'^$', views.index, name="mfa_home"),
|
||||||
url(r"toggleKey", views.toggleKey, name="toggle_key"),
|
url(r'goto/(.*)', views.goto, name="mfa_goto"),
|
||||||
url(r"delete", views.delKey, name="mfa_delKey"),
|
url(r'selct_method', views.show_methods, name="mfa_methods_list"),
|
||||||
url(r"reset", views.reset_cookie, name="mfa_reset_cookie"),
|
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"),
|
||||||
|
|
||||||
]
|
]
|
||||||
# print(urlpatterns)
|
# print(urlpatterns)
|
||||||
44
mfa/views.py
44
mfa/views.py
@@ -2,30 +2,25 @@ import importlib
|
|||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpResponse,HttpResponseRedirect
|
from django.http import HttpResponse,HttpResponseRedirect
|
||||||
|
from .models import *
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
except:
|
except:
|
||||||
from django.core.urlresolvers import reverse # pyre-ignore[21]
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.template.context_processors import csrf
|
||||||
from user_agents import parse
|
from django.template.context import RequestContext
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from . import TrustedDevice
|
from . import TrustedDevice
|
||||||
from .models import User_Keys
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from user_agents import parse
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
keys=[]
|
keys=[]
|
||||||
context = {
|
context={"keys":User_Keys.objects.filter(username=request.user.username),"UNALLOWED_AUTHEN_METHODS":settings.MFA_UNALLOWED_METHODS
|
||||||
"keys": User_Keys.objects.filter(username=request.user.username),
|
,"HIDE_DISABLE":getattr(settings,"MFA_HIDE_DISABLE",[]),'RENAME_METHODS':getattr(settings,'MFA_RENAME_METHODS',{})}
|
||||||
"UNALLOWED_AUTHEN_METHODS": settings.MFA_UNALLOWED_METHODS,
|
|
||||||
"HIDE_DISABLE": getattr(settings, "MFA_HIDE_DISABLE", []),
|
|
||||||
"RENAME_METHODS": getattr(settings, "MFA_RENAME_METHODS", {}),
|
|
||||||
}
|
|
||||||
for k in context["keys"]:
|
for k in context["keys"]:
|
||||||
k.name = getattr(settings, "MFA_RENAME_METHODS", {}).get(k.key_type, k.key_type)
|
k.name = getattr(settings,'MFA_RENAME_METHODS',{}).get(k.key_type,k.key_type)
|
||||||
if k.key_type =="Trusted Device":
|
if k.key_type =="Trusted Device":
|
||||||
setattr(k,"device",parse(k.properties.get("user_agent","-----")))
|
setattr(k,"device",parse(k.properties.get("user_agent","-----")))
|
||||||
elif k.key_type == "FIDO2":
|
elif k.key_type == "FIDO2":
|
||||||
@@ -37,16 +32,13 @@ def index(request):
|
|||||||
context["keys"]=keys
|
context["keys"]=keys
|
||||||
return render(request,"MFA.html",context)
|
return render(request,"MFA.html",context)
|
||||||
|
|
||||||
|
|
||||||
def verify(request,username):
|
def verify(request,username):
|
||||||
request.session["base_username"] = username
|
request.session["base_username"] = username
|
||||||
#request.session["base_password"] = password
|
#request.session["base_password"] = password
|
||||||
keys=User_Keys.objects.filter(username=username,enabled=1)
|
keys=User_Keys.objects.filter(username=username,enabled=1)
|
||||||
methods=list(set([k.key_type for k in keys]))
|
methods=list(set([k.key_type for k in keys]))
|
||||||
|
|
||||||
if "Trusted Device" in methods and not request.session.get(
|
if "Trusted Device" in methods and not request.session.get("checked_trusted_device",False):
|
||||||
"checked_trusted_device", False
|
|
||||||
):
|
|
||||||
if TrustedDevice.verify(request):
|
if TrustedDevice.verify(request):
|
||||||
return login(request)
|
return login(request)
|
||||||
methods.remove("Trusted Device")
|
methods.remove("Trusted Device")
|
||||||
@@ -60,25 +52,17 @@ def verify(request, username):
|
|||||||
return HttpResponseRedirect(reverse(keys[0].key_type.lower() + "_auth"))
|
return HttpResponseRedirect(reverse(keys[0].key_type.lower() + "_auth"))
|
||||||
return show_methods(request)
|
return show_methods(request)
|
||||||
|
|
||||||
|
|
||||||
def show_methods(request):
|
def show_methods(request):
|
||||||
return render(
|
return render(request,"select_mfa_method.html", {'RENAME_METHODS':getattr(settings,'MFA_RENAME_METHODS',{})})
|
||||||
request,
|
|
||||||
"select_mfa_method.html",
|
|
||||||
{"RENAME_METHODS": getattr(settings, "MFA_RENAME_METHODS", {})},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def reset_cookie(request):
|
def reset_cookie(request):
|
||||||
response=HttpResponseRedirect(settings.LOGIN_URL)
|
response=HttpResponseRedirect(settings.LOGIN_URL)
|
||||||
response.delete_cookie("base_username")
|
response.delete_cookie("base_username")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def login(request):
|
def login(request):
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
callable_func = __get_callable_function__(settings.MFA_LOGIN_CALLBACK)
|
callable_func = __get_callable_function__(settings.MFA_LOGIN_CALLBACK)
|
||||||
return callable_func(request,username=request.session["base_username"])
|
return callable_func(request,username=request.session["base_username"])
|
||||||
|
|
||||||
@@ -92,11 +76,9 @@ def delKey(request):
|
|||||||
else:
|
else:
|
||||||
return HttpResponse("Error: You own this token so you can't delete it")
|
return HttpResponse("Error: You own this token so you can't delete it")
|
||||||
|
|
||||||
|
|
||||||
def __get_callable_function__(func_path):
|
def __get_callable_function__(func_path):
|
||||||
import importlib
|
import importlib
|
||||||
|
if not '.' in func_path:
|
||||||
if not "." in func_path:
|
|
||||||
raise Exception("class Name should include modulename.classname")
|
raise Exception("class Name should include modulename.classname")
|
||||||
|
|
||||||
parsed_str = func_path.split(".")
|
parsed_str = func_path.split(".")
|
||||||
@@ -107,7 +89,6 @@ def __get_callable_function__(func_path):
|
|||||||
raise Exception("Module does not have requested function")
|
raise Exception("Module does not have requested function")
|
||||||
return callable_func
|
return callable_func
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def toggleKey(request):
|
def toggleKey(request):
|
||||||
id=request.GET["id"]
|
id=request.GET["id"]
|
||||||
@@ -123,6 +104,5 @@ def toggleKey(request):
|
|||||||
else:
|
else:
|
||||||
return HttpResponse("Error")
|
return HttpResponse("Error")
|
||||||
|
|
||||||
|
|
||||||
def goto(request,method):
|
def goto(request,method):
|
||||||
return HttpResponseRedirect(reverse(method.lower()+"_auth"))
|
return HttpResponseRedirect(reverse(method.lower()+"_auth"))
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
django >= 2.2
|
django >= 2.2
|
||||||
|
jsonfield
|
||||||
simplejson
|
simplejson
|
||||||
pyotp
|
pyotp
|
||||||
python-u2flib-server
|
python-u2flib-server
|
||||||
ua-parser
|
ua-parser
|
||||||
user-agents
|
user-agents
|
||||||
python-jose
|
python-jose
|
||||||
fido2 == 1.1.0
|
fido2 == 1.0.0
|
||||||
jsonLookup
|
jsonLookup
|
||||||
raven
|
|
||||||
pyre-check
|
|
||||||
|
|||||||
Reference in New Issue
Block a user