add tests for the new bitcoin authentication backend

This commit is contained in:
Gonçalo Valério 2020-02-19 17:56:22 +00:00
parent 1eac8809e8
commit fb86a99a58
6 changed files with 155 additions and 20 deletions

View File

@ -7,6 +7,7 @@ History
++++++++++++++
* Add validation for existing addresses on the signup form
* Add rudimentary BitId support
0.0.2 (2020-01-08)
++++++++++++++++++

View File

@ -54,7 +54,7 @@ Add Django-Cryptolock's URL patterns:
]
Add the following settings to your project:
Add the following settings to your project for the Monero Backend:
* ``django_cryptolock.backends.MoneroAddressBackend`` to your
``AUTHENTICATION_BACKENDS``
@ -64,6 +64,9 @@ Add the following settings to your project:
``DJCL_MONERO_WALLET_RPC_USER`` and ``DJCL_MONERO_WALLET_RPC_PASS`` to specify
which wallet RPC should be used.
In case only Bitcoin Backend is used, you just need ``DJCL_MONERO_NETWORK`` with
one of two possible values: ``mainnet`` or ``testnet``.
Finaly create the templates files (``login.html`` and ``signup.html``) under a
``django_cryptolock`` subfolder.

View File

@ -1,9 +1,13 @@
import warnings
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from monerorpc.authproxy import AuthServiceProxy, JSONRPCException
from pybitid import bitid
from .models import Address
from .utils import verify_signature
@ -27,7 +31,9 @@ class MoneroAddressBackend(ModelBackend):
return None
stored_address = (
Address.objects.select_related("user").filter(address=address).first()
Address.objects.select_related("user")
.filter(address=address, network=Address.NETWORK_MONERO)
.first()
)
if not stored_address:
return None
@ -46,11 +52,37 @@ class BitcoinAddressBackend(ModelBackend):
"""Custom Bitcoin-BitId authentication backend."""
def authenticate(
self, request, address=None, challenge=None, signature=None, **kwargs
self, request, address=None, bitid_uri=None, signature=None, **kwargs
):
"""
Validates the provided signature for the given Bitcoin address and challenge.
This method does not rely on any external components, everything is done locally.
"""
pass
network = getattr(settings, "DJCL_BITCOIN_NETWORK", None)
if not network:
warnings.warn(
_("Please configure the bitcoin network in the settings file")
)
is_testnet = True if network == "testnet" else False
if not all([address, bitid_uri, signature]):
return None
stored_address = (
Address.objects.select_related("user")
.filter(address=address, network=Address.NETWORK_BITCOIN)
.first()
)
if not stored_address:
return None
callback_uri = request.build_absolute_uri()
valid_signature = bitid.challenge_valid(
address, signature, bitid_uri, callback_uri, is_testnet
)
if valid_signature:
return stored_address.user
else:
return None

View File

@ -29,11 +29,13 @@ else:
AUTHENTICATION_BACKENDS = [
"django_cryptolock.backends.MoneroAddressBackend",
"django_cryptolock.backends.BitcoinAddressBackend",
"django.contrib.auth.backends.ModelBackend",
]
# Test only default settings
DJCL_MONERO_NETWORK = "stagenet"
DJCL_BITCOIN_NETWORK = "mainnet"
DJCL_MONERO_WALLET_RPC_HOST = "localhost:3030"
DJCL_MONERO_WALLET_RPC_USER = "test"

View File

@ -9,12 +9,29 @@ from model_mommy import mommy
from django_cryptolock.models import Address
VALID_ADDRESS = "46fYuhPAdsxMbEeMg97LhSbFPamdiCw7C6b19VEcZSmV6xboWFZuZQ9MTbj1wLszhUExHi63CMtsWjDTrRDqegZiPVebgYq"
User = get_user_model()
pytestmark = pytest.mark.django_db
DUMMY_CREDS = {"username": "test", "password": "insecure"}
VALID_MONERO_ADDRESS = "46fYuhPAdsxMbEeMg97LhSbFPamdiCw7C6b19VEcZSmV6xboWFZuZQ9MTbj1wLszhUExHi63CMtsWjDTrRDqegZiPVebgYq"
VALID_BITCOIN_ADDRESS = "1N5attoW1FviYGnLmRu9xjaPMKTkWxtUCW"
VALID_BITCOIN_SIG = "H5wI5uqhRCxBpyre2mYkjLxNKPi/TCj9IaHhmfnF8Wn1Pac6gsuYsd2GqTNpy/JFDv3HBSOD75pk2OsGDxE7U4o="
VALID_BITID_URI = "bitid://www.django-cryptolock.test/?x=44d91949c7b2eb20"
EXAMPLE_LOGIN_URL = "https://www.django-cryptolock.test/"
def set_monero_settings(settings):
settings.AUTHENTICATION_BACKENDS = [
"django_cryptolock.backends.MoneroAddressBackend",
"django.contrib.auth.backends.ModelBackend",
]
def set_bitcoin_settings(settings):
settings.AUTHENTICATION_BACKENDS = [
"django_cryptolock.backends.BitcoinAddressBackend",
"django.contrib.auth.backends.ModelBackend",
]
@pytest.fixture
@ -22,29 +39,33 @@ def existing_user():
return User.objects.create_user(**DUMMY_CREDS)
def test_monero_backend_receives_insuficient_data(existing_user):
def test_monero_backend_receives_insuficient_data(settings, existing_user):
set_monero_settings(settings)
user = authenticate(MagicMock(), username="test")
assert user is None
def test_monero_backend_lets_the_next_backend_to_be_used(existing_user):
def test_monero_backend_lets_the_next_backend_to_be_used(settings, existing_user):
set_monero_settings(settings)
user = authenticate(MagicMock(), **DUMMY_CREDS)
assert user is not None
def test_monero_backend_does_not_find_address(existing_user):
def test_monero_backend_does_not_find_address(settings, existing_user):
set_monero_settings(settings)
user = authenticate(
MagicMock(), address=VALID_ADDRESS, challeng="1", signature="somesig"
MagicMock(), address=VALID_MONERO_ADDRESS, challeng="1", signature="somesig"
)
assert user is None
def test_monero_backend_cannot_connect_to_RPC(existing_user):
mommy.make(Address, address=VALID_ADDRESS, user=existing_user)
def test_monero_backend_cannot_connect_to_RPC(settings, existing_user):
set_monero_settings(settings)
mommy.make(Address, address=VALID_MONERO_ADDRESS, user=existing_user)
user = authenticate(
MagicMock(),
address=VALID_ADDRESS,
address=VALID_MONERO_ADDRESS,
challenge="1",
signature="invalid sig",
**DUMMY_CREDS
@ -53,25 +74,101 @@ def test_monero_backend_cannot_connect_to_RPC(existing_user):
assert user is None
def test_monero_backend_invalid_signature(existing_user):
mommy.make(Address, address=VALID_ADDRESS, user=existing_user)
def test_monero_backend_invalid_signature(settings, existing_user):
set_monero_settings(settings)
mommy.make(Address, address=VALID_MONERO_ADDRESS, user=existing_user)
with patch("django_cryptolock.backends.verify_signature") as verify_mock:
verify_mock.return_value = False
user = authenticate(
MagicMock(), address=VALID_ADDRESS, challenge="1", signature="invalid sig"
MagicMock(),
address=VALID_MONERO_ADDRESS,
challenge="1",
signature="invalid sig",
)
assert user is None
def test_monero_backed_valid_signature(existing_user):
mommy.make(Address, address=VALID_ADDRESS, user=existing_user)
def test_monero_backed_valid_signature(settings, existing_user):
set_monero_settings(settings)
mommy.make(Address, address=VALID_MONERO_ADDRESS, user=existing_user)
with patch("django_cryptolock.backends.verify_signature") as verify_mock:
verify_mock.return_value = True
user = authenticate(
MagicMock(), address=VALID_ADDRESS, challenge="1", signature="valid sig"
MagicMock(),
address=VALID_MONERO_ADDRESS,
challenge="1",
signature="valid sig",
)
assert user == existing_user
def test_bitcoin_backend_receives_insuficient_data(settings, existing_user):
set_bitcoin_settings(settings)
user = authenticate(MagicMock(), username="test")
assert user is None
def test_bitcoin_backend_lets_the_next_backend_to_be_used(settings, existing_user):
set_bitcoin_settings(settings)
user = authenticate(MagicMock(), **DUMMY_CREDS)
assert user is not None
def test_bitcoin_backend_does_not_find_address(settings, existing_user):
set_bitcoin_settings(settings)
user = authenticate(
MagicMock(),
address=VALID_BITCOIN_ADDRESS,
bitid_uri="bitid://something",
signature="somesig",
)
assert user is None
def test_bitcoin_backend_invalid_signature(settings, existing_user):
set_bitcoin_settings(settings)
mommy.make(
Address,
address=VALID_BITCOIN_ADDRESS,
network=Address.NETWORK_BITCOIN,
user=existing_user,
)
mock = MagicMock()
mock.build_absolute_uri.return_value = EXAMPLE_LOGIN_URL
user = authenticate(
mock,
address=VALID_BITCOIN_ADDRESS,
bitid_uri=VALID_BITID_URI,
signature="invalid sig",
)
assert user is None
def test_bitcoin_backend_valid_signature(settings, existing_user):
set_bitcoin_settings(settings)
set_bitcoin_settings(settings)
mommy.make(
Address,
address=VALID_BITCOIN_ADDRESS,
network=Address.NETWORK_BITCOIN,
user=existing_user,
)
mock = MagicMock()
mock.build_absolute_uri.return_value = EXAMPLE_LOGIN_URL
user = authenticate(
mock,
address=VALID_BITCOIN_ADDRESS,
bitid_uri=VALID_BITID_URI,
signature=VALID_BITCOIN_SIG,
)
assert user == existing_user

View File

@ -89,7 +89,7 @@ def test_wrong_monero_network_address(settings):
def test_wrong_bitcoin_network_address(settings):
settings.DJCL_MONERO_NETWORK = "testnet"
settings.DJCL_BITCOIN_NETWORK = "testnet"
addr = mommy.make(
Address, address=VALID_BITCOIN_MAINNET_ADDR, network=Address.NETWORK_BITCOIN
)