IdHub/idhub/user/views.py

520 lines
16 KiB
Python

import os
import json
import base64
import qrcode
import logging
import datetime
import weasyprint
import qrcode.image.svg
from io import BytesIO
from pathlib import Path
from pyhanko.sign import fields, signers
from pyhanko import stamp
from pyhanko.pdf_utils import text
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from django.views.generic.edit import (
UpdateView,
CreateView,
DeleteView,
FormView
)
from django.views.generic.base import TemplateView
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.http import HttpResponse
from django.contrib import messages
from django_tables2 import SingleTableView
from idhub.user.tables import (
DashboardTable,
PersonalInfoTable,
RolesTable,
DIDTable,
CredentialsTable
)
from django.core.cache import cache
from django.conf import settings
from idhub.user.forms import (
ProfileForm,
RequestCredentialForm,
DemandAuthorizationForm,
TermsConditionsForm
)
from utils import certs
from idhub.mixins import UserView
from idhub.models import DID, VerificableCredential, Event, Membership
from idhub_auth.models import User
class MyProfile(UserView):
title = _("My profile")
section = "MyProfile"
class MyWallet(UserView):
title = _("My wallet")
section = "MyWallet"
class DashboardView(UserView, SingleTableView):
template_name = "idhub/user/dashboard.html"
table_class = DashboardTable
title = _('Dashboard')
subtitle = _('Events')
icon = 'bi bi-bell'
section = "Home"
def get_queryset(self, **kwargs):
queryset = Event.objects.select_related('user').filter(
user=self.request.user)
return queryset
class ProfileView(MyProfile, UpdateView, SingleTableView):
template_name = "idhub/user/profile.html"
table_class = PersonalInfoTable
subtitle = _('My personal data')
icon = 'bi bi-person-gear'
fields = ('first_name', 'last_name', 'email')
success_url = reverse_lazy('idhub:user_profile')
model = User
def get_queryset(self, **kwargs):
queryset = Membership.objects.select_related('user').filter(
user=self.request.user)
return queryset
def get_object(self):
return self.request.user
def get_form(self):
form = super().get_form()
form.fields['first_name'].disabled = True
form.fields['last_name'].disabled = True
form.fields['email'].disabled = True
return form
def form_valid(self, form):
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'lang': self.request.LANGUAGE_CODE,
})
return context
class RolesView(MyProfile, SingleTableView):
template_name = "idhub/user/roles.html"
table_class = RolesTable
subtitle = _('My roles')
icon = 'fa-brands fa-critical-role'
def get_queryset(self, **kwargs):
queryset = self.request.user.roles.all()
return queryset
class GDPRView(MyProfile, TemplateView):
template_name = "idhub/user/gdpr.html"
subtitle = _('GDPR info')
icon = 'bi bi-file-earmark-medical'
class CredentialsView(MyWallet, SingleTableView):
template_name = "idhub/user/credentials.html"
table_class = CredentialsTable
subtitle = _('Credential management')
icon = 'bi bi-patch-check-fill'
def get_queryset(self):
queryset = VerificableCredential.objects.filter(
user=self.request.user)
return queryset
class TermsAndConditionsView(UserView, FormView):
template_name = "idhub/user/terms_conditions.html"
title = _("GDPR")
section = ""
subtitle = _('Terms and Conditions')
icon = 'bi bi-file-earmark-medical'
form_class = TermsConditionsForm
success_url = reverse_lazy('idhub:user_dashboard')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
if self.request.user.accept_gdpr:
kwargs['initial'] = {
"accept_privacy": True,
"accept_legal": True,
"accept_cookies": True
}
return kwargs
def form_valid(self, form):
user = form.save()
return super().form_valid(form)
class CredentialView(MyWallet, TemplateView):
template_name = "idhub/user/credential.html"
subtitle = _('Credential')
icon = 'bi bi-patch-check-fill'
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = get_object_or_404(
VerificableCredential,
pk=self.pk,
user=self.request.user
)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'object': self.object,
})
return context
class CredentialPdfView(MyWallet, TemplateView):
template_name = "certificates/4_Model_Certificat.html"
subtitle = _('Credential management')
icon = 'bi bi-patch-check-fill'
file_name = "certificate.pdf"
def get(self, request, *args, **kwargs):
self.admin_validated = cache.get("KEY_DIDS")
pk = kwargs['pk']
self.user = self.request.user
self.object = get_object_or_404(
VerificableCredential,
pk=pk,
eidas1_did__isnull=False,
user=self.request.user
)
self.url_id = "{}://{}/public/credentials/{}".format(
self.request.scheme,
self.request.get_host(),
self.object.hash
)
data = self.build_certificate()
if self.object.eidas1_did:
doc = self.insert_signature(data)
else:
doc = data
response = HttpResponse(doc, content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename={}'.format(self.file_name)
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
this_folder = str(Path.cwd())
path_img_sig = "idhub/static/images/4_Model_Certificat_html_58d7f7eeb828cf29.jpg"
img_signature = next(Path.cwd().glob(path_img_sig))
with open(img_signature, 'rb') as _f:
img_sig = base64.b64encode(_f.read()).decode('utf-8')
path_img_head = "idhub/static/images/4_Model_Certificat_html_7a0214c6fc8f2309.jpg"
img_header= next(Path.cwd().glob(path_img_head))
with open(img_header, 'rb') as _f:
img_head = base64.b64encode(_f.read()).decode('utf-8')
qr = self.generate_qr_code(self.url_id)
first_name = self.user.first_name and self.user.first_name.upper() or ""
last_name = self.user.first_name and self.user.last_name.upper() or ""
document_id = "0000000-L"
course = "COURSE 1"
address = "ADDRESS"
date_course = datetime.datetime.now()
n_hours = 40
n_lections = 5
issue_date = datetime.datetime.now()
context.update({
'object': self.object,
"image_signature": img_sig,
"image_header": img_head,
"first_name": first_name,
"last_name": last_name,
"document_id": document_id,
"course": course,
"address": address,
"date_course": date_course,
"n_hours": n_hours,
"n_lections": n_lections,
"issue_date": issue_date,
"qr": qr
})
return context
def build_certificate(self):
doc = self.render_to_response(context=self.get_context_data())
doc.render()
pdf = weasyprint.HTML(string=doc.content)
return pdf.write_pdf()
def generate_qr_code(self, data):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(data)
qr.make(fit=True)
img_buffer = BytesIO()
img = qr.make_image(fill_color="black", back_color="white")
img.save(img_buffer, format="PNG")
return base64.b64encode(img_buffer.getvalue()).decode('utf-8')
def get_pfx_data(self):
did = self.object.eidas1_did
pw = self.admin_validated
if not did or not pw:
return None, None
key_material = json.loads(did.get_key_material(pw))
cert = key_material.get("cert")
passphrase = key_material.get("passphrase")
if cert and passphrase:
return base64.b64decode(cert), passphrase.encode('utf-8')
return None, None
def signer_init(self):
pfx_data, passphrase = self.get_pfx_data()
if not pfx_data or not passphrase:
return
s = certs.load_cert(
pfx_data, passphrase
)
return s
def insert_signature(self, doc):
sig = self.signer_init()
if not sig:
return
_buffer = BytesIO()
_buffer.write(doc)
w = IncrementalPdfFileWriter(_buffer)
fields.append_signature_field(
w, sig_field_spec=fields.SigFieldSpec(
'Signature', box=(150, 100, 450, 150)
)
)
meta = signers.PdfSignatureMetadata(field_name='Signature')
pdf_signer = signers.PdfSigner(
meta, signer=sig, stamp_style=stamp.TextStampStyle(
stamp_text='Signed by: %(signer)s\nTime: %(ts)s\nURL: %(url)s',
text_box_style=text.TextBoxStyle()
)
)
_bf_out = BytesIO()
pdf_signer.sign_pdf(w, output=_bf_out, appearance_text_params={'url': self.url_id})
return _bf_out.read()
class CredentialJsonView(MyWallet, TemplateView):
def get(self, request, *args, **kwargs):
pk = kwargs['pk']
self.object = get_object_or_404(
VerificableCredential,
pk=pk,
user=self.request.user
)
pass_enc = self.request.session.get("key_did")
data = ""
if pass_enc:
user_pass = self.request.user.decrypt_data(
pass_enc,
self.request.user.password+self.request.session._session_key
)
data = self.object.get_data(user_pass)
response = HttpResponse(data, content_type="application/json")
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
return response
class PublicCredentialJsonView(View):
def get(self, request, *args, **kwargs):
pk = kwargs['pk']
self.object = get_object_or_404(
VerificableCredential,
hash=pk,
eidas1_did__isnull=False,
)
response = HttpResponse(self.object.data, content_type="application/json")
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
return response
class CredentialsRequestView(MyWallet, FormView):
template_name = "idhub/user/credentials_request.html"
subtitle = _('Credential request')
icon = 'bi bi-patch-check-fill'
form_class = RequestCredentialForm
success_url = reverse_lazy('idhub:user_credentials')
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if not self.admin_validated:
return redirect(reverse_lazy('idhub:user_dashboard'))
return response
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
kwargs['lang'] = self.request.LANGUAGE_CODE
domain = "{}://{}".format(self.request.scheme, self.request.get_host())
kwargs['domain'] = domain
pass_enc = self.request.session.get("key_did")
if pass_enc:
user_pass = self.request.user.decrypt_data(
pass_enc,
self.request.user.password+self.request.session._session_key
)
else:
pass_enc = None
kwargs['password'] = user_pass
return kwargs
def form_valid(self, form):
cred = form.save()
if cred:
messages.success(self.request, _("The credential was issued successfully!"))
Event.set_EV_CREDENTIAL_ISSUED_FOR_USER(cred)
Event.set_EV_CREDENTIAL_ISSUED(cred)
url = self.request.session.pop('next_url', None)
if url:
return redirect(url)
else:
messages.error(self.request, _("The credential does not exist!"))
return super().form_valid(form)
class DemandAuthorizationView(MyWallet, FormView):
template_name = "idhub/user/credentials_presentation.html"
subtitle = _('Credential presentation')
icon = 'bi bi-patch-check-fill'
form_class = DemandAuthorizationForm
success_url = reverse_lazy('idhub:user_demand_authorization')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
try:
authorization = form.save()
except Exception:
txt = _("Problems connecting with {url}").format(
url=form.org.response_uri
)
messages.error(self.request, txt)
return super().form_valid(form)
if authorization:
return redirect(authorization)
else:
messages.error(self.request, _("Error sending credential!"))
return super().form_valid(form)
class DidsView(MyWallet, SingleTableView):
template_name = "idhub/user/dids.html"
table_class = DIDTable
subtitle = _('Identities (DIDs)')
icon = 'bi bi-patch-check-fill'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'dids': self.request.user.dids,
})
return context
def get_queryset(self, **kwargs):
queryset = DID.objects.filter(user=self.request.user)
return queryset
class DidRegisterView(MyWallet, CreateView):
template_name = "idhub/user/did_register.html"
subtitle = _('Add a new Identity (DID)')
icon = 'bi bi-patch-check-fill'
wallet = True
model = DID
fields = ('label', 'type')
success_url = reverse_lazy('idhub:user_dids')
object = None
def form_valid(self, form):
form.instance.user = self.request.user
pw = self.request.user.decrypt_data(
self.request.session.get("key_did"),
self.request.user.password+self.request.session._session_key
)
form.instance.set_did(pw)
form.save()
messages.success(self.request, _('DID created successfully'))
Event.set_EV_DID_CREATED(form.instance)
Event.set_EV_DID_CREATED_BY_USER(form.instance)
return super().form_valid(form)
class DidEditView(MyWallet, UpdateView):
template_name = "idhub/user/did_register.html"
subtitle = _('Identities (DIDs)')
icon = 'bi bi-patch-check-fill'
wallet = True
model = DID
fields = ('label',)
success_url = reverse_lazy('idhub:user_dids')
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = get_object_or_404(self.model, pk=self.pk)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
user = form.save()
messages.success(self.request, _('DID updated successfully'))
return super().form_valid(form)
class DidDeleteView(MyWallet, DeleteView):
subtitle = _('Identities (DIDs)')
icon = 'bi bi-patch-check-fill'
wallet = True
model = DID
success_url = reverse_lazy('idhub:user_dids')
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = get_object_or_404(self.model, pk=self.pk)
Event.set_EV_DID_DELETED(self.object)
self.object.delete()
messages.success(self.request, _('DID delete successfully'))
return redirect(self.success_url)