current active challenges are now stored on the database
This commit is contained in:
parent
0ad15c6277
commit
045f79f867
|
@ -1,3 +1,5 @@
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
@ -7,7 +9,7 @@ from django.conf import settings
|
||||||
|
|
||||||
from pybitid import bitid
|
from pybitid import bitid
|
||||||
|
|
||||||
from .models import Address
|
from .models import Address, Challenge
|
||||||
from .validators import validate_monero_address, validate_bitcoin_address
|
from .validators import validate_monero_address, validate_bitcoin_address
|
||||||
from .utils import generate_challenge
|
from .utils import generate_challenge
|
||||||
|
|
||||||
|
@ -21,17 +23,22 @@ class ChallengeMixin(forms.Form):
|
||||||
|
|
||||||
challenge = forms.CharField()
|
challenge = forms.CharField()
|
||||||
|
|
||||||
def include_challange(self):
|
def include_challenge(self):
|
||||||
new_challenge = bitid.build_uri(
|
"""Created a new challenge only when no data is provided by user."""
|
||||||
self.request.build_absolute_uri(), generate_challenge()
|
|
||||||
)
|
|
||||||
if not self.data:
|
if not self.data:
|
||||||
self.request.session["current_challenge"] = new_challenge
|
new_challenge = bitid.build_uri(
|
||||||
|
self.request.build_absolute_uri(), Challenge.objects.generate()
|
||||||
|
)
|
||||||
self.initial["challenge"] = new_challenge
|
self.initial["challenge"] = new_challenge
|
||||||
|
|
||||||
def clean_challenge(self):
|
def clean_challenge(self):
|
||||||
challenge = self.cleaned_data.get("challenge")
|
challenge_uri = urlparse(self.cleaned_data.get("challenge"))
|
||||||
if not challenge or challenge != self.request.session.get("current_challenge"):
|
query = parse_qs(challenge_uri.query)
|
||||||
|
if not query.get("x"):
|
||||||
|
raise forms.ValidationError(_("Invalid or outdated challenge"))
|
||||||
|
|
||||||
|
challenge = query["x"][0]
|
||||||
|
if not challenge or not Challenge.objects.is_active(challenge):
|
||||||
raise forms.ValidationError(_("Invalid or outdated challenge"))
|
raise forms.ValidationError(_("Invalid or outdated challenge"))
|
||||||
|
|
||||||
return challenge
|
return challenge
|
||||||
|
@ -54,7 +61,7 @@ class SimpleLoginForm(ChallengeMixin, forms.Form):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.request = request
|
self.request = request
|
||||||
self.user_cache = None
|
self.user_cache = None
|
||||||
self.include_challange()
|
self.include_challenge()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
address = self.cleaned_data.get("address")
|
address = self.cleaned_data.get("address")
|
||||||
|
@ -99,7 +106,7 @@ class SimpleSignUpForm(ChallengeMixin, forms.Form):
|
||||||
must be created."""
|
must be created."""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.request = request
|
self.request = request
|
||||||
self.include_challange()
|
self.include_challenge()
|
||||||
self.network = None
|
self.network = None
|
||||||
|
|
||||||
def clean_address(self):
|
def clean_address(self):
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.db.models.manager import Manager
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from .utils import generate_challenge
|
||||||
|
|
||||||
|
|
||||||
|
class ChallengeManager(Manager):
|
||||||
|
"""Provides methods to easily create and verify challenges."""
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
token = generate_challenge()
|
||||||
|
age = getattr(settings, "DJCL_CHALLENGE_EXPIRATION", 10)
|
||||||
|
expiry_date = timezone.now() + timedelta(minutes=age)
|
||||||
|
return self.create(challenge=token, expires=expiry_date)
|
||||||
|
|
||||||
|
def is_active(self, challenge):
|
||||||
|
"""Returns True if the challenge can be used. Otherwise False."""
|
||||||
|
now = timezone.now()
|
||||||
|
return self.filter(challenge=challenge, expires__gte=now).exists()
|
||||||
|
|
||||||
|
def invalidate(self, challenge):
|
||||||
|
"""Removes the provided challenge if it exists."""
|
||||||
|
self.filter(challenge=challenge).delete()
|
||||||
|
|
||||||
|
def clean_expired(self):
|
||||||
|
"""Delete all expired challenges. Returns nº of entries removed."""
|
||||||
|
now = timezone.now()
|
||||||
|
del_summary = self.filter(expires__lt=now).delete()
|
||||||
|
return del_summary[0]
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.2.5 on 2020-05-12 11:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
import model_utils.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('django_cryptolock', '0002_auto_20200218_1312'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Challenge',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||||
|
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||||
|
('challenge', models.CharField(max_length=150)),
|
||||||
|
('expires', models.DateTimeField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Challenge',
|
||||||
|
'verbose_name_plural': 'Challenges',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError
|
||||||
from model_utils.models import TimeStampedModel
|
from model_utils.models import TimeStampedModel
|
||||||
|
|
||||||
from .validators import validate_monero_address, validate_bitcoin_address
|
from .validators import validate_monero_address, validate_bitcoin_address
|
||||||
|
from .managers import ChallengeManager
|
||||||
|
|
||||||
|
|
||||||
class Address(TimeStampedModel):
|
class Address(TimeStampedModel):
|
||||||
|
@ -39,3 +40,22 @@ class Address(TimeStampedModel):
|
||||||
validate_bitcoin_address(self.address)
|
validate_bitcoin_address(self.address)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
raise ValidationError(_("Invalid address for the given network"))
|
raise ValidationError(_("Invalid address for the given network"))
|
||||||
|
|
||||||
|
|
||||||
|
class Challenge(TimeStampedModel):
|
||||||
|
"""Challenges provided to users for authentication purposes."""
|
||||||
|
|
||||||
|
challenge = models.CharField(max_length=150)
|
||||||
|
expires = models.DateTimeField(null=False)
|
||||||
|
|
||||||
|
objects = ChallengeManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta definition for Challenge."""
|
||||||
|
|
||||||
|
verbose_name = _("Challenge")
|
||||||
|
verbose_name_plural = _("Challenges")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Unicode representation of Challenge."""
|
||||||
|
return self.challenge
|
||||||
|
|
|
@ -10,13 +10,20 @@ from monerorpc.authproxy import JSONRPCException
|
||||||
|
|
||||||
from .forms import SimpleSignUpForm, SimpleLoginForm
|
from .forms import SimpleSignUpForm, SimpleLoginForm
|
||||||
from .utils import verify_monero_signature, verify_bitcoin_signature
|
from .utils import verify_monero_signature, verify_bitcoin_signature
|
||||||
from .models import Address
|
from .models import Address, Challenge
|
||||||
|
|
||||||
|
|
||||||
class CryptoLockLoginView(LoginView):
|
class CryptoLockLoginView(LoginView):
|
||||||
template_name = "django_cryptolock/login.html"
|
template_name = "django_cryptolock/login.html"
|
||||||
form_class = SimpleLoginForm
|
form_class = SimpleLoginForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
response = super().form_valid(form)
|
||||||
|
challenge = form.cleaned_data["challenge"]
|
||||||
|
Challenge.objects.invalidate(challenge)
|
||||||
|
Challenge.objects.clean_expired()
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class CryptoLockSignUpView(FormView):
|
class CryptoLockSignUpView(FormView):
|
||||||
template_name = "django_cryptolock/signup.html"
|
template_name = "django_cryptolock/signup.html"
|
||||||
|
@ -36,10 +43,12 @@ class CryptoLockSignUpView(FormView):
|
||||||
|
|
||||||
username = form.cleaned_data["username"]
|
username = form.cleaned_data["username"]
|
||||||
address = form.cleaned_data["address"]
|
address = form.cleaned_data["address"]
|
||||||
|
challenge = form.cleaned_data["challenge"]
|
||||||
|
|
||||||
if valid_sig:
|
if valid_sig:
|
||||||
user = get_user_model().objects.create(username=username)
|
user = get_user_model().objects.create(username=username)
|
||||||
user.address_set.create(address=address, network=form.network)
|
user.address_set.create(address=address, network=form.network)
|
||||||
|
Challenge.objects.invalidate(challenge)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
else:
|
else:
|
||||||
form._errors["signature"] = ErrorList([_("Invalid signature")])
|
form._errors["signature"] = ErrorList([_("Invalid signature")])
|
||||||
|
|
|
@ -20,6 +20,14 @@ django\_cryptolock.migrations.0002\_auto\_20200218\_1312 module
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
django\_cryptolock.migrations.0003\_challenge module
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: django_cryptolock.migrations.0003_challenge
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -47,6 +47,8 @@ Optional Configuration
|
||||||
default is ``16`` and you should avoid lower values unless you know what you
|
default is ``16`` and you should avoid lower values unless you know what you
|
||||||
are doing.
|
are doing.
|
||||||
|
|
||||||
|
``DJCL_CHALLENGE_EXPIRATION`` can be used to control how long a challenge is
|
||||||
|
valid. The default value is `10` minutes.
|
||||||
|
|
||||||
Using the default forms and views
|
Using the default forms and views
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from model_mommy import mommy
|
from model_mommy import mommy
|
||||||
|
from pybitid import bitid
|
||||||
|
|
||||||
from django_cryptolock.forms import SimpleLoginForm, SimpleSignUpForm
|
from django_cryptolock.forms import SimpleLoginForm, SimpleSignUpForm
|
||||||
from django_cryptolock.models import Address
|
from django_cryptolock.models import Address, Challenge
|
||||||
|
|
||||||
from .helpers import set_monero_settings, set_bitcoin_settings
|
from .helpers import set_monero_settings, set_bitcoin_settings
|
||||||
|
|
||||||
|
@ -14,77 +17,120 @@ pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
VALID_MONERO_ADDRESS = "46fYuhPAdsxMbEeMg97LhSbFPamdiCw7C6b19VEcZSmV6xboWFZuZQ9MTbj1wLszhUExHi63CMtsWjDTrRDqegZiPVebgYq"
|
VALID_MONERO_ADDRESS = "46fYuhPAdsxMbEeMg97LhSbFPamdiCw7C6b19VEcZSmV6xboWFZuZQ9MTbj1wLszhUExHi63CMtsWjDTrRDqegZiPVebgYq"
|
||||||
VALID_BITCOIN_ADDRESS = "1N5attoW1FviYGnLmRu9xjaPMKTkWxtUCW"
|
VALID_BITCOIN_ADDRESS = "1N5attoW1FviYGnLmRu9xjaPMKTkWxtUCW"
|
||||||
|
FUTURE_TIME = timezone.now() + timedelta(minutes=15)
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
def gen_challenge(request, challenge):
|
||||||
|
return bitid.build_uri(request.build_absolute_uri(), challenge)
|
||||||
|
|
||||||
|
|
||||||
def test_simpleloginform_generates_new_challenge():
|
def test_simpleloginform_generates_new_challenge():
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
initial = {}
|
|
||||||
request.session.__setitem__.side_effect = initial.__setitem__
|
|
||||||
request.session.__getitem__.side_effect = initial.__getitem__
|
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
form = SimpleLoginForm(request=request)
|
form = SimpleLoginForm(request=request)
|
||||||
|
challenge = Challenge.objects.first()
|
||||||
assert form.initial.get("challenge")
|
assert form.initial.get("challenge")
|
||||||
assert initial["current_challenge"] == form.initial.get("challenge")
|
assert form.initial.get("challenge") == gen_challenge(request, challenge.challenge)
|
||||||
assert form.initial.get("challenge").startswith("bitid://something")
|
assert form.initial.get("challenge").startswith("bitid://something")
|
||||||
|
|
||||||
|
|
||||||
def test_simpleloginform_generates_no_new_challenge():
|
def test_simpleloginform_generates_no_new_challenge():
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
initial = {}
|
|
||||||
request.session.__setitem__.side_effect = initial.__setitem__
|
|
||||||
request.session.__getitem__.side_effect = initial.__getitem__
|
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
form = SimpleLoginForm(request=request, data={"address": ""})
|
form = SimpleLoginForm(request=request, data={"address": ""})
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
assert not form.initial.get("challenge")
|
assert not form.initial.get("challenge")
|
||||||
assert not initial.get("current_challenge")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_simpleloginform_valid_data(settings):
|
def test_simpleloginform_valid_data(settings):
|
||||||
settings.DJCL_MONERO_NETWORK = "mainnet"
|
settings.DJCL_MONERO_NETWORK = "mainnet"
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
|
request = MagicMock()
|
||||||
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
|
||||||
|
form = SimpleLoginForm(
|
||||||
|
request=request,
|
||||||
|
data={
|
||||||
|
"address": VALID_MONERO_ADDRESS,
|
||||||
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
|
"signature": "some valid signature",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with patch("django_cryptolock.forms.authenticate") as auth_mock:
|
||||||
|
auth_mock.return_value = mommy.make(User)
|
||||||
|
assert form.is_valid()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_simpleloginform_invalid_challenge(settings):
|
||||||
|
settings.DJCL_MONERO_NETWORK = "mainnet"
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
form = SimpleLoginForm(
|
form = SimpleLoginForm(
|
||||||
request=request,
|
request=request,
|
||||||
data={
|
data={
|
||||||
"address": VALID_MONERO_ADDRESS,
|
"address": VALID_MONERO_ADDRESS,
|
||||||
"challenge": "12345678",
|
"challenge": gen_challenge(request, "1234567"),
|
||||||
"signature": "some valid signature",
|
"signature": "some valid signature",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
with patch("django_cryptolock.forms.authenticate") as auth_mock:
|
with patch("django_cryptolock.forms.authenticate") as auth_mock:
|
||||||
auth_mock.return_value = mommy.make(User)
|
auth_mock.return_value = mommy.make(User)
|
||||||
request.session.get.return_value = "12345678"
|
assert not form.is_valid()
|
||||||
assert form.is_valid()
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_simpleloginform_expired_challenge(settings):
|
||||||
|
settings.DJCL_MONERO_NETWORK = "mainnet"
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=timezone.now())
|
||||||
|
request = MagicMock()
|
||||||
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
form = SimpleLoginForm(
|
||||||
|
request=request,
|
||||||
|
data={
|
||||||
|
"address": VALID_MONERO_ADDRESS,
|
||||||
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
|
"signature": "some valid signature",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with patch("django_cryptolock.forms.authenticate") as auth_mock:
|
||||||
|
auth_mock.return_value = mommy.make(User)
|
||||||
|
assert not form.is_valid()
|
||||||
|
|
||||||
|
|
||||||
def test_simplesignupform_generates_new_challenge():
|
def test_simplesignupform_generates_new_challenge():
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
initial = {}
|
|
||||||
request.session.__setitem__.side_effect = initial.__setitem__
|
|
||||||
request.session.__getitem__.side_effect = initial.__getitem__
|
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
form = SimpleSignUpForm(request=request)
|
form = SimpleSignUpForm(request=request)
|
||||||
|
challenge = Challenge.objects.first()
|
||||||
assert form.initial.get("challenge")
|
assert form.initial.get("challenge")
|
||||||
assert initial["current_challenge"] == form.initial.get("challenge")
|
assert form.initial.get("challenge") == gen_challenge(request, challenge.challenge)
|
||||||
assert form.initial.get("challenge").startswith("bitid://something")
|
assert form.initial.get("challenge").startswith("bitid://something")
|
||||||
|
|
||||||
|
|
||||||
def test_simplesignupform_generates_no_new_challenge():
|
def test_simplesignupform_generates_no_new_challenge():
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
initial = {}
|
|
||||||
request.session.__setitem__.side_effect = initial.__setitem__
|
|
||||||
request.session.__getitem__.side_effect = initial.__getitem__
|
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
form = SimpleSignUpForm(request=request, data={"address": ""})
|
form = SimpleSignUpForm(request=request, data={"address": ""})
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
assert not form.initial.get("challenge")
|
assert not form.initial.get("challenge")
|
||||||
assert not initial.get("current_challenge")
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_address_unique(settings):
|
def test_validate_address_unique(settings):
|
||||||
settings.DJCL_MONERO_NETWORK = "mainnet"
|
settings.DJCL_MONERO_NETWORK = "mainnet"
|
||||||
mommy.make(Address, address=VALID_MONERO_ADDRESS)
|
mommy.make(Address, address=VALID_MONERO_ADDRESS)
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
form = SimpleSignUpForm(
|
form = SimpleSignUpForm(
|
||||||
|
@ -92,7 +138,7 @@ def test_validate_address_unique(settings):
|
||||||
data={
|
data={
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"address": VALID_MONERO_ADDRESS,
|
"address": VALID_MONERO_ADDRESS,
|
||||||
"challenge": "12345678",
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
"signature": "some valid signature",
|
"signature": "some valid signature",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -100,17 +146,18 @@ def test_validate_address_unique(settings):
|
||||||
assert "This address already exists" in form.errors["address"]
|
assert "This address already exists" in form.errors["address"]
|
||||||
|
|
||||||
|
|
||||||
def test_simplesignupform_validate_bitcoin_addr(settings):
|
def test_simplesignupform_valid_bitcoin_addr(settings):
|
||||||
set_bitcoin_settings(settings)
|
set_bitcoin_settings(settings)
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
|
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
request.session.get.return_value = "12345678"
|
|
||||||
form = SimpleSignUpForm(
|
form = SimpleSignUpForm(
|
||||||
request=request,
|
request=request,
|
||||||
data={
|
data={
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"address": VALID_BITCOIN_ADDRESS,
|
"address": VALID_BITCOIN_ADDRESS,
|
||||||
"challenge": "12345678",
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
"signature": "some valid signature",
|
"signature": "some valid signature",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -119,23 +166,25 @@ def test_simplesignupform_validate_bitcoin_addr(settings):
|
||||||
|
|
||||||
def test_simplesignupform_valid_monero_addr(settings):
|
def test_simplesignupform_valid_monero_addr(settings):
|
||||||
set_monero_settings(settings)
|
set_monero_settings(settings)
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
|
|
||||||
settings.DJCL_MONERO_NETWORK = "mainnet"
|
settings.DJCL_MONERO_NETWORK = "mainnet"
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
request.session.get.return_value = "12345678"
|
|
||||||
form = SimpleSignUpForm(
|
form = SimpleSignUpForm(
|
||||||
request=request,
|
request=request,
|
||||||
data={
|
data={
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"address": VALID_MONERO_ADDRESS,
|
"address": VALID_MONERO_ADDRESS,
|
||||||
"challenge": "12345678",
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
"signature": "some valid signature",
|
"signature": "some valid signature",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert form.is_valid()
|
assert form.is_valid()
|
||||||
|
|
||||||
|
|
||||||
def test_simplesignupform_validate_invalid_addr():
|
def test_simplesignupform_invalid_addr():
|
||||||
|
mommy.make(Challenge, challenge="12345678", expires=FUTURE_TIME)
|
||||||
request = MagicMock()
|
request = MagicMock()
|
||||||
request.build_absolute_uri.return_value = "http://something/"
|
request.build_absolute_uri.return_value = "http://something/"
|
||||||
form = SimpleSignUpForm(
|
form = SimpleSignUpForm(
|
||||||
|
@ -143,9 +192,17 @@ def test_simplesignupform_validate_invalid_addr():
|
||||||
data={
|
data={
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"address": "bad addr",
|
"address": "bad addr",
|
||||||
"challenge": "12345678",
|
"challenge": gen_challenge(request, "12345678"),
|
||||||
"signature": "some valid signature",
|
"signature": "some valid signature",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
assert "Invalid address" in form.errors["address"]
|
assert "Invalid address" in form.errors["address"]
|
||||||
|
|
||||||
|
|
||||||
|
# def test_simplesignupform_invalid_challenge():
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
# def test_simple_signupform_expired_challenge():
|
||||||
|
# pass
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from model_mommy import mommy
|
||||||
|
|
||||||
|
from django_cryptolock.models import Challenge
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
class TestChallengeManager:
|
||||||
|
@pytest.mark.parametrize("conf", (None, 5, 10, 15, 20, 30, 60))
|
||||||
|
def test_generate_challenge_with_expiration(self, settings, conf):
|
||||||
|
if conf:
|
||||||
|
settings.DJCL_CHALLENGE_EXPIRATION = conf
|
||||||
|
test = timezone.now() + timedelta(minutes=conf + 1)
|
||||||
|
else:
|
||||||
|
test = timezone.now() + timedelta(minutes=11)
|
||||||
|
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
|
challenge = Challenge.objects.generate()
|
||||||
|
assert challenge.challenge
|
||||||
|
assert challenge.expires < test
|
||||||
|
assert Challenge.objects.all().exists()
|
||||||
|
|
||||||
|
def test_is_active_when_expired(self):
|
||||||
|
challenge = mommy.make(Challenge, challenge="1234", expires=timezone.now())
|
||||||
|
assert not Challenge.objects.is_active(challenge=challenge.challenge)
|
||||||
|
|
||||||
|
def test_is_active_when_inexistent(self):
|
||||||
|
assert not Challenge.objects.is_active(challenge="1234")
|
||||||
|
|
||||||
|
def test_is_active(self):
|
||||||
|
challenge = Challenge.objects.generate()
|
||||||
|
assert Challenge.objects.is_active(challenge=challenge.challenge)
|
||||||
|
|
||||||
|
def test_invalidate_existing_challenge(self):
|
||||||
|
challenge = Challenge.objects.generate()
|
||||||
|
Challenge.objects.invalidate(challenge.challenge)
|
||||||
|
assert not Challenge.objects.all().exists()
|
||||||
|
|
||||||
|
def test_invalidate_inexistent_challenge(self):
|
||||||
|
Challenge.objects.invalidate("1234")
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("num", (2, 5, 10, 15))
|
||||||
|
def test_clean_expired_challenges(self, num):
|
||||||
|
mommy.make(Challenge, num, expires=timezone.now())
|
||||||
|
Challenge.objects.generate()
|
||||||
|
deleted = Challenge.objects.clean_expired()
|
||||||
|
assert deleted == num
|
||||||
|
assert Challenge.objects.count() == 1
|
Loading…
Reference in New Issue