djgentelella 0.3.29__py3-none-any.whl → 0.4.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- djgentelella/__init__.py +1 -1
- djgentelella/admin.py +4 -3
- djgentelella/fields/files.py +27 -1
- djgentelella/firmador_digital/__init__.py +0 -0
- djgentelella/firmador_digital/config/__init__.py +0 -0
- djgentelella/firmador_digital/config/asgi_config.py +32 -0
- djgentelella/firmador_digital/config/asgi_worker.py +5 -0
- djgentelella/firmador_digital/config/websocket_urls.py +4 -0
- djgentelella/firmador_digital/consumers/__init__.py +0 -0
- djgentelella/firmador_digital/consumers/pdf_render.py +8 -0
- djgentelella/firmador_digital/consumers/sign.py +148 -0
- djgentelella/firmador_digital/forms.py +28 -0
- djgentelella/firmador_digital/gunicorn/config_asgi.py +12 -0
- djgentelella/firmador_digital/gunicorn/config_wsgi.py +13 -0
- djgentelella/firmador_digital/models.py +33 -0
- djgentelella/firmador_digital/signvalue_utils.py +41 -0
- djgentelella/firmador_digital/utils.py +154 -0
- djgentelella/firmador_digital/viewsets.py +77 -0
- djgentelella/locale/es/LC_MESSAGES/django.mo +0 -0
- djgentelella/locale/es/LC_MESSAGES/django.po +103 -6
- djgentelella/locale/es/LC_MESSAGES/djangojs.mo +0 -0
- djgentelella/locale/es/LC_MESSAGES/djangojs.po +107 -8
- djgentelella/management/commands/createbasejs.py +3 -0
- djgentelella/management/commands/loaddevstatic.py +18 -0
- djgentelella/migrations/0013_usersignatureconfig.py +25 -0
- djgentelella/serializers/firmador_digital.py +103 -0
- djgentelella/settings.py +4 -2
- djgentelella/static/djgentelella.readonly.vendors.min.css +1 -1
- djgentelella/static/djgentelella.readonly.vendors.min.js +1 -1
- djgentelella/static/djgentelella.vendors.header.min.js +1 -1
- djgentelella/static/djgentelella.vendors.min.css +2 -2
- djgentelella/static/gentelella/css/pdfviewer.css +147 -0
- djgentelella/static/gentelella/images/firmador.ico +0 -0
- djgentelella/static/gentelella/js/base/digital_signature.js +945 -0
- djgentelella/static/gentelella/js/base.js +947 -0
- djgentelella/static/gentelella/js/datatables.js +2 -2
- djgentelella/static/gentelella/js/widgets.js +72 -59
- djgentelella/static/vendors/friconix/friconix.js +1 -1
- djgentelella/static/vendors/pdfjs/images/altText_add.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/altText_disclaimer.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/altText_done.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/altText_spinner.svg +30 -0
- djgentelella/static/vendors/pdfjs/images/altText_warning.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/cursor-editorFreeHighlight.svg +6 -0
- djgentelella/static/vendors/pdfjs/images/cursor-editorFreeText.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/cursor-editorInk.svg +4 -0
- djgentelella/static/vendors/pdfjs/images/cursor-editorTextHighlight.svg +8 -0
- djgentelella/static/vendors/pdfjs/images/editor-toolbar-delete.svg +5 -0
- djgentelella/static/vendors/pdfjs/images/loading-icon.gif +0 -0
- djgentelella/static/vendors/pdfjs/images/messageBar_closingButton.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/messageBar_warning.svg +3 -0
- djgentelella/static/vendors/pdfjs/images/toolbarButton-editorHighlight.svg +6 -0
- djgentelella/static/vendors/pdfjs/images/toolbarButton-menuArrow.svg +3 -0
- djgentelella/static/vendors/timeline/css/timeline.css +1 -1
- djgentelella/static/vendors/timeline/js/timeline.js +1 -1
- djgentelella/templates/gentelella/registration/login.html +38 -42
- djgentelella/templates/gentelella/statics/javascript.html +75 -61
- djgentelella/templates/gentelella/statics/stylesheets.html +11 -10
- djgentelella/templates/gentelella/widgets/addtreeselect.html +1 -1
- djgentelella/templates/gentelella/widgets/chunkedupload.html +2 -2
- djgentelella/templates/gentelella/widgets/digital_signature.html +208 -0
- djgentelella/templates/gentelella/widgets/file.html +15 -16
- djgentelella/templatetags/gtsettings.py +4 -0
- djgentelella/urls.py +1 -1
- djgentelella/widgets/core.py +5 -2
- djgentelella/widgets/digital_signature.py +116 -0
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/METADATA +18 -20
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/RECORD +72 -35
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/WHEEL +1 -1
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/AUTHORS +0 -0
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/LICENSE.txt +0 -0
- {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/top_level.txt +0 -0
djgentelella/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.4.30'
|
djgentelella/admin.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
|
|
3
|
+
from djgentelella.firmador_digital.models import UserSignatureConfig
|
|
3
4
|
from djgentelella.models import MenuItem, Help, GentelellaSettings, Notification, \
|
|
4
5
|
ChunkedUpload
|
|
5
6
|
from djgentelella.models import PermissionsCategoryManagement
|
|
@@ -9,7 +10,6 @@ from djgentelella.utils import clean_cache
|
|
|
9
10
|
class MenuAdmin(admin.ModelAdmin):
|
|
10
11
|
filter_horizontal = ['permission']
|
|
11
12
|
|
|
12
|
-
|
|
13
13
|
class GentelellaSettingsAdmin(admin.ModelAdmin):
|
|
14
14
|
list_display = ['key', 'value']
|
|
15
15
|
search_fields = ['key']
|
|
@@ -21,18 +21,19 @@ class GentelellaSettingsAdmin(admin.ModelAdmin):
|
|
|
21
21
|
|
|
22
22
|
clean_settings_cache.short_description = "Clean settings cache"
|
|
23
23
|
|
|
24
|
-
|
|
25
24
|
class NotificationAdmin(admin.ModelAdmin):
|
|
26
25
|
list_display = ['description', 'state', 'message_type']
|
|
27
26
|
list_editable = ['state']
|
|
28
27
|
|
|
29
|
-
|
|
30
28
|
class ChunkedUploadAdmin(admin.ModelAdmin):
|
|
31
29
|
list_display = ('upload_id', 'filename', 'status', 'created_on')
|
|
32
30
|
search_fields = ('filename', 'filename')
|
|
33
31
|
list_filter = ('status',)
|
|
34
32
|
|
|
33
|
+
class UserSignatureConfigAdmin(admin.ModelAdmin):
|
|
34
|
+
list_display = ('id', 'user', 'config')
|
|
35
35
|
|
|
36
|
+
admin.site.register(UserSignatureConfig, UserSignatureConfigAdmin)
|
|
36
37
|
admin.site.register(ChunkedUpload, ChunkedUploadAdmin)
|
|
37
38
|
admin.site.register(MenuItem, MenuAdmin)
|
|
38
39
|
admin.site.register(Help)
|
djgentelella/fields/files.py
CHANGED
|
@@ -7,6 +7,7 @@ from django.utils.text import slugify
|
|
|
7
7
|
from django.utils.translation import gettext_lazy as _
|
|
8
8
|
from rest_framework import serializers
|
|
9
9
|
|
|
10
|
+
from djgentelella.firmador_digital.signvalue_utils import ValueDSParser
|
|
10
11
|
from djgentelella.models import ChunkedUpload
|
|
11
12
|
|
|
12
13
|
|
|
@@ -68,7 +69,7 @@ class GTBase64FileField(serializers.FileField):
|
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
class ChunkedFileField(serializers.FileField):
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
def parse_value(self, value):
|
|
73
74
|
"""
|
|
74
75
|
Parses the given value and returns the parsed result.
|
|
@@ -123,3 +124,28 @@ class ChunkedFileField(serializers.FileField):
|
|
|
123
124
|
if data and value.name and value.storage.exists(value.name):
|
|
124
125
|
name = Path(value.name).name
|
|
125
126
|
return {'name': value.name, 'url': data, 'display_name': name}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DigitalSignatureField(serializers.FileField, ValueDSParser):
|
|
130
|
+
def to_internal_value(self, data):
|
|
131
|
+
"""
|
|
132
|
+
Converts the given data to internal value representation.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
data (str): The data to be converted.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The internal value representation of the data, or None if the data is invalid
|
|
139
|
+
or does not contain the required attributes.
|
|
140
|
+
"""
|
|
141
|
+
dev = None
|
|
142
|
+
jsondata = self.get_json_file(data)
|
|
143
|
+
if jsondata:
|
|
144
|
+
return self.get_filefield(jsondata)
|
|
145
|
+
return dev
|
|
146
|
+
|
|
147
|
+
def to_representation(self, value):
|
|
148
|
+
data = super().to_representation(value)
|
|
149
|
+
if data and value.name and value.storage.exists(value.name):
|
|
150
|
+
name = Path(value.name).name
|
|
151
|
+
return {'name': value.name, 'url': data, 'display_name': name}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from django.core.asgi import get_asgi_application
|
|
5
|
+
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
6
|
+
from channels.auth import AuthMiddlewareStack
|
|
7
|
+
from djgentelella.firmador_digital.config.websocket_urls import websocket_urlpatterns
|
|
8
|
+
|
|
9
|
+
class AsgiConfig:
|
|
10
|
+
|
|
11
|
+
def __init__(self, settings_module: str):
|
|
12
|
+
# Configuramos la variable de entorno con el módulo de settings
|
|
13
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
|
|
14
|
+
# variable de la aplicación
|
|
15
|
+
self.application = self._build_application()
|
|
16
|
+
|
|
17
|
+
def _build_application(self):
|
|
18
|
+
"""
|
|
19
|
+
Construye y retorna la aplicación ASGI compuesta.
|
|
20
|
+
"""
|
|
21
|
+
return ProtocolTypeRouter({
|
|
22
|
+
"http": get_asgi_application(),
|
|
23
|
+
"websocket": AuthMiddlewareStack(
|
|
24
|
+
URLRouter(websocket_urlpatterns)
|
|
25
|
+
),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
def __call__(self, scope, receive, send):
|
|
29
|
+
"""
|
|
30
|
+
Permite que la instancia sea llamada como una aplicación ASGI.
|
|
31
|
+
"""
|
|
32
|
+
return self.application(scope, receive, send)
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import logging
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from PIL import Image
|
|
6
|
+
from PIL.Image import Resampling
|
|
7
|
+
from channels.generic.websocket import JsonWebsocketConsumer
|
|
8
|
+
from django.contrib.staticfiles import finders
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
|
|
11
|
+
from djgentelella.firmador_digital.utils import RemoteSignerClient
|
|
12
|
+
from djgentelella.serializers.firmador_digital import (
|
|
13
|
+
WSRequest,
|
|
14
|
+
InitialSignatureSerializer,
|
|
15
|
+
CompleteSignatureSerializer,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("djgentelella")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SignConsumer(JsonWebsocketConsumer):
|
|
22
|
+
def __init__(self, *args, **kwargs):
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
self.data = None
|
|
25
|
+
|
|
26
|
+
def connect(self):
|
|
27
|
+
self.accept()
|
|
28
|
+
self.signclient = RemoteSignerClient(None)
|
|
29
|
+
|
|
30
|
+
def get_serializer(self, content):
|
|
31
|
+
serializer = WSRequest(data=content)
|
|
32
|
+
|
|
33
|
+
if serializer.is_valid():
|
|
34
|
+
if serializer.validated_data["action"] == "initial_signature":
|
|
35
|
+
return InitialSignatureSerializer(data=content)
|
|
36
|
+
if serializer.validated_data["action"] == "complete_signature":
|
|
37
|
+
return CompleteSignatureSerializer(data=content)
|
|
38
|
+
|
|
39
|
+
def disconnect(self, close_code):
|
|
40
|
+
super().disconnect(close_code)
|
|
41
|
+
|
|
42
|
+
def receive_json(self, content, **kwargs):
|
|
43
|
+
"""
|
|
44
|
+
Called with decoded JSON content.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
serializer = self.get_serializer(content)
|
|
48
|
+
|
|
49
|
+
if serializer.is_valid():
|
|
50
|
+
|
|
51
|
+
match serializer.validated_data["action"]:
|
|
52
|
+
case "initial_signature":
|
|
53
|
+
self.do_initial_signature(serializer)
|
|
54
|
+
case "complete_signature":
|
|
55
|
+
self.do_complete_signature(serializer)
|
|
56
|
+
case _:
|
|
57
|
+
self.do_default(serializer)
|
|
58
|
+
else:
|
|
59
|
+
# errors when serializing data
|
|
60
|
+
self.send_json({
|
|
61
|
+
"result": False,
|
|
62
|
+
"error": str(_("Invalid request.")),
|
|
63
|
+
"details": serializer.errors,
|
|
64
|
+
"status": 400,
|
|
65
|
+
"code": 11,
|
|
66
|
+
})
|
|
67
|
+
logger.error("Invalid request.")
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
# uncontrolled data serializing errors
|
|
71
|
+
self.send_json({
|
|
72
|
+
"result": False,
|
|
73
|
+
"error": str(_("An unexpected error occurred.")),
|
|
74
|
+
"details": str(e),
|
|
75
|
+
"status": 500,
|
|
76
|
+
"code": 999,
|
|
77
|
+
})
|
|
78
|
+
logger.error("An unexpected error occurred.", exc_info=e)
|
|
79
|
+
|
|
80
|
+
def do_initial_signature(self, serializer):
|
|
81
|
+
signer = RemoteSignerClient(self.scope["user"])
|
|
82
|
+
|
|
83
|
+
# data for the request to Firmador server
|
|
84
|
+
response = signer.send_document_to_sign(
|
|
85
|
+
instance=serializer.validated_data["instance"],
|
|
86
|
+
usertoken=serializer.validated_data["card"],
|
|
87
|
+
docsettings=serializer.validated_data["docsettings"],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# remove signer image
|
|
91
|
+
if "imageIcon" in response:
|
|
92
|
+
del response["imageIcon"]
|
|
93
|
+
|
|
94
|
+
# about writing the answer to add b64image
|
|
95
|
+
logo_url = serializer.validated_data["logo_url"]
|
|
96
|
+
if "b64image" in response and logo_url:
|
|
97
|
+
response["b64image"] = self.get_logo_base64(logo_url)
|
|
98
|
+
|
|
99
|
+
self.send_json(response)
|
|
100
|
+
|
|
101
|
+
def do_complete_signature(self, serializer):
|
|
102
|
+
try:
|
|
103
|
+
signer = RemoteSignerClient(self.scope["user"])
|
|
104
|
+
data = dict(serializer.validated_data)
|
|
105
|
+
response = signer.complete_signature(data)
|
|
106
|
+
self.send_json({"result": response})
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error("Complete the signature fail", exc_info=e)
|
|
109
|
+
|
|
110
|
+
def do_default(self, serializer):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
def get_logo_base64(self, path_logo, target_width=128):
|
|
114
|
+
if not path_logo:
|
|
115
|
+
print("Do not have logo")
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
# Si la ruta contiene el prefijo /static/, lo removemos
|
|
119
|
+
if path_logo.startswith('/static/'):
|
|
120
|
+
path_logo = path_logo[len('/static/'):]
|
|
121
|
+
|
|
122
|
+
# Buscar la ruta real del archivo en los directorios estáticos
|
|
123
|
+
real_path = finders.find(path_logo)
|
|
124
|
+
if not real_path:
|
|
125
|
+
print("Logo file not found in static directories.")
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
with Image.open(real_path) as img:
|
|
130
|
+
# Calcular la altura de forma proporcional
|
|
131
|
+
ratio = target_width / float(img.width)
|
|
132
|
+
target_height = int(img.height * ratio)
|
|
133
|
+
|
|
134
|
+
# Redimensionar
|
|
135
|
+
img = img.resize((target_width, target_height), Resampling.LANCZOS)
|
|
136
|
+
|
|
137
|
+
# Guardar en memoria
|
|
138
|
+
buffer = BytesIO()
|
|
139
|
+
img.save(buffer, format='PNG')
|
|
140
|
+
|
|
141
|
+
# Convertir a base64
|
|
142
|
+
b64_logo = base64.b64encode(buffer.getvalue()).decode()
|
|
143
|
+
return b64_logo
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print("Error leyendo logo:", e)
|
|
147
|
+
logger.error("Reading logo fail", exc_info=e)
|
|
148
|
+
return ""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from django import forms
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
|
|
7
|
+
from djgentelella.firmador_digital.signvalue_utils import ValueDSParser
|
|
8
|
+
from djgentelella.forms.forms import GTForm
|
|
9
|
+
from djgentelella.widgets import core as genwidgets
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger('djgentelella')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CardForm(GTForm):
|
|
15
|
+
card = forms.ChoiceField(choices=[], widget=genwidgets.Select, label=_("Card"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RenderValueForm(GTForm, ValueDSParser):
|
|
19
|
+
value = forms.CharField(required=True)
|
|
20
|
+
|
|
21
|
+
def clean_value(self):
|
|
22
|
+
value = self.cleaned_data['value']
|
|
23
|
+
jsondata = self.get_json_file(value)
|
|
24
|
+
if not jsondata:
|
|
25
|
+
raise ValidationError(
|
|
26
|
+
_("Invalid value not encoded as b64 o json parser error"),
|
|
27
|
+
code="invalid")
|
|
28
|
+
return jsondata
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
|
|
6
|
+
os.getenv("DJANGO_ASETTINGS_MODULE", "demo.asettings"))
|
|
7
|
+
|
|
8
|
+
bind = getattr(settings, "UVICORN_BIND", "127.0.0.1:9022")
|
|
9
|
+
wsgi_app = getattr(settings, "GUNICORN_ASGI_APP", "demo.asgi:application")
|
|
10
|
+
workers = getattr(settings, "UVICORN_WORKER", 1)
|
|
11
|
+
worker_class = getattr(settings, "UVICORN_WORKER_CLASS",
|
|
12
|
+
"demo.asgi_worker.UvicornWorker")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
|
|
6
|
+
|
|
7
|
+
bind = getattr(settings, "GUNICORN_BIND", "unix:/run/supervisor/gunicorn_wsgi.sock")
|
|
8
|
+
wsgi_app = getattr(settings, "GUNICORN_WSGI_APP", "demo.wsgi:application")
|
|
9
|
+
workers = getattr(settings, "GUNICORN_WORKERS", 1)
|
|
10
|
+
worker_class = getattr(settings, "GUNICORN_WORKER_CLASS",
|
|
11
|
+
"demo.asgi_worker.UvicornWorker")
|
|
12
|
+
user = getattr(settings, "GUNICORN_USER", "demo")
|
|
13
|
+
group = getattr(settings, "GUNICORN_GROUP", "demo")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from django.db import models
|
|
3
|
+
|
|
4
|
+
def get_signature_default():
|
|
5
|
+
return {
|
|
6
|
+
"backgroundColor": "transparente",
|
|
7
|
+
"cAdESLevel": "LTA",
|
|
8
|
+
"contact": "",
|
|
9
|
+
"country": "CR",
|
|
10
|
+
"dateFormat": "dd/MM/yyyy hh\:mm\:ss a",
|
|
11
|
+
"defaultSignMessage": "Esta es una representación gráfica únicamente,\nverifique la validez de la firma.",
|
|
12
|
+
"font": "Nimbus Sans Regular",
|
|
13
|
+
"fontAlignment": "RIGHT",
|
|
14
|
+
"fontColor": "000000",
|
|
15
|
+
"fontSize": "7",
|
|
16
|
+
"image": "",
|
|
17
|
+
"language": "es",
|
|
18
|
+
"pAdESLevel": "LTA",
|
|
19
|
+
"place": "",
|
|
20
|
+
"portNumber": "3516",
|
|
21
|
+
"reason": "",
|
|
22
|
+
"signHeight": "33",
|
|
23
|
+
"signWidth": "133",
|
|
24
|
+
"signX": "40",
|
|
25
|
+
"signY": "60",
|
|
26
|
+
"xAdESLevel": "LTA",
|
|
27
|
+
"isVisibleSignature": False,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class UserSignatureConfig(models.Model):
|
|
32
|
+
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
|
33
|
+
config = models.JSONField(default=get_signature_default)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
|
|
7
|
+
from djgentelella.models import ChunkedUpload
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("djgentelella")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ValueDSParser:
|
|
13
|
+
|
|
14
|
+
def get_file_from_token(self, token):
|
|
15
|
+
tmpupload = ChunkedUpload.objects.filter(upload_id=token).first()
|
|
16
|
+
dev = None
|
|
17
|
+
if tmpupload:
|
|
18
|
+
dev = tmpupload.get_uploaded_file()
|
|
19
|
+
return dev
|
|
20
|
+
|
|
21
|
+
def get_json_file(self, value):
|
|
22
|
+
jsondata = None
|
|
23
|
+
try:
|
|
24
|
+
instance = base64.b64decode(value.encode())
|
|
25
|
+
jsondata = json.loads(instance.decode())
|
|
26
|
+
except Exception as e:
|
|
27
|
+
logger.error("Validation of value on digital signature fail", exc_info=e)
|
|
28
|
+
return jsondata
|
|
29
|
+
|
|
30
|
+
def get_filefield(self, jsondata):
|
|
31
|
+
if 'field_name' in jsondata:
|
|
32
|
+
ccinstance = ContentType.objects.get_for_id(jsondata['contenttype'])
|
|
33
|
+
instance = ccinstance.get_object_for_this_type(pk=jsondata['pk'])
|
|
34
|
+
return self.get_instance_file(instance, jsondata['field_name'])
|
|
35
|
+
if 'token' in jsondata:
|
|
36
|
+
return self.get_file_from_token(jsondata['token'])
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def get_instance_file(self, instance, fieldname):
|
|
40
|
+
file_path = getattr(instance, fieldname)
|
|
41
|
+
return file_path
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.core.files.base import ContentFile
|
|
8
|
+
from django.utils.timezone import now
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
from requests import HTTPError, Timeout, RequestException
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RemoteSignerClient:
|
|
16
|
+
def __init__(self, user):
|
|
17
|
+
self.user = user
|
|
18
|
+
|
|
19
|
+
def load_settings(self, docsettings):
|
|
20
|
+
from djgentelella.firmador_digital.models import UserSignatureConfig
|
|
21
|
+
sc = UserSignatureConfig.objects.filter(user=self.user).first()
|
|
22
|
+
settings = {}
|
|
23
|
+
if sc:
|
|
24
|
+
settings.update(sc.config)
|
|
25
|
+
settings.update(docsettings)
|
|
26
|
+
return settings
|
|
27
|
+
|
|
28
|
+
def load_certificate(self, certtoken):
|
|
29
|
+
return certtoken["certificate"]
|
|
30
|
+
|
|
31
|
+
def get_b64document(self, value):
|
|
32
|
+
|
|
33
|
+
return base64.b64encode(value).decode()
|
|
34
|
+
|
|
35
|
+
def send_document_to_sign(self, instance, usertoken, docsettings):
|
|
36
|
+
b64doc = self.get_b64document(instance['value'])
|
|
37
|
+
|
|
38
|
+
files = {
|
|
39
|
+
"b64Document": b64doc,
|
|
40
|
+
"DocumentExtension": ".pdf",
|
|
41
|
+
"settings": self.load_settings(docsettings),
|
|
42
|
+
"certToken": self.load_certificate(usertoken),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
response = requests.post(
|
|
46
|
+
settings.FIRMADOR_SIGN_URL, json=files
|
|
47
|
+
)
|
|
48
|
+
return response.json()
|
|
49
|
+
|
|
50
|
+
def _finalize_signature(self, data_to_sign, task):
|
|
51
|
+
result = None
|
|
52
|
+
error_msg = _("An unexpected error occurred during the signing process.")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
logger.info("Sending request to signing service")
|
|
56
|
+
response = requests.post(settings.FIRMADOR_SIGN_COMPLETE, json=data_to_sign)
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
logger.info("Successfully finalized signature for the task.")
|
|
59
|
+
result = response.json()
|
|
60
|
+
except HTTPError as errh:
|
|
61
|
+
logger.error(
|
|
62
|
+
"HTTPError during signature finalization for task %s: %s",
|
|
63
|
+
task,
|
|
64
|
+
str(errh),
|
|
65
|
+
exc_info=errh
|
|
66
|
+
)
|
|
67
|
+
error_msg = _(
|
|
68
|
+
"An error occurred while communicating with the signing service."
|
|
69
|
+
)
|
|
70
|
+
result = self.get_error_response(error_msg, str(errh), 502, 2)
|
|
71
|
+
|
|
72
|
+
except ConnectionError as errc:
|
|
73
|
+
logger.error(
|
|
74
|
+
"ConnectionError during signature finalization for task %s: %s",
|
|
75
|
+
task,
|
|
76
|
+
str(errc),
|
|
77
|
+
exc_info=errc
|
|
78
|
+
)
|
|
79
|
+
error_msg = _("Unable to connect to the signing service.")
|
|
80
|
+
result = self.get_error_response(error_msg, str(errc), 503, 2)
|
|
81
|
+
except Timeout as errt:
|
|
82
|
+
logger.error(
|
|
83
|
+
"Timeout during signature finalization for task %s: %s",
|
|
84
|
+
task,
|
|
85
|
+
str(errt),
|
|
86
|
+
exc_info=errt
|
|
87
|
+
)
|
|
88
|
+
error_msg = _("The request to the signing service timed out.")
|
|
89
|
+
result = self.get_error_response(error_msg, str(errt), 408, 12)
|
|
90
|
+
except RequestException as errr:
|
|
91
|
+
logger.error(
|
|
92
|
+
"RequestException during signature finalization for task %s: %s",
|
|
93
|
+
task,
|
|
94
|
+
str(errr),
|
|
95
|
+
exc_info=errr
|
|
96
|
+
)
|
|
97
|
+
error_msg = _("An exception ocurred el request to the signing service.")
|
|
98
|
+
result = self.get_error_response(error_msg, str(errr), 500, 999)
|
|
99
|
+
except Exception as erre:
|
|
100
|
+
logger.error(
|
|
101
|
+
"Exception during signature finalization for task %s: %s",
|
|
102
|
+
task,
|
|
103
|
+
str(erre),
|
|
104
|
+
exc_info=erre
|
|
105
|
+
)
|
|
106
|
+
error_msg = _("An unexpected error occurred during the signing process.")
|
|
107
|
+
result = self.get_error_response(error_msg, str(erre), 500, 999)
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
def get_error_response(self, error_msg, details, status, code):
|
|
112
|
+
return {
|
|
113
|
+
"result": False,
|
|
114
|
+
"error": error_msg,
|
|
115
|
+
"details": details,
|
|
116
|
+
"status": status,
|
|
117
|
+
"code": code,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def complete_signature(self, data_to_sign):
|
|
121
|
+
from djgentelella.models import ChunkedUpload
|
|
122
|
+
datatosign = {
|
|
123
|
+
"signature": data_to_sign["signature"],
|
|
124
|
+
"documentid": data_to_sign["documentid"],
|
|
125
|
+
"certificate": data_to_sign["certificate"],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
instance = data_to_sign["instance"]
|
|
129
|
+
doc_info = self._finalize_signature(datatosign, instance)
|
|
130
|
+
|
|
131
|
+
if doc_info:
|
|
132
|
+
name = "%s_%s_%s.pdf" % (
|
|
133
|
+
instance['pk'],
|
|
134
|
+
self.user.pk,
|
|
135
|
+
now().strftime("%m%d%Y"),
|
|
136
|
+
)
|
|
137
|
+
chunk = self.convert_to_django_file(
|
|
138
|
+
doc_info["bytes"],
|
|
139
|
+
name,
|
|
140
|
+
)
|
|
141
|
+
chfile = ChunkedUpload.objects.create(
|
|
142
|
+
filename=name,
|
|
143
|
+
file=ContentFile(b'', name=name),
|
|
144
|
+
completed_on=now(),
|
|
145
|
+
created_on=now(),
|
|
146
|
+
user=self.user,
|
|
147
|
+
status=2 # this is complete, but I can import constants here
|
|
148
|
+
)
|
|
149
|
+
chfile.append_chunk(chunk, save=True)
|
|
150
|
+
return chfile.upload_id
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
def convert_to_django_file(self, data, name):
|
|
154
|
+
return io.BytesIO(base64.b64decode(data))
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from django.contrib.auth.decorators import login_required
|
|
2
|
+
from django.contrib.contenttypes.models import ContentType
|
|
3
|
+
from django.utils.decorators import method_decorator
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
from rest_framework.views import APIView
|
|
7
|
+
|
|
8
|
+
from djgentelella.firmador_digital.consumers.pdf_render import PDFRenderer
|
|
9
|
+
from djgentelella.firmador_digital.forms import RenderValueForm
|
|
10
|
+
from djgentelella.models import ChunkedUpload
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@method_decorator(login_required, name='dispatch')
|
|
14
|
+
class DigitalSignatureRenderFileAPIView(APIView):
|
|
15
|
+
renderer_classes = [PDFRenderer]
|
|
16
|
+
|
|
17
|
+
form_class = RenderValueForm
|
|
18
|
+
|
|
19
|
+
def check_user(self, cc, pk, user, token=None):
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
def get_token_from_form(self, form):
|
|
23
|
+
jsondata = form.cleaned_data['value']
|
|
24
|
+
if 'token' in jsondata:
|
|
25
|
+
return jsondata['token']
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
def get_file_from_token(self, token):
|
|
29
|
+
tmpupload = ChunkedUpload.objects.filter(upload_id=token).first()
|
|
30
|
+
dev = None
|
|
31
|
+
if tmpupload:
|
|
32
|
+
dev = tmpupload.get_uploaded_file()
|
|
33
|
+
return dev
|
|
34
|
+
|
|
35
|
+
def file_document(self, form, cc, pk):
|
|
36
|
+
jsondata = form.cleaned_data['value']
|
|
37
|
+
if 'field_name' in jsondata:
|
|
38
|
+
ccinstance = ContentType.objects.get_for_id(cc)
|
|
39
|
+
instance = ccinstance.get_object_for_this_type(pk=pk)
|
|
40
|
+
return self.get_instance_file(instance, jsondata['field_name'])
|
|
41
|
+
if 'token' in jsondata:
|
|
42
|
+
return self.get_file_from_token(jsondata['token'])
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def get(self, request, cc, pk, format=None):
|
|
46
|
+
"""
|
|
47
|
+
Retrieve and serve the PDF file associated with a DigitalSignature instance.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
request (HttpRequest): The incoming HTTP request.
|
|
51
|
+
pk (int or str): The primary key of the DigitalSignature instance.
|
|
52
|
+
format (optional): The format specifier (default is None).
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Response: An HTTP response containing the PDF file data with the
|
|
56
|
+
content type 'application/pdf'. If the DigitalSignature instance
|
|
57
|
+
is not found, returns a 404 Not Found response.
|
|
58
|
+
"""
|
|
59
|
+
form = self.form_class(data=request.GET)
|
|
60
|
+
if not form.is_valid():
|
|
61
|
+
return Response({"detail": "File not found"},
|
|
62
|
+
status=status.HTTP_404_NOT_FOUND)
|
|
63
|
+
token = self.get_token_from_form(form)
|
|
64
|
+
if not self.check_user(cc, pk, request.user, token=token):
|
|
65
|
+
return Response({"detail": "File not found"},
|
|
66
|
+
status=status.HTTP_403_FORBIDDEN)
|
|
67
|
+
|
|
68
|
+
file_data = self.file_document(form, cc, pk)
|
|
69
|
+
|
|
70
|
+
return Response(file_data, content_type='application/pdf')
|
|
71
|
+
|
|
72
|
+
def get_instance_file(self, instance, fieldname):
|
|
73
|
+
file_path = getattr(instance, fieldname)
|
|
74
|
+
file_data = None
|
|
75
|
+
with open(file_path.path, 'rb') as f:
|
|
76
|
+
file_data = f.read()
|
|
77
|
+
return file_data
|
|
Binary file
|