add tests for the new bitcoin authentication backend
This commit is contained in:
parent
1eac8809e8
commit
fb86a99a58
|
@ -7,6 +7,7 @@ History
|
|||
++++++++++++++
|
||||
|
||||
* Add validation for existing addresses on the signup form
|
||||
* Add rudimentary BitId support
|
||||
|
||||
0.0.2 (2020-01-08)
|
||||
++++++++++++++++++
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue