qrcode: add qrcode reader management endpoints (#82650)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Corentin Sechet 2023-11-02 13:51:40 +01:00 committed by Benjamin Dauvergne
parent a51c49a865
commit 82e9018865
4 changed files with 246 additions and 0 deletions

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.18 on 2023-11-02 09:31
import uuid
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qrcode', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Reader',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('uuid', models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='UUID')),
('validity_start', models.DateTimeField(verbose_name='Validity Start Date')),
('validity_end', models.DateTimeField(verbose_name='Validity End Date')),
(
'resource',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='readers',
to='qrcode.qrcodeconnector',
),
),
],
),
]

View File

@ -1,12 +1,14 @@
import binascii
import os
import uuid
from datetime import datetime, timezone
from io import BytesIO
from django.core.validators import RegexValidator
from django.db import models
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext_lazy as _
@ -33,6 +35,16 @@ CERTIFICATE_SCHEMA = {
},
}
READER_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'additionalProperties': False,
'properties': {
'validity_start': {'type': 'string', 'format': 'date-time'},
'validity_end': {'type': 'string', 'format': 'date-time'},
},
}
def generate_key():
key = os.urandom(32)
@ -135,6 +147,90 @@ class QRCodeConnector(BaseResource):
qr_code = certificate.generate_qr_code()
return HttpResponse(qr_code, content_type='image/png')
@endpoint(
name='save-reader',
pattern=f'^{UUID_PATTERN}?$',
example_pattern='{uuid}',
description=_('Create or update a qrcode reader'),
post={'request_body': {'schema': {'application/json': READER_SCHEMA}}},
parameters={
'uuid': {
'description': _('QRCode reader identifier'),
'example_value': '12345678-1234-1234-1234-123456789012',
}
},
)
def save_reader(self, request, uuid=None, post_data=None):
validity_start = parse_datetime(post_data['validity_start'])
validity_end = parse_datetime(post_data['validity_end'])
if not uuid:
reader = self.readers.create(
validity_start=validity_start,
validity_end=validity_end,
)
else:
reader = get_object_or_404(self.readers, uuid=uuid)
reader.validity_start = validity_start
reader.validity_end = validity_end
reader.save()
return {
'data': {
'uuid': reader.uuid,
'url': reader.get_url(request),
}
}
@endpoint(
name='get-reader',
description=_('Get informations about a QRCode reader'),
pattern=f'^{UUID_PATTERN}$',
example_pattern='{uuid}',
parameters={
'uuid': {
'description': _('QRCode reader identifier'),
'example_value': '12345678-1234-1234-1234-123456789012',
}
},
)
def get_reader(self, request, uuid):
reader = get_object_or_404(self.readers, uuid=uuid)
return {
'err': 0,
'data': {
'uuid': reader.uuid,
'validity_start': reader.validity_start.isoformat(),
'validity_end': reader.validity_end.isoformat(),
'url': reader.get_url(request),
},
}
@endpoint(
name='open-reader',
description=_('Open a QRCode reader page.'),
pattern=f'^{UUID_PATTERN}$',
example_pattern='{uuid}',
parameters={
'uuid': {
'description': _('QRCode reader identifier'),
'example_value': '12345678-1234-1234-1234-123456789012',
}
},
)
def open_reader(self, request, uuid):
reader = get_object_or_404(self.readers, uuid=uuid)
now = datetime.now(timezone.utc)
return TemplateResponse(
request,
'qrcode/qrcode-reader.html',
context={
'started': now >= reader.validity_start,
'expired': now >= reader.validity_end,
'reader': reader,
},
)
def encode_mime_like(data):
msg = ''
@ -198,3 +294,22 @@ class Certificate(models.Model):
},
)
return request.build_absolute_uri(qrcode_relative_url)
class Reader(models.Model):
uuid = models.UUIDField(verbose_name=_('UUID'), unique=True, default=uuid.uuid4)
validity_start = models.DateTimeField(verbose_name=_('Validity Start Date'))
validity_end = models.DateTimeField(verbose_name=_('Validity End Date'))
resource = models.ForeignKey(QRCodeConnector, on_delete=models.CASCADE, related_name='readers')
def get_url(self, request):
relative_url = reverse(
'generic-endpoint',
kwargs={
'slug': self.resource.slug,
'connector': self.resource.get_connector_slug(),
'endpoint': 'open-reader',
'rest': str(self.uuid),
},
)
return request.build_absolute_uri(relative_url)

View File

@ -0,0 +1,20 @@
{% load i18n %}
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<style>
</style>
<body>
{% if not started %}
{% trans "Reader isn't usable yet." %}
{% elif expired %}
{% trans "Reader has expired." %}
{% else %}
{{ reader.validity_start }}<br />
{{ now }}
{{ reader.uuid }}
{% endif %}
</body>
</html>

View File

@ -120,3 +120,79 @@ def test_get_qrcode(app, connector):
with open('tests/data/qrcode/test-qrcode.png', 'rb') as expected_qrcode:
# just check images are the same. Decoded content is tested javascript-side.
assert response.body == expected_qrcode.read()
def test_save_reader(app, connector):
endpoint = generic_endpoint_url('qrcode', 'save-reader', slug=connector.slug)
result = app.post_json(
endpoint,
params={
'validity_start': '2022-01-01 10:00:00+00:00',
'validity_end': '2023-01-01 10:00:00+00:00',
},
)
assert result.json['err'] == 0
reader_uuid = result.json['data']['uuid']
assert result.json['data']['url'] == f'http://testserver/qrcode/test/open-reader/{reader_uuid}'
reader = connector.readers.get(uuid=reader_uuid)
assert reader.validity_start == datetime.datetime(2022, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
assert reader.validity_end == datetime.datetime(2023, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
result = app.post_json(
f'{endpoint}/{reader_uuid}',
params={
'validity_start': '2024-01-01T10:00:00+00:00',
'validity_end': '2025-01-01T10:00:00+00:00',
},
)
reader.refresh_from_db()
assert reader.validity_start == datetime.datetime(2024, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
assert reader.validity_end == datetime.datetime(2025, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
def test_get_reader(app, connector):
reader = connector.readers.create(
validity_start=datetime.datetime(2022, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc),
validity_end=datetime.datetime(2023, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc),
)
endpoint = generic_endpoint_url('qrcode', 'get-reader', slug=connector.slug)
result = app.get(f'{endpoint}/{reader.uuid}')
assert result.json == {
'err': 0,
'data': {
'uuid': str(reader.uuid),
'validity_start': '2022-01-01T10:00:00+00:00',
'validity_end': '2023-01-01T10:00:00+00:00',
'url': f'http://testserver/qrcode/test/open-reader/{reader.uuid}',
},
}
def test_open_reader(app, connector, freezer):
reader = connector.readers.create(
validity_start=datetime.datetime(2022, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc),
validity_end=datetime.datetime(2023, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc),
)
endpoint = generic_endpoint_url('qrcode', 'open-reader', slug=connector.slug)
freezer.move_to('2022-01-01T09:59:59')
result = app.get(f'{endpoint}/{reader.uuid}')
assert 'Reader isn\'t usable yet' in result.body.decode('utf-8')
freezer.move_to('2022-01-01T10:00:00')
result = app.get(f'{endpoint}/{reader.uuid}')
assert str(reader.uuid) in result.body.decode('utf-8')
freezer.move_to('2023-01-01T10:00:01')
result = app.get(f'{endpoint}/{reader.uuid}')
assert 'Reader has expired.' in result.body.decode('utf-8')