qrcode: create qrcode connector & certificate management endpoints (#82648)
gitea/passerelle/pipeline/head This commit looks good
Details
gitea/passerelle/pipeline/head This commit looks good
Details
This commit is contained in:
parent
e506facfd6
commit
1e12dae71b
|
@ -0,0 +1,15 @@
|
||||||
|
# passerelle - uniform access to multiple data sources and services
|
||||||
|
# Copyright (C) 2023 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.xs
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-11-02 09:29
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import passerelle.apps.qrcode.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('base', '0030_resourcelog_base_resour_appname_298cbc_idx'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='QRCodeConnector',
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
'id',
|
||||||
|
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
('title', models.CharField(max_length=50, verbose_name='Title')),
|
||||||
|
('slug', models.SlugField(unique=True, verbose_name='Identifier')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
(
|
||||||
|
'key',
|
||||||
|
models.CharField(
|
||||||
|
default=passerelle.apps.qrcode.models.generate_key,
|
||||||
|
max_length=64,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.RegexValidator(
|
||||||
|
'[a-z|0-9]{64}', 'Key should be a 32 bytes hexadecimal string'
|
||||||
|
)
|
||||||
|
],
|
||||||
|
verbose_name='Private Key',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'users',
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name='_qrcode_qrcodeconnector_users_+',
|
||||||
|
related_query_name='+',
|
||||||
|
to='base.ApiUser',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'QR Code',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Certificate',
|
||||||
|
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')),
|
||||||
|
('data', models.JSONField(null=True, verbose_name='Certificate Data')),
|
||||||
|
(
|
||||||
|
'resource',
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='certificates',
|
||||||
|
to='qrcode.qrcodeconnector',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,114 @@
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.dateparse import parse_datetime
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from passerelle.base.models import BaseResource
|
||||||
|
from passerelle.utils.api import endpoint
|
||||||
|
|
||||||
|
CERTIFICATE_SCHEMA = {
|
||||||
|
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'type': 'object',
|
||||||
|
'unflatten': True,
|
||||||
|
'additionalProperties': False,
|
||||||
|
'properties': {
|
||||||
|
'data': {
|
||||||
|
'type': 'object',
|
||||||
|
'title': _('Data to encode in the certificate'),
|
||||||
|
'additionalProperties': {'type': 'string'},
|
||||||
|
},
|
||||||
|
'validity_start': {'type': 'string', 'format': 'date-time'},
|
||||||
|
'validity_end': {'type': 'string', 'format': 'date-time'},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key():
|
||||||
|
key = os.urandom(32)
|
||||||
|
return ''.join(format(x, '02x') for x in key)
|
||||||
|
|
||||||
|
|
||||||
|
UUID_PATTERN = '(?P<uuid>[0-9|a-f]{8}-[0-9|a-f]{4}-[0-9|a-f]{4}-[0-9|a-f]{4}-[0-9a-f]{12})'
|
||||||
|
|
||||||
|
|
||||||
|
class QRCodeConnector(BaseResource):
|
||||||
|
category = _('Misc')
|
||||||
|
|
||||||
|
key = models.CharField(
|
||||||
|
_('Private Key'),
|
||||||
|
max_length=64,
|
||||||
|
default=generate_key,
|
||||||
|
validators=[RegexValidator(r'[a-z|0-9]{64}', 'Key should be a 32 bytes hexadecimal string')],
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('QR Code')
|
||||||
|
|
||||||
|
@endpoint(
|
||||||
|
name='save-certificate',
|
||||||
|
pattern=f'^{UUID_PATTERN}?$',
|
||||||
|
example_pattern='{uuid}',
|
||||||
|
description=_('Create or update a certificate'),
|
||||||
|
post={'request_body': {'schema': {'application/json': CERTIFICATE_SCHEMA}}},
|
||||||
|
parameters={
|
||||||
|
'uuid': {
|
||||||
|
'description': _('Certificate identifier'),
|
||||||
|
'example_value': '12345678-1234-1234-1234-123456789012',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def save_certificate(self, request, uuid=None, post_data=None):
|
||||||
|
validity_start = parse_datetime(post_data['validity_start'])
|
||||||
|
validity_end = parse_datetime(post_data['validity_end'])
|
||||||
|
data = post_data['data']
|
||||||
|
|
||||||
|
if not uuid:
|
||||||
|
certificate = self.certificates.create(
|
||||||
|
data=data,
|
||||||
|
validity_start=validity_start,
|
||||||
|
validity_end=validity_end,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
certificate = get_object_or_404(self.certificates, uuid=uuid)
|
||||||
|
certificate.validity_start = validity_start
|
||||||
|
certificate.validity_end = validity_end
|
||||||
|
certificate.data = data
|
||||||
|
certificate.save()
|
||||||
|
|
||||||
|
return {'data': {'uuid': certificate.uuid}}
|
||||||
|
|
||||||
|
@endpoint(
|
||||||
|
name='get-certificate',
|
||||||
|
description=_('Retrieve an existing certificate'),
|
||||||
|
pattern=f'^{UUID_PATTERN}$',
|
||||||
|
example_pattern='{uuid}',
|
||||||
|
parameters={
|
||||||
|
'uuid': {
|
||||||
|
'description': _('Certificate identifier'),
|
||||||
|
'example_value': '12345678-1234-1234-1234-123456789012',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get_certificate(self, request, uuid):
|
||||||
|
certificate = get_object_or_404(self.certificates, uuid=uuid)
|
||||||
|
return {
|
||||||
|
'err': 0,
|
||||||
|
'data': {
|
||||||
|
'uuid': certificate.uuid,
|
||||||
|
'data': certificate.data,
|
||||||
|
'validity_start': certificate.validity_start.isoformat(),
|
||||||
|
'validity_end': certificate.validity_end.isoformat(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Certificate(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'))
|
||||||
|
data = models.JSONField(null=True, verbose_name='Certificate Data')
|
||||||
|
resource = models.ForeignKey(QRCodeConnector, on_delete=models.CASCADE, related_name='certificates')
|
|
@ -189,6 +189,7 @@ INSTALLED_APPS = (
|
||||||
'passerelle.apps.twilio',
|
'passerelle.apps.twilio',
|
||||||
'passerelle.apps.vivaticket',
|
'passerelle.apps.vivaticket',
|
||||||
'passerelle.apps.sendethic',
|
'passerelle.apps.sendethic',
|
||||||
|
'passerelle.apps.qrcode',
|
||||||
# backoffice templates and static
|
# backoffice templates and static
|
||||||
'gadjo',
|
'gadjo',
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
# passerelle - uniform access to multiple data sources and services
|
||||||
|
# Copyright (C) 2022 Entr'ouvert
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Affero General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from passerelle.apps.qrcode.models import QRCodeConnector
|
||||||
|
from tests.utils import generic_endpoint_url, setup_access_rights
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def connector(db):
|
||||||
|
return setup_access_rights(
|
||||||
|
QRCodeConnector.objects.create(
|
||||||
|
slug='test',
|
||||||
|
key='5e8176e50d45b67e9db875d6006edf3ba805ff4ef4d945327012db4c797be1be',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_certificate(app, connector):
|
||||||
|
endpoint = generic_endpoint_url('qrcode', 'save-certificate', slug=connector.slug)
|
||||||
|
|
||||||
|
result = app.post_json(
|
||||||
|
endpoint,
|
||||||
|
params={
|
||||||
|
'data': {
|
||||||
|
'first_name': 'Georges',
|
||||||
|
'last_name': 'Abitbol',
|
||||||
|
},
|
||||||
|
'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
|
||||||
|
|
||||||
|
certificate_uuid = result.json['data']['uuid']
|
||||||
|
certificate = connector.certificates.get(uuid=certificate_uuid)
|
||||||
|
|
||||||
|
assert certificate.data['first_name'] == 'Georges'
|
||||||
|
assert certificate.data['last_name'] == 'Abitbol'
|
||||||
|
assert certificate.validity_start == datetime.datetime(2022, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
|
||||||
|
assert certificate.validity_end == datetime.datetime(2023, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
result = app.post_json(
|
||||||
|
f'{endpoint}/{certificate_uuid}',
|
||||||
|
params={
|
||||||
|
'data': {
|
||||||
|
'first_name': 'Robert',
|
||||||
|
'last_name': 'Redford',
|
||||||
|
},
|
||||||
|
'validity_start': '2024-01-01T10:00:00+00:00',
|
||||||
|
'validity_end': '2025-01-01T10:00:00+00:00',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
certificate.refresh_from_db()
|
||||||
|
assert certificate.data['first_name'] == 'Robert'
|
||||||
|
assert certificate.data['last_name'] == 'Redford'
|
||||||
|
assert certificate.validity_start == datetime.datetime(2024, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
|
||||||
|
assert certificate.validity_end == datetime.datetime(2025, 1, 1, 10, 0, 0, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_certificate(app, connector):
|
||||||
|
certificate = connector.certificates.create(
|
||||||
|
data={
|
||||||
|
'first_name': 'Georges',
|
||||||
|
'last_name': 'Abitbol',
|
||||||
|
},
|
||||||
|
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-certificate', slug=connector.slug)
|
||||||
|
result = app.get(f'{endpoint}/{certificate.uuid}')
|
||||||
|
|
||||||
|
assert result.json == {
|
||||||
|
'err': 0,
|
||||||
|
'data': {
|
||||||
|
'uuid': str(certificate.uuid),
|
||||||
|
'data': {'first_name': 'Georges', 'last_name': 'Abitbol'},
|
||||||
|
'validity_start': '2022-01-01T10:00:00+00:00',
|
||||||
|
'validity_end': '2023-01-01T10:00:00+00:00',
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue