start package
This commit is contained in:
parent
de5e37d266
commit
de18189a9d
|
@ -1,2 +1,3 @@
|
|||
*egg-info
|
||||
*.pyc
|
||||
.tox
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
recursive-include secretquestions/templates *
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
SQ_SESSION_KEY = getattr(settings, 'SQ_SESSION_KEY', 'sq_token')
|
||||
SQ_TOKEN_TTL = getattr(settings, 'SQ_TOKEN_TTL', 60*3)
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
import re
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
from .views import SecretQuestionWizard
|
||||
from .conf import SQ_SESSION_KEY, SQ_TOKEN_TTL
|
||||
|
||||
def secret_questions_required(view, ttl=SQ_TOKEN_TTL):
|
||||
def _wrapped(request, *args, **kwargs):
|
||||
session_token, url, date = request.session.get(SQ_SESSION_KEY,
|
||||
(None, None, datetime.now()))
|
||||
get_token = request.GET.get(SQ_SESSION_KEY, None)
|
||||
date_max = date + timedelta(seconds=ttl)
|
||||
|
||||
if session_token is None or get_token is None:
|
||||
wiz = SecretQuestionWizard(request)
|
||||
return wiz(request, *args, **kwargs)
|
||||
|
||||
if date_max < datetime.now() or \
|
||||
not request.get_full_path().startswith(url):
|
||||
if request.method == "POST":
|
||||
messages.error(request, _("Your modifications were canceled."))
|
||||
url = request.get_full_path()
|
||||
clean_url = re.sub("(.*)%s=[a..z0..9]*(.*)" % SQ_SESSION_KEY, "\\1", url)
|
||||
return redirect(clean_url)
|
||||
|
||||
if session_token == get_token:
|
||||
return view(request, *args, **kwargs)
|
||||
|
||||
raise Exception('SQ')
|
||||
|
||||
return _wrapped
|
|
@ -5,7 +5,9 @@ from django import forms
|
|||
from django.forms.models import modelformset_factory, ModelForm
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .models import Answer, crypt_answer
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from .models import Answer, crypt_answer, check_answer
|
||||
|
||||
|
||||
MAX_SECRET_QUESTIONS = getattr(settings, 'MAX_SECRET_QUESTIONS', 3)
|
||||
|
@ -62,3 +64,23 @@ class AnswerFormSet(_FreeAnswerFormSet):
|
|||
questions.append(question)
|
||||
|
||||
return super(AnswerFormSet, self).clean()
|
||||
|
||||
|
||||
class UsernameForm(forms.Form):
|
||||
username = forms.CharField()
|
||||
|
||||
def clean_username(self):
|
||||
data = self.cleaned_data['username']
|
||||
try:
|
||||
return User.objects.get(username=data)
|
||||
except User.DoesNotExist:
|
||||
raise forms.ValidationError(_("Username not found"))
|
||||
|
||||
|
||||
class QuestionForm(forms.Form):
|
||||
raw_answer = forms.CharField()
|
||||
|
||||
def clean_raw_answer(self):
|
||||
data = self.cleaned_data['raw_answer']
|
||||
if not check_answer(data, self.answer.secret):
|
||||
raise forms.ValidationError(_("This answer is incorrect."))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from django.db import models
|
||||
|
||||
from django.contrib.auth.models import get_hexdigest
|
||||
from django.contrib.auth.models import get_hexdigest, check_password
|
||||
|
||||
|
||||
def crypt_answer(raw):
|
||||
|
@ -13,6 +13,9 @@ def crypt_answer(raw):
|
|||
return '%s$%s$%s' % (algo, salt, hsh)
|
||||
|
||||
|
||||
def check_answer(raw, crypted):
|
||||
return check_password(raw, crypted)
|
||||
|
||||
class Question(models.Model):
|
||||
text = models.CharField(max_length=255)
|
||||
|
||||
|
@ -21,7 +24,7 @@ class Question(models.Model):
|
|||
|
||||
|
||||
class Answer(models.Model):
|
||||
user = models.ForeignKey('auth.User')
|
||||
user = models.ForeignKey('auth.User', related_name="secret_answers")
|
||||
question = models.ForeignKey('secretquestions.Question')
|
||||
secret = models.CharField(max_length=255)
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.user %}
|
||||
<p>{% trans "Step" %} {{ step }}/{{ step_count }}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
{% if form.answer %}
|
||||
{{ form.answer.question }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<table>
|
||||
{{ form }}
|
||||
</table>
|
||||
<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
|
||||
{{ previous_fields|safe }}
|
||||
<input type="submit" value="{% trans "Next" %}">
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
from configuration import ConfigurationTest
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.client import Client
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from secretquestions.models import Question, Answer
|
||||
|
||||
|
||||
class ConfigurationTest(TestCase):
|
||||
|
||||
client = Client()
|
||||
username = 'paul'
|
||||
password = 'lemay'
|
||||
|
||||
def setUp(self):
|
||||
self.create_user()
|
||||
self.create_questions()
|
||||
|
||||
def create_user(self):
|
||||
self.user = User.objects.create(username=self.username)
|
||||
self.user.set_password(self.password)
|
||||
self.user.save()
|
||||
|
||||
def create_questions(self):
|
||||
self.question1 = Question.objects.create(text="question1")
|
||||
self.question2 = Question.objects.create(text="question2")
|
||||
self.question3 = Question.objects.create(text="question3")
|
||||
self.questions = (self.question1, self.question2, self.question3)
|
||||
|
||||
def test_access_setup_questions_for_anonymous(self):
|
||||
url = reverse('sq_setup')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual('Location' in response, True)
|
||||
self.assertEqual(settings.LOGIN_URL in response['Location'], True)
|
||||
|
||||
def test_access_setup_questions_for_authenticated(self):
|
||||
self.assertEqual(self.client.login(username=self.username,
|
||||
password=self.password), True)
|
||||
|
||||
url = reverse('sq_setup')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_setting_answer_for_one_question(self):
|
||||
raw_password = 'xxx'
|
||||
self.assertEqual(self.client.login(username=self.username,
|
||||
password=self.password), True)
|
||||
url = reverse('sq_setup')
|
||||
|
||||
data = {
|
||||
'form-TOTAL_FORMS': u'1',
|
||||
'form-INITIAL_FORMS': u'0',
|
||||
'form-MAX_NUM_FORMS': u'',
|
||||
'form-0-question': self.question1.id,
|
||||
'form-0-secret': raw_password,
|
||||
}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
answer = Answer.objects.get(user=self.user, question=self.question1)
|
||||
self.assertNotEqual(answer.secret, raw_password)
|
||||
self.assertNotEqual(answer.secret, '')
|
||||
|
||||
|
||||
def test_setting_empty_answer_for_one_question(self):
|
||||
raw_password = ''
|
||||
self.assertEqual(self.client.login(username=self.username,
|
||||
password=self.password), True)
|
||||
url = reverse('sq_setup')
|
||||
|
||||
data = {
|
||||
'form-TOTAL_FORMS': u'1',
|
||||
'form-INITIAL_FORMS': u'0',
|
||||
'form-MAX_NUM_FORMS': u'',
|
||||
'form-0-question': self.question1.id,
|
||||
'form-0-secret': raw_password,
|
||||
}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
with self.assertRaises(Answer.DoesNotExist):
|
||||
Answer.objects.get(user=self.user, question=self.question1)
|
||||
|
||||
def test_check_reset(self):
|
||||
raw_password = 'xxx'
|
||||
self.test_setting_answer_for_one_question()
|
||||
url = reverse('sq_setup')
|
||||
response = self.client.get(url)
|
||||
self.assertFalse(raw_password in response.content)
|
||||
answer = Answer.objects.get(user=self.user, question=self.question1)
|
||||
self.assertFalse(answer.secret in response.content)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
SECRET_KEY = 'secret'
|
||||
|
||||
ROOT_URLCONF = 'secretquestions.tests.urls'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
}
|
||||
}
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.admin',
|
||||
'registration',
|
||||
'secretquestions',)
|
|
@ -0,0 +1,11 @@
|
|||
from django.conf.urls.defaults import patterns, include
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^admin/(.*)', include(admin.site.urls)),
|
||||
(r'^accounts/', include('registration.urls')),
|
||||
(r'^secret/', include('secretquestions.urls')),
|
||||
)
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.conf.urls.defaults import patterns
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'questions/setup$', 'secretquestions.views.setup_form'),
|
||||
(r'questions/ask$','secretquestions.views.ask_form'),
|
||||
url(r'questions/setup$', 'secretquestions.views.setup_form',
|
||||
name="sq_setup"),
|
||||
)
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
from urlparse import urlparse, parse_qs
|
||||
from urllib import urlencode
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render_to_response, redirect
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib import messages
|
||||
from django.contrib.formtools.wizard import FormWizard
|
||||
|
||||
from .forms import AnswerFormSet
|
||||
from django.middleware.csrf import _get_new_csrf_key
|
||||
|
||||
from .forms import AnswerFormSet, UsernameForm, QuestionForm
|
||||
from .conf import SQ_SESSION_KEY
|
||||
|
||||
@login_required
|
||||
def setup_form(request):
|
||||
|
@ -27,5 +36,56 @@ def setup_form(request):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def ask_form(request):
|
||||
pass
|
||||
class SecretQuestionWizard(FormWizard):
|
||||
__name__ = 'SecretQuestionWizard' # fix for debugtoolbar introspection
|
||||
|
||||
def __init__(self, request):
|
||||
self.user = None
|
||||
self.redirect = request.get_full_path()
|
||||
|
||||
self.step_mapping = {}
|
||||
|
||||
if request.user.is_authenticated():
|
||||
form_list = []
|
||||
else:
|
||||
if request.POST:
|
||||
username = request.POST.get('0-username')
|
||||
try:
|
||||
self.user = User.objects.get(username=username)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
form_list = [UsernameForm, ]
|
||||
|
||||
if self.user:
|
||||
for answer in self.user.secret_answers.all():
|
||||
self.step_mapping[len(form_list)] = answer
|
||||
form_list.append(QuestionForm)
|
||||
|
||||
super(SecretQuestionWizard, self).__init__(form_list)
|
||||
|
||||
def get_form(self, step, data=None):
|
||||
answer = self.step_mapping.get(step, None)
|
||||
form = super(SecretQuestionWizard, self).get_form(step, data)
|
||||
form.user = self.user
|
||||
form.answer = answer
|
||||
return form
|
||||
|
||||
def get_template(self, step):
|
||||
return 'secretquestions/step.html'
|
||||
|
||||
def done(self, request, form_list):
|
||||
|
||||
for form in form_list:
|
||||
if not form.is_valid():
|
||||
return self.redirect
|
||||
|
||||
token = _get_new_csrf_key()
|
||||
path = urlparse(self.redirect).path
|
||||
params = parse_qs(urlparse(self.redirect).query, keep_blank_values=True)
|
||||
params[SQ_SESSION_KEY] = token
|
||||
qs = urlencode(params)
|
||||
url = "%s?%s" % (path, qs)
|
||||
|
||||
request.session[SQ_SESSION_KEY] = (token, path, datetime.now())
|
||||
|
||||
return redirect(url)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
[tox]
|
||||
envlist = django1.3, django1.4
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
django-registration==0.8
|
||||
|
||||
commands =
|
||||
django-admin.py test secretquestions --settings=secretquestions.tests.settings
|
||||
|
||||
[testenv:django1.3]
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
django==1.3
|
||||
|
||||
[testenv:django1.4]
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
django==1.4
|
Reference in New Issue