понедельник, 12 мая 2014 г.

Опрос (ч.6)

В части 5 нашего Django урока по созданию опроса мы заставили работать нашу форму голосования. В админке можно создавать опросы, и они будут отображаться. В этой части мы обсудим вопросы безопасности созданного приложения.


1. Попробуйте перейти на детальную страницу опроса, снятого с публикации. Система спокойно позволяет вам это. Давайте сделаем так, что при попытке обращения пользователя по url к опросу, снятого с публикации, он видел сообщение: "Извините, опрос снят с публикации".
Для этого отредактируем файл шаблона: polls/templates/polls/detail.html:
{% if question.is_active %}

    <h1>{{ question.title }}</h1>
    {% if error_message %}
        <p><strong>{{ error_message }}</strong></p>
    {% endif %}

    <form action="{% url 'polls:vote' question.id %}" method="post">
        {% csrf_token %}
        {% for answer in question.answer_set.all %}
            <input type="radio" name="answer" id="answer{{ forloop.counter }}" value="{{ answer.id }}" />
            <label for="answer{{ forloop.counter }}">{{ answer.answer }}</label><br />
        {% endfor %}
        <input type="submit" value="Голосовать" />
    </form>

{% else %}
    <p>Извините, опрос снят с публикации.</p>
{% endif %}

2. На странице просмотра опроса пользователь может указать id несуществующего ответа, что породит ошибку DoesNotExist at /polls/1/vote/ Answer matching query does not exist.
Чтобы ее избежать, отредактируем polls/views.py:
def vote(request, poll_id):

    question = get_object_or_404(Question, pk=poll_id)
    
    if request.POST.get('answer'):
        try:
            selected_answer = question.answer_set.get(pk=request.POST['answer'])
        except(Answer.DoesNotExist):
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "Указан недопустимый ответ",
            })
        selected_answer.votes += 1
        selected_answer.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
    else:
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "Вы не выбрали ответ.",
        })

Теперь, если хакер в форме укажет id ответа другого опроса и отправит форму, наше приложение не увеличит счетчик голосов в другом опросе и вернет ошибку: "Указан недопустимый ответ".

3. Если хакер в форме голосования вместо числового id ответа укажет русские символы, например:
<input type="radio" value="ЭТО_ХАКЕР" id="answer1" name="answer">

то это вызовет ошибку:
UnicodeEncodeError at /polls/2/vote/ 'decimal' codec can't encode characters in position 0-24: invalid decimal Unicode string

Если пользователь укажет вместо числового id знак минуса "-", то это породит ошибку:
ValueError at /polls/2/vote/ invalid literal for int() with base 10: '-'

Теперь, зная типы возникающих ошибок, добавим их в блок except в файле polls/views.py:
def vote(request, poll_id):

    question = get_object_or_404(Question, pk=poll_id)
    
    if request.POST.get('answer'):
        try:
            selected_answer = question.answer_set.get(pk=request.POST['answer'])
        except(Answer.DoesNotExist, UnicodeEncodeError, ValueError):
            # ...

Таким образом, мы подавили ошибки при передаче некорректных данных.

Примечание: По идее, мы не должны заботиться о хороших сообщениях для хакеров, усложняя тем самым логику нашего приложения. Для хакера сойдет сообщение Server Error (500), которое будет если в firstsite/settings.py установить:
DEBUG = False

TEMPLATE_DEBUG = False

ALLOWED_HOSTS = ['127.0.0.1']

4. Тем не менее мы все равно имеем шанс проголосовать, к примеру за опрос id = 2, находясь на странице опроса с id = 1. Для этого нам достаточно изменить у формы атрибут action и изменить id ответа. Пример:

<form method="post" action="/polls/3/vote/">
    <input type="radio" value="7" id="answer1" name="answer">
    <label for="answer1">Картошка</label><br>
    <!-- Другие поля формы... -->
</form>


Да, пусть хакер это делает, т. к. это равносильно тому, что он просто откроет открытый опубликованный опрос и там проголосует. Но нехорошо будет если хакер сможет таким образом увеличивать число голосов по уже закрытым опросам. Чтобы этого не произошло, отредактируем polls/views.py:
def vote(request, poll_id):

    question = get_object_or_404(Question, pk=poll_id)
    if not question.is_active:
        return HttpResponse('Опрос снят с публикации')
    # ...

Таким образом, если кто-то попробует увеличить счетчик снятого с публикации опроса, он получит сообщение "Опрос снят с публикации".

В следующей части урока по Django по созданию опроса мы защитим наши опросы от повторного голосования.

Продолжение - Опрос (ч.7)

Комментариев нет:

Отправить комментарий