Remove ed25519, add pynacl, documentation updates and preparation for PyPI
This commit is contained in:
parent
f000e564f9
commit
fff2d3e244
24 changed files with 404 additions and 82 deletions
14
AUTHORS.md
Normal file
14
AUTHORS.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: Credits
|
||||||
|
---
|
||||||
|
|
||||||
|
Development Lead
|
||||||
|
================
|
||||||
|
|
||||||
|
- Miroslav Shubernetskiy - <https://github.com/miki725>
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
============
|
||||||
|
|
||||||
|
- Keaton Brown - <https://gitlab.com/WolfgangAxel>
|
||||||
|
- Python 2 removal, Django 2.2 upgrade
|
13
AUTHORS.rst
Normal file
13
AUTHORS.rst
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
Development Lead
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Miroslav Shubernetskiy - https://github.com/miki725
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Keaton Brown - https://gitlab.com/WolfgangAxel
|
||||||
|
* Python 2 removal, Django 2.2 upgrade
|
21
CHANGELOG
21
CHANGELOG
|
@ -1,3 +1,24 @@
|
||||||
|
Sat, 14 Sep 2019 02:38:56 -0500
|
||||||
|
Keaton <kii-chan@tutanota.com>
|
||||||
|
Remove ed25519, add pynacl, documentation updates and preparation for PyPI
|
||||||
|
|
||||||
|
- Fixed typos in readme, fixed formatting to be a little nicer.
|
||||||
|
- Converted `tests` "app". Run tests as follows:
|
||||||
|
|
||||||
|
python3 tests/manage.py test
|
||||||
|
|
||||||
|
- `python-ed25519` lists on its GitHub page that it is depreciated, and that
|
||||||
|
`pynacl` is the recommended alternative. As such, I've converted all calls
|
||||||
|
to the ed25519 library into pynacl calls. All the tests pass, so we *should*
|
||||||
|
be good...
|
||||||
|
- Sidenote, I should really get in the habit of making sure tests pass before
|
||||||
|
committing...
|
||||||
|
- Started adding files needed by PyPI. Honestly I'm not sure that everything
|
||||||
|
is 100% necessary, but I'm not really concerning myself with it for this
|
||||||
|
commit. I just want to push the
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
Wed, 04 Sep 2019 21:08:57 -0500
|
Wed, 04 Sep 2019 21:08:57 -0500
|
||||||
Keaton <kii-chan@tutanota.com>
|
Keaton <kii-chan@tutanota.com>
|
||||||
Misc updates
|
Misc updates
|
||||||
|
|
13
HISTORY.md
Normal file
13
HISTORY.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
title: History
|
||||||
|
---
|
||||||
|
|
||||||
|
0.2.0 (2019-??-??)
|
||||||
|
==================
|
||||||
|
|
||||||
|
- First release of rewrite on PyPI.
|
||||||
|
|
||||||
|
0.1.0 (2015-05-20)
|
||||||
|
==================
|
||||||
|
|
||||||
|
- First release on PyPI.
|
14
HISTORY.rst
Normal file
14
HISTORY.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
.. :changelog:
|
||||||
|
|
||||||
|
History
|
||||||
|
-------
|
||||||
|
|
||||||
|
0.2.0 (2019-09-20)
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* First release of rewrite on PyPI.
|
||||||
|
|
||||||
|
0.1.0 (2015-05-20)
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* First release on PyPI.
|
25
LICENSE.md
Normal file
25
LICENSE.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
title: License
|
||||||
|
---
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015, Miroslav Shubernetskiy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -22,7 +22,9 @@ so all credit for this working belongs with them.
|
||||||
|
|
||||||
First step is to install `django-sqrl-2` which is easies to do using pip:
|
First step is to install `django-sqrl-2` which is easies to do using pip:
|
||||||
|
|
||||||
$ python3 -m pip install #django-sqrl-2
|
```
|
||||||
|
$ #python3 -m pip install django-sqrl-2
|
||||||
|
```
|
||||||
|
|
||||||
### Django settings
|
### Django settings
|
||||||
|
|
||||||
|
@ -30,28 +32,34 @@ Once installed there are a few required changes in Django settings:
|
||||||
|
|
||||||
* Make sure that some required Django apps are used:
|
* Make sure that some required Django apps are used:
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
```
|
||||||
...,
|
INSTALLED_APPS = [
|
||||||
'sqrl',
|
...,
|
||||||
'django.contrib.auth',
|
'sqrl',
|
||||||
'django.contrib.sessions',
|
'django.contrib.auth',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.sessions',
|
||||||
]
|
'django.contrib.staticfiles',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
* Make sure that some required Django middleware are used:
|
* Make sure that some required Django middleware are used:
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = [
|
```
|
||||||
...
|
MIDDLEWARE_CLASSES = [
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
...
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
]
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
* Change `AUTHENTICATION_BACKENDS` to use SQRL backend vs Django's
|
* Change `AUTHENTICATION_BACKENDS` to use SQRL backend vs Django's
|
||||||
`ModelBackend` (default):
|
`ModelBackend` (default):
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
```
|
||||||
'sqrl.backends.SQRLModelBackend',
|
AUTHENTICATION_BACKENDS = [
|
||||||
]
|
'sqrl.backends.SQRLModelBackend',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
* If you are using Django admin, following are required:
|
* If you are using Django admin, following are required:
|
||||||
|
|
||||||
|
@ -59,46 +67,55 @@ Once installed there are a few required changes in Django settings:
|
||||||
This allows Django to prioritize `sqrl` templates since `django-sqrl`
|
This allows Django to prioritize `sqrl` templates since `django-sqrl`
|
||||||
overwrites some of them.
|
overwrites some of them.
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
```
|
||||||
...,
|
INSTALLED_APPS = [
|
||||||
'sqrl',
|
...,
|
||||||
'django.contrib.admin',
|
'sqrl',
|
||||||
...
|
'django.contrib.admin',
|
||||||
]
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
* Make sure to add a custom template directory in settings. `django-sqrl`
|
* Make sure to add a custom template directory in settings. `django-sqrl`
|
||||||
extends Django admin's `base.html` which by default causes infinite recursion.
|
extends Django admin's `base.html` which by default causes infinite recursion.
|
||||||
To solve that, simply add a custom template directory which allows `django-sqrl`
|
To solve that, simply add a custom template directory which allows `django-sqrl`
|
||||||
to explicitly extend from `django.contrib.admin` `base.html` template:
|
to explicitly extend from `django.contrib.admin` `base.html` template:
|
||||||
|
|
||||||
import os
|
```
|
||||||
import django
|
import os
|
||||||
TEMPLATE_DIRS = [
|
import django
|
||||||
os.path.dirname(django.__file__),
|
TEMPLATE_DIRS = [
|
||||||
]
|
os.path.dirname(django.__file__),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## URLs
|
## URLs
|
||||||
|
|
||||||
All of SQRL functionality is enabled by adding its URLs to the root URL config:
|
All of SQRL functionality is enabled by adding its URLs to the root URL config:
|
||||||
|
|
||||||
from django.urls import path, include
|
```
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
...
|
...
|
||||||
path('sqrl/', include('sqrl.urls', namespace="sqrl")),
|
path('sqrl/', include('sqrl.urls', namespace="sqrl")),
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
If you use Django admin, the `/admin/sqrl_manage` will be available to manage
|
If you use Django admin, the `/admin/sqrl_manage` endpoint will be available to manage
|
||||||
your site's SQRL identities.
|
your site's SQRL identities.
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
Now that SQRL is installed in your Django project, you can use it on any login
|
Now that SQRL is installed in your Django project, you can use it on any login
|
||||||
page with three simple template tag:
|
page with three simple template tags:
|
||||||
|
|
||||||
{% load sqrl %}
|
```
|
||||||
{% sqrl as sqrl_session %}
|
{% load sqrl %}
|
||||||
{% sqrl_login_dropin sqrl_session [[a named redirect]] %}
|
{% sqrl as sqrl_session %}
|
||||||
|
{% sqrl_login_dropin sqrl_session [[a named redirect]] %}
|
||||||
|
```
|
||||||
|
|
||||||
The [[named redirect]] is the page that should be redirected to after logging
|
The [[named redirect]] is the page that should be redirected to after logging
|
||||||
in. Any name that can be resolved by django's `reverse` function will work (i.e.
|
in. Any name that can be resolved by django's `reverse` function will work (i.e.
|
||||||
|
@ -112,13 +129,15 @@ These three tags will add a simple element to your login page:
|
||||||
If that doesn't suit your fancy, you may build your own template from the
|
If that doesn't suit your fancy, you may build your own template from the
|
||||||
following essential tags:
|
following essential tags:
|
||||||
|
|
||||||
{% load sqrl %}
|
```
|
||||||
{% sqrl as sqrl_session %}
|
{% load sqrl %}
|
||||||
<a href="{{ sqrl_session.sqrl_url }}">
|
{% sqrl as sqrl_session %}
|
||||||
<div id="sqrl-qr" data-sqrl="{{ sqrl_session.sqrl_url }}"></div>
|
<a href="{{ sqrl_session.sqrl_url }}">
|
||||||
</a>
|
<div id="sqrl-qr" data-sqrl="{{ sqrl_session.sqrl_url }}"></div>
|
||||||
<script>SQRL_NEXT="{{ your desired redirect }}"; SQRL_CHECK_URL="{% sqrl_status_url_script_tag sqrl_session %}"</script>
|
</a>
|
||||||
<script type="application/javascript" src="{% static 'sqrl/sqrl.js' %}"></script>
|
<script>SQRL_NEXT="{{ your desired redirect }}"; SQRL_CHECK_URL="{% sqrl_status_url_script_tag sqrl_session %}"</script>
|
||||||
|
<script type="application/javascript" src="{% static 'sqrl/sqrl.js' %}"></script>
|
||||||
|
```
|
||||||
|
|
||||||
## Management Command
|
## Management Command
|
||||||
|
|
||||||
|
@ -126,19 +145,27 @@ SQRL uses server state to keep track of open SQRL transactions in order to
|
||||||
mitigate replay attacks. Since this state will constantly grow if not cleared,
|
mitigate replay attacks. Since this state will constantly grow if not cleared,
|
||||||
`django-sqrl` provides a helper management command to clear expired states:
|
`django-sqrl` provides a helper management command to clear expired states:
|
||||||
|
|
||||||
$ python3 manage.py clearsqrlnuts
|
```
|
||||||
|
$ python3 manage.py clearsqrlnuts
|
||||||
|
```
|
||||||
|
|
||||||
It is recommended to run this command as repeating task. Here is an example
|
It is recommended to run this command as repeating task. Here is an example
|
||||||
configuration for `cron`:
|
configuration for `cron`:
|
||||||
|
|
||||||
*/5 * * * * python manage.py clearsqrlnuts >/dev/null 2>&1
|
```
|
||||||
|
*/5 * * * * python manage.py clearsqrlnuts >/dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
## ~~Testing~~
|
## ~~Testing~~
|
||||||
|
|
||||||
~~To run the tests, you need to install the testing requirements first:~~
|
~~To run the tests, you need to install the testing requirements first:~~
|
||||||
|
|
||||||
$ #make install
|
```
|
||||||
|
$ #make install
|
||||||
|
```
|
||||||
|
|
||||||
~~Then to run the tests, you can use use the Makefile command:~~
|
~~Then to run the tests, you can use use the Makefile command:~~
|
||||||
|
|
||||||
$ #make test
|
```
|
||||||
|
$ #make test
|
||||||
|
```
|
12
requirements-dev.txt
Normal file
12
requirements-dev.txt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-r requirements.txt
|
||||||
|
coverage
|
||||||
|
django-extensions
|
||||||
|
django-sslserver
|
||||||
|
flake8
|
||||||
|
mock
|
||||||
|
Sphinx
|
||||||
|
sphinx-autobuild
|
||||||
|
sphinx-rtd-theme
|
||||||
|
tox
|
||||||
|
watchdog
|
||||||
|
Werkzeug
|
|
@ -1,3 +1,3 @@
|
||||||
Django
|
django
|
||||||
django-braces
|
django-braces
|
||||||
ed25519
|
pynacl
|
||||||
|
|
55
setup.py
Normal file
55
setup.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
from sqrl import __author__, __version__
|
||||||
|
|
||||||
|
|
||||||
|
def read(fname):
|
||||||
|
return open(os.path.join(os.path.dirname(__file__), fname), 'r').read()
|
||||||
|
|
||||||
|
|
||||||
|
authors = read('AUTHORS.md')
|
||||||
|
history = read('HISTORY.md')
|
||||||
|
licence = read('LICENSE.md')
|
||||||
|
readme = read('README.md')
|
||||||
|
|
||||||
|
requirements = read('requirements.txt').splitlines() + [
|
||||||
|
'setuptools',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_requirements = (
|
||||||
|
read('requirements.txt').splitlines()
|
||||||
|
+ read('requirements-dev.txt').splitlines()[1:]
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='django-sqrl-2',
|
||||||
|
version=__version__,
|
||||||
|
author=__author__,
|
||||||
|
description='SQRL authentication support for Django',
|
||||||
|
long_description='\n\n'.join([readme, history, authors, licence]),
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://gitlub.com/WolfgangAxel/django-sqrl-2',
|
||||||
|
license='MIT',
|
||||||
|
packages=find_packages(exclude=['tests', 'tests.*']),
|
||||||
|
install_requires=requirements,
|
||||||
|
test_suite='tests',
|
||||||
|
tests_require=test_requirements,
|
||||||
|
keywords=' '.join([
|
||||||
|
'django-sqrl',
|
||||||
|
'django-sqrl-2',
|
||||||
|
'sqrl'
|
||||||
|
]),
|
||||||
|
classifiers=[
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Development Status :: 2 - Pre-Alpha',
|
||||||
|
],
|
||||||
|
python_requires='>=3.7',
|
||||||
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__author__ = 'Miroslav Shubernetskiy | Keaton Brown'
|
__author__ = 'Keaton Brown'
|
||||||
__email__ = 'miroslav@miki725.com | kii-chan@tutanota.com'
|
__email__ = 'kii-chan@tutanota.com'
|
||||||
__version__ = '0.1.0'
|
__version__ = '0.2.0'
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import ed25519
|
from nacl import signing
|
||||||
|
from nacl.exceptions import BadSignatureError
|
||||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
from django.utils.crypto import constant_time_compare, salted_hmac
|
||||||
|
|
||||||
from .utils import Base64, Encoder
|
from .utils import Base64, Encoder
|
||||||
|
@ -89,6 +90,8 @@ class Ed25519(object):
|
||||||
def __init__(self, public_key, private_key, msg):
|
def __init__(self, public_key, private_key, msg):
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
self.private_key = private_key
|
self.private_key = private_key
|
||||||
|
if private_key and type(private_key) == bytes:
|
||||||
|
self.private_key = private_key[:32]
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
def is_signature_valid(self, other_signature):
|
def is_signature_valid(self, other_signature):
|
||||||
|
@ -101,10 +104,10 @@ class Ed25519(object):
|
||||||
Boolean indicating whether validation has succeeded.
|
Boolean indicating whether validation has succeeded.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
vk = ed25519.VerifyingKey(self.public_key)
|
vk = signing.VerifyKey(self.public_key)
|
||||||
vk.verify(other_signature, self.msg)
|
vk.verify(self.msg, other_signature)
|
||||||
return True
|
return True
|
||||||
except (AssertionError, ed25519.BadSignatureError):
|
except (AssertionError, BadSignatureError) as e:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def sign_data(self):
|
def sign_data(self):
|
||||||
|
@ -116,8 +119,8 @@ class Ed25519(object):
|
||||||
bytes
|
bytes
|
||||||
ed25519 signature
|
ed25519 signature
|
||||||
"""
|
"""
|
||||||
sk = ed25519.SigningKey(self.private_key)
|
sk = signing.SigningKey(self.private_key)
|
||||||
return sk.sign(self.msg)
|
return sk.sign(self.msg).signature
|
||||||
|
|
||||||
|
|
||||||
def generate_randomness(size=32):
|
def generate_randomness(size=32):
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% sqrl as sqrl_session %}
|
{% sqrl as sqrl_session %}
|
||||||
{% sqrl_login_dropin sqrl_session login %}
|
{% sqrl_login_dropin sqrl_session "sqrl:login" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
|
@ -23,5 +23,6 @@ All necessary data should already be in the context.
|
||||||
|
|
||||||
Please note again that this template is for SQRL-exclusive logins.
|
Please note again that this template is for SQRL-exclusive logins.
|
||||||
If you would like to add SQRL login to an existing login page,
|
If you would like to add SQRL login to an existing login page,
|
||||||
you should rather adjust that template as it is probably way more involved.
|
you should rather adjust that template as it is probably way more involved
|
||||||
|
to add that here.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
|
@ -15,7 +15,7 @@ def sqrl(context):
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('sqrl/sqrl-dropin.html')
|
@register.inclusion_tag('sqrl/sqrl-dropin.html')
|
||||||
def sqrl_login_dropin(sqrl_session, redir=""):
|
def sqrl_login_dropin(sqrl_session, redir="sqrl:login"):
|
||||||
"""
|
"""
|
||||||
Creates a drop-in SQRL element in your template pages.
|
Creates a drop-in SQRL element in your template pages.
|
||||||
Add it to your login template to make it SQRL-aware.
|
Add it to your login template to make it SQRL-aware.
|
||||||
|
|
|
@ -4,7 +4,8 @@ from collections import OrderedDict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import ed25519
|
from nacl import signing
|
||||||
|
from nacl.exceptions import BadSignatureError
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from ..crypto import HMAC, Ed25519, generate_randomness
|
from ..crypto import HMAC, Ed25519, generate_randomness
|
||||||
|
@ -111,11 +112,11 @@ class TestEd25519(unittest.TestCase):
|
||||||
b'\x97\x145\x90N[\xb9\xfc\x8e\x8a\x9e\xd2=\xad\x84\xcd\xf1\x93\x06'
|
b'\x97\x145\x90N[\xb9\xfc\x8e\x8a\x9e\xd2=\xad\x84\xcd\xf1\x93\x06'
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('ed25519.SigningKey')
|
@mock.patch('nacl.signing.SigningKey')
|
||||||
def test_sign_data_mock(self, mock_signing_key):
|
def test_sign_data_mock(self, mock_signing_key):
|
||||||
signature = self.sig.sign_data()
|
signature = self.sig.sign_data()
|
||||||
|
|
||||||
self.assertEqual(signature, mock_signing_key.return_value.sign.return_value)
|
self.assertEqual(signature, mock_signing_key.return_value.sign.return_value.signature)
|
||||||
mock_signing_key.assert_called_once_with(self.sig.private_key)
|
mock_signing_key.assert_called_once_with(self.sig.private_key)
|
||||||
mock_signing_key.return_value.sign.assert_called_once_with(self.sig.msg)
|
mock_signing_key.return_value.sign.assert_called_once_with(self.sig.msg)
|
||||||
|
|
||||||
|
@ -125,17 +126,17 @@ class TestEd25519(unittest.TestCase):
|
||||||
self.assertTrue(self.sig.is_signature_valid(signature))
|
self.assertTrue(self.sig.is_signature_valid(signature))
|
||||||
self.assertFalse(self.sig.is_signature_valid(b'a' + signature[:-1]))
|
self.assertFalse(self.sig.is_signature_valid(b'a' + signature[:-1]))
|
||||||
|
|
||||||
@mock.patch('ed25519.VerifyingKey')
|
@mock.patch('nacl.signing.VerifyKey')
|
||||||
def test_is_signature_mock(self, mock_verifying_key):
|
def test_is_signature_mock(self, mock_verifying_key):
|
||||||
is_valid = self.sig.is_signature_valid(mock.sentinel.signature)
|
is_valid = self.sig.is_signature_valid(mock.sentinel.signature)
|
||||||
|
|
||||||
self.assertTrue(is_valid)
|
self.assertTrue(is_valid)
|
||||||
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
||||||
mock_verifying_key.return_value.verify.assert_called_once_with(
|
mock_verifying_key.return_value.verify.assert_called_once_with(
|
||||||
mock.sentinel.signature, self.data
|
self.data, mock.sentinel.signature
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('ed25519.VerifyingKey')
|
@mock.patch('nacl.signing.VerifyKey')
|
||||||
def test_is_signature_mock_assertion_error(self, mock_verifying_key):
|
def test_is_signature_mock_assertion_error(self, mock_verifying_key):
|
||||||
mock_verifying_key.return_value.verify.side_effect = AssertionError
|
mock_verifying_key.return_value.verify.side_effect = AssertionError
|
||||||
|
|
||||||
|
@ -144,19 +145,19 @@ class TestEd25519(unittest.TestCase):
|
||||||
self.assertFalse(is_valid)
|
self.assertFalse(is_valid)
|
||||||
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
||||||
mock_verifying_key.return_value.verify.assert_called_once_with(
|
mock_verifying_key.return_value.verify.assert_called_once_with(
|
||||||
mock.sentinel.signature, self.data
|
self.data, mock.sentinel.signature
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('ed25519.VerifyingKey')
|
@mock.patch('nacl.signing.VerifyKey')
|
||||||
def test_is_signature_mock_bas_signature_error(self, mock_verifying_key):
|
def test_is_signature_mock_bad_signature_error(self, mock_verifying_key):
|
||||||
mock_verifying_key.return_value.verify.side_effect = ed25519.BadSignatureError
|
mock_verifying_key.return_value.verify.side_effect = BadSignatureError
|
||||||
|
|
||||||
is_valid = self.sig.is_signature_valid(mock.sentinel.signature)
|
is_valid = self.sig.is_signature_valid(mock.sentinel.signature)
|
||||||
|
|
||||||
self.assertFalse(is_valid)
|
self.assertFalse(is_valid)
|
||||||
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
mock_verifying_key.assert_called_once_with(self.sig.public_key)
|
||||||
mock_verifying_key.return_value.verify.assert_called_once_with(
|
mock_verifying_key.return_value.verify.assert_called_once_with(
|
||||||
mock.sentinel.signature, self.data
|
self.data, mock.sentinel.signature
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import ed25519
|
from nacl import signing
|
||||||
import mock
|
import mock
|
||||||
from django import forms, test
|
from django import forms, test
|
||||||
from django.contrib.auth import SESSION_KEY, get_user_model
|
from django.contrib.auth import SESSION_KEY, get_user_model
|
||||||
|
@ -19,9 +19,10 @@ TESTING_MODULE = 'sqrl.forms'
|
||||||
|
|
||||||
class TestRequestForm(test.TestCase):
|
class TestRequestForm(test.TestCase):
|
||||||
def get_key_pair(self):
|
def get_key_pair(self):
|
||||||
signing_key, verifying_key = ed25519.create_keypair()
|
signing_key = signing.SigningKey.generate()
|
||||||
signing_key = signing_key.to_bytes()
|
verifying_key = signing_key.verify_key
|
||||||
verifying_key = verifying_key.to_bytes()
|
signing_key = signing_key._signing_key
|
||||||
|
verifying_key = verifying_key._key
|
||||||
|
|
||||||
return signing_key, verifying_key
|
return signing_key, verifying_key
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ class TestSQRLStatusView(test.TestCase):
|
||||||
|
|
||||||
def test_get_success_url_complete_registration(self):
|
def test_get_success_url_complete_registration(self):
|
||||||
self.view.request.GET['url'] = '?next={}'.format(reverse('sqrl:login'))
|
self.view.request.GET['url'] = '?next={}'.format(reverse('sqrl:login'))
|
||||||
self.view.request.user.is_authenticated.return_value = False
|
self.view.request.user.is_authenticated = False
|
||||||
self.view.request.session = {SQRL_IDENTITY_SESSION_KEY: ''}
|
self.view.request.session = {SQRL_IDENTITY_SESSION_KEY: ''}
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -16,7 +16,7 @@ urlpatterns = [
|
||||||
path("auth/", SQRLAuthView.as_view(), name="auth"),
|
path("auth/", SQRLAuthView.as_view(), name="auth"),
|
||||||
path("login/", SQRLLoginView.as_view(), name="login"),
|
path("login/", SQRLLoginView.as_view(), name="login"),
|
||||||
path("manage/", SQRLIdentityManagementView.as_view(), name='manage'),
|
path("manage/", SQRLIdentityManagementView.as_view(), name='manage'),
|
||||||
path("register/",SQRLCompleteRegistrationView.as_view(), name='complete-registration'),
|
path("register/", SQRLCompleteRegistrationView.as_view(), name='complete-registration'),
|
||||||
re_path(r"^status/(?P<transaction>[A-Za-z0-9_-]{43})/$", SQRLStatusView.as_view(), name='status'),
|
re_path(r"^status/(?P<transaction>[A-Za-z0-9_-]{43})/$", SQRLStatusView.as_view(), name='status'),
|
||||||
path('admin/sqrl_manage/', AdminSiteSQRLIdentityManagementView.as_view(), name='admin-sqrl_manage'),
|
path('admin/sqrl_manage/', AdminSiteSQRLIdentityManagementView.as_view(), name='admin-sqrl_manage'),
|
||||||
]
|
]
|
||||||
|
|
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
95
tests/settings.py
Normal file
95
tests/settings.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Bare ``settings.py`` for running tests for rest_framework_bulk
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import django
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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',
|
||||||
|
]
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.dirname(django.__file__),],
|
||||||
|
'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',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'sqrl',
|
||||||
|
'tests',
|
||||||
|
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'sqrl.backends.SQRLModelBackend',
|
||||||
|
)
|
||||||
|
|
||||||
|
SQRL_SERVER_FRIENDLY_NAME = 'Django SQRL Test Site'
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
SECRET_KEY = 'foo'
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'tests.urls'
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'formatters': {
|
||||||
|
'simple': {
|
||||||
|
'format': '%(levelname)s %(message)s'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'simple'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'sqrl': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
'django.request': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': 'ERROR',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
4
tests/static/sqrl/jquery.min.js
vendored
Normal file
4
tests/static/sqrl/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,3 @@
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
|
@ -39,6 +37,12 @@
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
{% block footer %}
|
||||||
|
SQRL Test server. Originally created by <a href="https://github.com/miki725">miki725</a>. Revised by <a href="https://gitlab.com/WolfgangAxel">WolfgangAxel</a>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
1
tests/templates/sqrl.html
Normal file
1
tests/templates/sqrl.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{% extends 'base.html' %}
|
17
tests/urls.py
Normal file
17
tests/urls.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth import urls as auth_urlpatterns
|
||||||
|
from django.contrib.auth.views import LogoutView
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", TemplateView.as_view(template_name='sqrl.html'), name='index'),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("sqrl/", include('sqrl.urls', namespace='sqrl')),
|
||||||
|
path("logout/", LogoutView.as_view(), {'next_page': 'sqrl:login'}, name='logout'),
|
||||||
|
# Doesn't this not work/break things?
|
||||||
|
path("", include(auth_urlpatterns)),
|
||||||
|
]
|
Loading…
Reference in a new issue