Compare commits

..

21 Commits
dev ... v2.6rc1

Author SHA1 Message Date
Mohamed ElKalioby
cf527d9c26 Minor Fixes 2022-09-11 09:49:55 +03:00
Spitap
b96319c7b8 cursor pointer to toolbtn class, added to totp copy 2022-09-07 17:39:11 +02:00
Spitap
04938855bb Allow one-click copy totp secret 2022-09-07 17:32:00 +02:00
Spitap
a702739d01 Documentation changes, test_db to .gitignore 2022-09-07 17:17:25 +02:00
Mohamed ElKalioby
dcd962ad16 Added Enforce Recovery Method 2022-09-07 11:53:26 +03:00
Mohamed ElKalioby
e42770e852 Added MFRENAMEMETHOD, MFA_REDIRECT_USER_TO_LAST_METHOD, Alot of theme fixes 2022-09-07 09:30:23 +03:00
Spitap
1da193f34b More cleaning, better UX 2022-08-31 21:23:40 +02:00
Spitap
d0113dd2cc Fixes and applied comments 2022-08-31 21:13:15 +02:00
Spitap
cf4f6ed224 Cleaned recovery module a bit 2022-08-31 17:10:49 +02:00
Mohamed ElKalioby
de5808e998 Fix issue in methods page text 2022-08-31 16:52:35 +03:00
Mohamed ElKalioby
fe433dee7b updating Changelog 2022-08-31 16:50:38 +03:00
Mohamed ElKalioby
598968bc92 Recovery Codes Work in Progress 2022-08-31 16:43:44 +03:00
Spitap
91e44a78c1 Removed soft generation for tokens 2022-08-25 19:50:35 +02:00
Spitap
98ca5e972d recovery code hashing 2022-08-25 19:19:30 +02:00
Spitap
fe06e4a34d Fixed token gen bug, simplify session validation 2022-08-23 09:52:06 +02:00
Spitap
bcf3ecc15c Fixed generation issue, warning when user uses its last backup code 2022-08-22 12:15:08 +02:00
Spitap
dda23b35cb Edited readme, improved frontend 2022-08-20 20:52:10 +02:00
Spitap
43e33c1a12 fixed some bugs 2022-08-20 20:17:29 +02:00
Spitap
e06bd4d176 updated requierments 2022-08-20 20:09:14 +02:00
Spitap
98e9df8a23 Use only one key/user for backup codes, better UX, handle recovery mode deactivation 2022-08-20 20:07:36 +02:00
Spitap
3ac893ad50 recovery codes 2022-08-20 11:58:25 +02:00
23 changed files with 35 additions and 453 deletions

9
.gitignore vendored
View File

@@ -1,9 +1,5 @@
example/venv
# IDE
.idea
.vscode
example/venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -109,6 +105,3 @@ venv.bak/
# mypy
.mypy_cache/
example/test_db
# OS related
.DS_Store

View File

@@ -1,26 +1,5 @@
# Change Log
## 2.9.0
* Fix a typo,
Thanks to @jkirkcaldy
## 2.8.0
* Support For Django 4.0+ JSONField
* Removed jsonfield package from requirements
## 2.7.0
* Fixed #70
* Add QR Code for trusted device link
* Better formatting for trusted device start page.
## 2.6.1
* Fix: CVE-2022-42731: related to the possibility of registration replay attack.
Thanks to 'SSE (Secure Systems Engineering)'
## 2.5.1
* Fix: CVE-2022-42731: related to the possibility of registration replay attack.
Thanks to 'SSE (Secure Systems Engineering)'
## 2.6.0
## 2.6.0 (dev)
* Adding Backup Recovery Codes (Recovery) as a method.
Thanks to @Spitfireap for work, and @peterthomassen for guidance.
* Added: `RECOVERY_ITERATION` to set the number of iteration when hashing recovery token

View File

@@ -1,7 +1,6 @@
# django-mfa2
A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Email Tokens , Trusted Devices and backup codes.
[![Works with PassKeys](https://github.com/mkalioby/django-mfa2/raw/master/img/Works%20with%20PassKeys-black.png)](https://fidoalliance.org/passkeys/)
### Pip Stats
[![PyPI version](https://badge.fury.io/py/django-mfa2.svg)](https://badge.fury.io/py/django-mfa2)
[![Downloads Count](https://static.pepy.tech/personalized-badge/django-mfa2?period=total&units=international_system&left_color=black&right_color=green&left_text=Downloads)](https://pepy.tech/project/django-mfa2)
@@ -11,21 +10,18 @@ A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Ema
[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/django-mfa2.svg)](https://anaconda.org/conda-forge/django-mfa2)
[![Conda Version](https://img.shields.io/conda/vn/conda-forge/django-mfa2.svg)](https://anaconda.org/conda-forge/django-mfa2)
Web Authencation API (WebAuthn) is state-of-the art techology that is expected to replace passwords.
![Andriod Fingerprint](https://cdn-images-1.medium.com/max/800/1*1FWkRE8D7NTA2Kn1DrPjPA.png)
For FIDO2, the following are supported
* **security keys** (Firefox 60+, Chrome 67+, Edge 18+, Safari 13 on Mac OS, Chrome on Andriod, Safari on iOS 13.3+),
* **Windows Hello** (Firefox 67+, Chrome 72+ , Edge),
* **Windows Hello** (Firefox 67+, Chrome 72+ , Edge) ,
* **Apple's Touch ID/Face ID** (Chrome 70+ on Mac OS X, Safari on macOS Big Sur, Safari on iOS 14.0+ ),
* **android-safetynet** (Chrome 70+, Firefox 68+)
* **NFC devices using PCSC** (Not Tested, but as supported in fido2)
* **Soft Tokens**
* ~~[krypt.co](https://krypt.co/): Login by a notification on your phone.~~
**Update**: Dec 2022, krypt.co has been killed by Google for Passkeys.
* [krypt.co](https://krypt.co/): Login by a notification on your phone.
In English :), It allows you to verify the user by security keys on PC, Laptops or Mobiles, Windows Hello (Fingerprint, PIN) on Windows 10 Build 1903+ (May 2019 Update) Touch/Face ID on Macbooks (Chrome, Safari), Touch/Face ID on iPhone and iPad and Fingerprint/Face/Iris/PIN on Android Phones.
@@ -35,8 +31,6 @@ Trusted device is a mode for the user to add a device that doesn't support secur
Package tested with Django 1.8, Django 2.2 on Python 2.7 and Python 3.5+ but it was not checked with any version in between but open for issues.
If you just need WebAuthn and Passkeys, you can use **[django-passkeys](https://github.com/mkalioby/django-passkeys)**, which is a slim-down of this app and much easier to integrate.
Depends on
* pyotp
@@ -48,12 +42,8 @@ Depends on
# Installation
1. using pip
* For Django >= 4.0
`pip install django-mfa2`
* For Django < 4.0
`pip install django-mfa2 jsonfield`
`pip install django-mfa2`
2. Using Conda forge
`conda config --add channels conda-forge`
@@ -197,43 +187,6 @@ function some_func() {
````
# Testing
We use `pytest` and several pytest plugins, especially the `pytest-django` and `pytest-cov` plugins that provide Django fixtures, and test coverage analysis.
In the root folder, `pytest.ini` contains configurations for running the tests, `requirements_testing.txt` contains the python packages required for running tests, and the folder `tests` contains the actual test files.
To run the tests, install the packages in requirements and requirements_testing.txt:
```bash
pip install -r requirements.txt -r requirements_testing.txt
```
then simply run pytest
```
pytest
```
to generate the coverage html pages:
```
pytest --cov=. --cov-report html -v
```
the coverage html files will be generated in the `htmlcov` folder
We use `tox` to test the package against different isolated environments. `tox.ini` contains the configurations for tox. To run the tests in the environments defined in the `tox.ini` file, make sure the python package tox is installed:
```
pip install tox
```
then run tox in the project root:
```
tox
```
# Contributors
* [mahmoodnasr](https://github.com/mahmoodnasr)
* [d3cline](https://github.com/d3cline)
@@ -243,10 +196,6 @@ tox
* [AndreasDickow](https://github.com/AndreasDickow)
* [mnelson4](https://github.com/mnelson4)
* [ezrajrice](https://github.com/ezrajrice)
* [Spitfireap](https://github.com/Spitfireap)
* [peterthomassen](https://github.com/peterthomassen)
* [oussjarrousse](https://github.com/oussjarrousse)
* [jkirkcaldy](https://github.com/jkirkcaldy)
# Security contact information

View File

@@ -16,13 +16,12 @@ Including another URLconf
from django.contrib import admin
from django.urls import path,re_path,include
from . import views,auth
from mfa import TrustedDevice
urlpatterns = [
path('admin/', admin.site.urls),
path('mfa/', include('mfa.urls')),
path('auth/login',auth.loginView,name="login"),
path('auth/logout',auth.logoutView,name="logout"),
path('devices/add/', TrustedDevice.add,name="add_trusted_device"),
re_path('^$',views.home,name='home'),
path('registered/',views.registered,name='registered')
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -16,7 +16,7 @@ from .views import login, reset_cookie
import datetime
from .Common import get_redirect_url
from django.utils import timezone
from django.http import JsonResponse
def recheck(request):
"""Starts FIDO2 recheck"""
@@ -49,15 +49,13 @@ def begin_registeration(request):
def complete_reg(request):
"""Completes the registeration, called by API"""
try:
if not "fido_state" in request.session:
return JsonResponse({'status': 'ERR', "message": "FIDO Status can't be found, please try again"})
data = cbor.decode(request.body)
client_data = CollectedClientData(data['clientDataJSON'])
att_obj = AttestationObject((data['attestationObject']))
server = getServer()
auth_data = server.register_complete(
request.session.pop('fido_state'),
request.session['fido_state'],
client_data,
att_obj
)
@@ -81,7 +79,7 @@ def complete_reg(request):
client.captureException()
except:
pass
return JsonResponse({'status': 'ERR', "message": "Error on server, please try again later"})
return HttpResponse(simplejson.dumps({'status': 'ERR', "message": "Error on server, please try again later"}))
def start(request):

View File

@@ -7,11 +7,10 @@ from django.template.context_processors import csrf
from .models import *
import user_agents
from django.utils import timezone
from django.urls import reverse
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
x=''.join(random.choice(chars) for _ in range(size))
if not User_Keys.objects.filter(properties__icontains='"key": "%s"'%x).exists(): return x
if not User_Keys.objects.filter(properties__shas="$.key="+x).exists(): return x
else: return id_generator(size,chars)
def getUserAgent(request):
@@ -58,13 +57,12 @@ def getCookie(request):
def add(request):
context=csrf(request)
if request.method=="GET":
context.update({"username":request.GET.get('u',''),"key":request.GET.get('k','')})
return render(request,"TrustedDevices/Add.html",context)
else:
key=request.POST["key"].replace("-","").replace(" ","").upper()
context["username"] = request.POST["username"]
context["key"] = request.POST["key"]
trusted_keys=User_Keys.objects.filter(username=request.POST["username"],properties__icontains='"key": "%s"'%key)
trusted_keys=User_Keys.objects.filter(username=request.POST["username"],properties__has="$.key="+key)
cookie=False
if trusted_keys.exists():
tk=trusted_keys[0]
@@ -99,7 +97,7 @@ def start(request):
request.session["td_id"]=td.id
try:
if td==None: td=User_Keys.objects.get(id=request.session["td_id"])
context={"key":td.properties["key"],"url":request.scheme+"://"+request.get_host() + reverse('add_trusted_device')}
context={"key":td.properties["key"]}
except:
del request.session["td_id"]
return start(request)
@@ -126,14 +124,12 @@ def verify(request):
json= jwt.decode(request.COOKIES.get('deviceid'),settings.SECRET_KEY)
if json["username"].lower()== request.session['base_username'].lower():
try:
uk = User_Keys.objects.get(username=request.POST["username"].lower(), properties__icontains='"key": "%s"'%json["key"])
uk = User_Keys.objects.get(username=request.POST["username"].lower(), properties__has="$.key=" + json["key"])
if uk.enabled and uk.properties["status"] == "trusted":
uk.last_used=timezone.now()
uk.save()
request.session["mfa"] = {"verified": True, "method": "Trusted Device","id":uk.id}
return True
except:
import traceback
print(traceback.format_exc())
return False
return False

View File

@@ -1,4 +1,4 @@
from django.apps import AppConfig
class myAppNameConfig(AppConfig):
name = 'mfa'
verbose_name = 'Django MFA2'
verbose_name = 'A Much Better Name'

View File

@@ -2,14 +2,7 @@
from __future__ import unicode_literals
from django.db import models, migrations
try:
from django.db.models import JSONField
except ImportError:
try:
from jsonfield.fields import JSONField
except ImportError:
raise ImportError("Can't find a JSONField implementation, please install jsonfield if django < 4.0")
import jsonfield.fields
def modify_json(apps, schema_editor):
@@ -31,7 +24,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='user_keys',
name='properties',
field=JSONField(null=True),
field=jsonfield.fields.JSONField(null=True),
),
migrations.RunPython(modify_json)
]

View File

@@ -1,12 +1,5 @@
from django.db import models
try:
from django.db.models import JSONField
except ModuleNotFoundError:
try:
from jsonfield import JSONField
except ModuleNotFoundError:
raise ModuleNotFoundError("Can't find a JSONField implementation, please install jsonfield if django < 4.0")
from jsonfield import JSONField
from jose import jwt
from django.conf import settings
#from jsonLookup import shasLookup, hasLookup

View File

@@ -116,7 +116,7 @@
</tr>
{% endif %}
{% else %}
<tr><td colspan="7" align="center">You don't have any keys yet.</td> </tr>
<tr><td colspan="7" align="center">You didn't have any keys yet.</td> </tr>
{% endif %}
</table>
</div>

View File

@@ -1,7 +1,5 @@
{% extends "base.html" %}
{% load static %}
{% block head %}
<script src="{% static 'mfa/js/qrious.min.js' %}" type="text/javascript"></script>
<style>
#two-factor-steps {
border: 1px solid #ccc;
@@ -14,12 +12,6 @@
</style>
<script type="text/javascript">
$(document).ready(function (){
var qr = new QRious({
element: document.getElementById('qr'),
value: "{{ url }}?u={{ request.user.username }}&k={{ key }}"
});
})
function sendEmail() {
$("#modal-title").html("Send Link")
$("#modal-body").html("Sending Email, Please wait....");
@@ -90,52 +82,21 @@
{% if not_allowed %}
<div class="alert alert-danger">You can't add any more devices, you need to remove previously trusted devices first.</div>
{% else %}
<p style="color: green">Allow access from mobile phone and tables.</p><br/>
<br/>
</div>
<div class="row">
<h5>Steps:</h5>
</div>
<div class="row">
<div class="col-md-6">
<h5>Using Camera</h5>
<ol>
<p style="color: green">Allow access from mobile phone and tables.</p>
<h5>Steps:</h5>
<ol>
<li>Using your mobile/table, open Chrome/Firefox.</li>
<li>Scan the following barcode <br/>
<img id="qr"/> <br/>
</li>
<li>Confirm the consent and submit form.</li>
</ol>
</div>
<div class="col-md-6">
<h5>Manual</h5>
<ol>
<li>Using your mobile/table, open Chrome/Firefox.</li>
<li>Go to <b>{{ url }}</b>&nbsp;&nbsp;</li>
<li>Go to <b>{{ HOST }}{{ BASE_URL }}devices/add</b>&nbsp;&nbsp;<a href="javascript:void(0)" onclick="sendEmail()" title="Send to my email"><i class="fas fa-paper-plane"></i></a></li>
<li>Enter your username & following 6 digits<br/>
<span style="font-size: 16px;font-weight: bold; margin-left: 50px">{{ key|slice:":3" }} - {{ key|slice:"3:" }}</span>
</li>
<li>Confirm the consent and submit form.</li>
</div>
</div>
<div class="row">
<div class="col-md-8 offset-2">
This window will ask to confirm the device.
</div>
</div>
<li>This window will ask to confirm the device.</li>
</ol>
{% endif %}
</div>
</div>
<br/>
<br/>
<br/>
<br/>
</div>
{% include "modal.html" %}
{% include 'mfa_check.html' %}
{% endblock %}

3
mfa/tests.py Normal file
View File

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

View File

@@ -1,109 +0,0 @@
[pytest]
# Searching
python_files = test_*
python_classes = Tests*
python_functions = test_*
env_files =
.env
# do not search for tests in these folders
norecursedirs = .vscode .tox docs example img mfa venv .coverage django_mfa2.egg-info
# Add folder to PYTHONPATH
# requires pytest >= 7.0.0
pythonpath = .
# https://pytest-django.readthedocs.io/en/latest/usage.html
DJANGO_SETTINGS_MODULE = tests.settings
# do not override the debug mode (True/False) set in the django settings module
# https://pytest-django.readthedocs.io/en/latest/usage.html#additional-pytest-ini-settings
django_debug_mode = keep
#
# set env variables
# https://tech.serhatteker.com/post/2020-02/test-env-vars-in-python/
# https://github.com/pytest-dev/pytest-env
; env =
; KEY=value
addopts =
# verbose
-v
# more verbosity
# -vv
# Don't show warnings
# -p no:warnings
# generates coverage report
# note that enabling pytest coverage will cause debugging pytest to fail on pycharm
# add the --no-cov to the pytest configuration on pycharm to allow for debugging pytest
--cov=./mfa
# surpress generating converage if one or more tests failed
; --no-cov-on-fail
# do not run migrations => faster test initialization
# --nomigrations
# Show hypthesis statistics whereever hypothesis was used
# ignore these tests/files when looking for tests
#--ignore=
# black
# --black
--hypothesis-show-statistics
# Add --reuse-db if you want to speed up tests by reusing the database between test runs.
#--reuse-db
# Define additional pytest markers so that using them in test will not trigger warnings
# To show the help line use: % pytest --marker
# To run pytest on a specifc marker use: pytest -m mark
# to run pytestt on several markers use quotation and logic operators as in:
# pytest -m "mark1 and mark2"
# pytest -m "mark1 or mark2"
# pytest -m "mark1 and not mark2"
markers =
API: tests of server api functions whether it is exposed as REST API or otherwise
BLACK_BOX: Black Box tests
WHITE_BOX: White Box tests
ENVIRONMENT: tests for the environment
CONFIGURATION: tests related configurations
LOGGING: tests related to logging
UNIT: Unit tests
INTEGRATION: Integration testing
UTILS: tests for utilities
FOCUS: tests under the microscope... under the spotlight... in focus
FUNC: functional teesting
REGRESSION: tests for fixed bugs
DJANGO: tests related to DJANGO
HTTP_REQUEST: tests of functions that handles HTTP REQUESTS
HTTP_GET: tests of functions that handles HTTP_GET_REQUESTS
HTTP_POST: tests of functions that handles HTTP_POST_REQUESTS
AUTH: tests related to user authentication
SQL_DB: tests related to the sql database
CLI: tests related to flask-cli
SERVER: tests for the server
API_V1: API related tests
PRIVILEGED_USER: tests for privileged users
NON_PRIVILEGED_USER: tests for non-privileged users
PERMISSIONS: tests related to permissions
ANNONYMOUS_USER: tests for non-authenticated users
AUTHENTICATED_USER: tests for authenticated users
ENDPOINTS: tests for endpoints (API nodes)
SERIALIZERS: tests for serializers
VIEWS: tests for DRF viewsets
FILTERS: tests for DRF filters
MODELS: tests for models
VALIDATORS: tests for validators
ERROR_HANDLING: tests for error handling
SECURITY: tests for security

View File

@@ -6,5 +6,5 @@ python-u2flib-server
ua-parser
user-agents
python-jose
fido2==1.1.2
fido2 == 1.0.0
jsonLookup

View File

@@ -1,10 +0,0 @@
tox
pytest>=7.0.0
pytest-xdist
pytest-cov # Test coverage
pytest-dotenv # plugin to load environment from .env file
pytest-env # plugin to allow passing environment variable to pytest environmentt
pytest-mock # plugin that provides a mocker fixture which is a thin-wrapper around the patching API provided by the mock package
hypothesis # plugin that helps in automatize generating random values instead of static values in pyttests
pytest-django # pytest support for Django
validators # a package containing several validator functions

View File

@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup(
name='django-mfa2',
version='2.8.0',
version='2.5.0',
description='Allows user to add 2FA to their accounts',
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
@@ -14,23 +14,24 @@ setup(
url = 'https://github.com/mkalioby/django-mfa2/',
download_url='https://github.com/mkalioby/django-mfa2/',
license='MIT',
packages=find_packages(exclude=("tests",)),
packages=find_packages(),
install_requires=[
'django >= 2.0',
'jsonfield',
'simplejson',
'pyotp',
'python-u2flib-server',
'ua-parser',
'user-agents',
'python-jose',
'fido2 == 1.1.2',
'fido2 == 1.0.0',
'jsonLookup'
],
python_requires=">=3.5",
include_package_data=True,
zip_safe=False, # because we're including static files
classifiers=[
"Development Status :: 5 - Production/Stable",
#"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 2.0",
@@ -40,7 +41,6 @@ setup(
"Framework :: Django :: 3.1",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
@@ -51,7 +51,6 @@ setup(
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries :: Python Modules",
]
)

View File

View File

@@ -1,19 +0,0 @@
import pytest
# @pytest.fixture
# def api_request(rf):
# request = rf.get('/url')
# # Modify the request object as needed (e.g., set user, add data)
# return request
# @pytest.fixture
# def create_test_model(db):
# def make_model(**kwargs):
# return MyModel.objects.create(**kwargs)
# return make_model
@pytest.fixture
def authenticated_user(client, django_user_model):
user = django_user_model.objects.create_user(username='test', password='123')
client.login(username='test', password='123')
return user

View File

@@ -1,51 +0,0 @@
import os
SECRET_KEY = 'fake-key-for-testing'
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'mfa'
]
ROOT_URLCONF="mfa.urls"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR ,'mfa','templates' ),
os.path.join(BASE_DIR ,'tests','templates' )
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
MFA_UNALLOWED_METHODS = []

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
{% endblock %}
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View File

@@ -1,29 +0,0 @@
import pytest
from django.urls import reverse
@pytest.mark.VIEWS
@pytest.mark.DJANGO
@pytest.mark.ANNONYMOUS_USER
@pytest.mark.django_db
def test_index_unauthenticated(client):
url = reverse("mfa_home")
response = client.get(url)
assert response is not None
assert response.status_code == 302
assert response.url=="/accounts/login/?next=/"
@pytest.mark.VIEWS
@pytest.mark.AUTHENTICATED_USER
@pytest.mark.django_db
def test_index_authenticated(client, authenticated_user):
url = reverse("mfa_home")
response = client.get(url)
assert response is not None
assert response.status_code == 200
assert isinstance(response.templates, list)
assert len(response.templates) == 4
for template in response.templates:
assert template.name in ["modal.html", "base.html", "mfa_base.html", "MFA.html"]

52
tox.ini
View File

@@ -1,52 +0,0 @@
# Tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
#
# See also https://tox.readthedocs.io/en/latest/config.html for more
# configuration options.
[tox]
# Choose your Python versions. They have to be available
# on the system the tests are run on.
# comma separated
envlist =
python{3.6,3.7,3.8,3.9}-django2.2
python{3.6,3.7,3.8,3.9}-django3
python{3.8,3.9,3.10,3.11}-django4
python{3.8,3.9,3.10,3.11}-django5
# Tell tox to not require a setup.py file
;skipsdist = True
isolated_build = True
[testenv]
# https://tox.wiki/en/latest/example/basic.html#using-a-different-default-pypi-url
;setenv =
; PIP_INDEX_URL = https://pypi.my-alternative-index.org
# https://tech.serhatteker.com/post/2020-02/test-env-vars-in-python/
;setenv =
; NAME=value
# https://tox.wiki/en/latest/example/basic.html#passing-down-environment-variables
# passenv = ENV_VAR_NAME
# https://tox.wiki/en/latest/example/pytest.html#extended-example-change-dir-before-test-and-use-per-virtualenv-tempdir
;changedir = tests
deps =
django22: django>=2.2,<2.3
django32: django>=3.2,<3.3
django40: django>=4.0,<4.1
django41: django>=4.1,<4.2
django42: django>=4.2,<4.3
django50: django==5.0
-rrequirements.txt
-rrequirements_testing.txt
# https://tox.wiki/en/latest/example/basic.html#ignoring-a-command-exit-code
commands =
; pytest --junitxml=report.xml
pytest {posargs}