Budujemy własny middleware - Blokada ekranu

W dzisiejszym poście, przedstawię sposób jak stworzyć własny middleware, który będzie działał zupełnie jak blokada ekranu znana nam z Windows-a.


Na początku stworzymy okno które będzie wyświetlane po zablokowaniu konta oraz umożliwi nam późniejsze odblokowanie ekranu. Do tego wymagane będzie utwotrzenie najpierw pliku który zmapuje nam url na naszą funkcję.

plik accounts/urls.py

urlpatterns = [
...
path('unblock/', views.blockPageView, name='block_page'),
...
]

Po czym tworzymy również formularz, który będzie obsługiwał nam naszą blokadę ekranu.

plik accounts/forms.py

from django import forms

from .models import Profile


class BlockScreenForm(forms.ModelForm):
pin = forms.IntegerField(label="Your pin", required=True)

class Meta:
model = Profile
fields = ('pin',)

Za obsługę całości, będzie odpowiadała funkcja blockPageView, która pozwoli nam limitować liczbę prób logowania a także ustawi ciasteczko sesyjne latest_move_time z datą ostatniego przeładowania dowolnej strony z serwisu.

plik: accounts/views.py

from datetime import datetime, timedelta
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import BlockScreenForm


def blockPageView(request):
if request.method == "POST" and request.user.is_authenticated:
form = BlockScreenForm(request.POST)

if int(request.user.profile.loginTryNumb) > 3:
form.add_error('pin', "Too many attempts.")
else:
if form.is_valid():
if form.cleaned_data['pin'] == request.user.profile.pin:
request.user.profile.loginTryNumb = 0
request.user.save()

expires = datetime.utcnow() + timedelta(seconds=settings.BLOCKTIME)
request.session['latest_move_time'] = expires.isoformat()
next = request.GET.get('next', '/')
return HttpResponseRedirect(next)
else:
request.user.profile.loginTryNumb += 1
request.user.save()
form.add_error('pin', "Bad pin")
else:
request.session['latest_move_time'] = datetime.utcnow().isoformat()
form = BlockScreenForm()
return render(request, 'account/blockPage.html', {'form': form})

Sam formularz jest dość prosty. Renderuje nam pole pin oraz wyświetla wszelkie komunikaty o błędach.

plik: accounts/templates/account/blockPage.html

{% extends 'base.html' %}

{% block login %}
<form id="login-form" autocomplete="off" class="form" action="" method="POST">
{% csrf_token %}
{{ form.pin }}
{% if form.errors %}
<div class="alert alert-danger">
{{ form.errors }}
</div>
{% endif %}
<input type="hidden" name="next" value="{{ request.path }}">
<div class="form-group">
<input type="submit" name="submit" class="btn btn-primary w-100 text-dark" value="Unblock">
</div>
</form>
{% endblock %}

Następnie już tworzymy nasz docelowy middleware, który będzie odpowiadał za kontrolę żądania użytkownika i miarę potrzeb odpowiednio je modyfikował.

accounts/middleware.py

from datetime import datetime, timedelta

from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin
from preferences import preferences


class BlockScreen(MiddlewareMixin):
"""Class to lock the screen of a web application.

:param get_response:response
"""

def __init__(self, get_response):
self.get_response = get_response

def process_response(self, request, response):
noBlock = [
"/accounts/signup/",
"/accounts/unblock/",
"/accounts/login/",
"/accounts/edit/",
]

if request.user.is_authenticated and request.path not in noBlock:
if not request.user.profile.pin:
messages.add_message(request, messages.INFO, preferences.AccountPreferences.storePin)
return redirect('set-pin')

if request.user.is_staff:
expires = datetime.utcnow() + timedelta(seconds=settings.BLOCKTIMEADMIN)
else:
expires = datetime.utcnow() + timedelta(seconds=settings.BLOCKTIME)

if 'latest_move_time' in request.session and datetime.utcnow() < datetime.fromisoformat(
request.session['latest_move_time']):
request.session['latest_move_time'] = expires.isoformat()
else:
response = redirect('block_page')
response['Location'] += '?next={}'.format(request.path)
return response
return response

W samym programie interesuje nas głównie funkcja process_response która odpowiada za przetworzenie naszego requestu.

  • Na samym poczatku definiujemy jakie ścieżki mają być wykluczone z blokady.
  • Następnie sprawdzamy czy użytkownik ma już nadany pin, jeśli nie to nadajemy go, przekierowując na odpowiednią stronę.
  • Idąc dalej, sprawdzamy jego rolę i w zależności od tego modyfikujemy czas (który jest pobierany z pliku settings.py) potrzebny do wygaśnięcia aplikacji.
  • Na samym końcu sprawdzamy, czy ostatnia akcja na stronie, została wykonana po upływie wyznaczonego czasu. Jeśli nie, przekierowujemy użytkownika na ekran blokady do funkcji blockPageView.

Pozostało nam dodać nasze własne middleware do pliku settings.py, by nasza warstwa pośrednia zaczęła działać.

main/settings.py

MIDDLEWARE = [
...
'accounts.middleware.BlockScreen',
]

Możemy też globalnie włączyć odświerzanie strony w odpowiednich interwałach po przez ustawienie meta http-equiv="refresh"

main/templates/base.html

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="author" content="Kamil Mirończuk">
{% if user.is_authenticated %}
{% if request.user.is_staff %}
<meta http-equiv="refresh" content="{% refreshAdmin %}" id="reload">
{% else %}
<meta http-equiv="refresh" content="{% refresh %}" id="reload">
{% endif %}
{% endif %}

Przechodząc do podsumowania, mimo dość małej ilości materiałów odnośnie tworzenia własnego middleware, myślę że w oparciu o powyższy kod, jesteś w stanie drogi Czytelniku, stworzyć inne przydatne funkcje w swojej aplikacji.

Kamil Mirończuk

I kiedy czegoś gorąco pragniesz, to cały wszechświat sprzyja potajemnie twojemu pragnieniu
~Paulo Coelho

Komentarze

Zostaw komentarz

Twój adres mailowy NIE zostanie opublikowany. W razie otrzymania zapytania, otrzymasz na niego odpowiedź.
Wymagane pola są oznaczone jako *