IdHub/idhub/user/views.py

571 lines
18 KiB
Python

import json
import base64
import qrcode
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.core.cache import cache
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 idhub.user.forms import (
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):
events_for_users = self.get_user_events()
queryset = Event.objects.select_related('user').filter(
user=self.request.user).filter(
type__in=events_for_users).order_by("-created")
return queryset
def get_user_events(self):
events_for_users = [
Event.Types.EV_USR_WELCOME, # User welcomed
Event.Types.EV_USR_UPDATED, # Your data updated by admin
Event.Types.EV_DID_CREATED, # DID created
Event.Types.EV_DID_DELETED, # DID deleted
Event.Types.EV_CREDENTIAL_DELETED, # Credential deleted
Event.Types.EV_CREDENTIAL_ISSUED, # Credential issued
Event.Types.EV_CREDENTIAL_PRESENTED, # Credential presented
Event.Types.EV_CREDENTIAL_CAN_BE_REQUESTED, # Credential available
Event.Types.EV_CREDENTIAL_REVOKED, # Credential revoked
Event.Types.EV_USR_SEND_VP, # User send verificable presentation
]
return events_for_users
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 = _('Data protection')
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 = _("Data Protection")
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):
form.save()
return super().form_valid(form)
class WaitingView(UserView, TemplateView):
template_name = "idhub/user/waiting.html"
title = _("Comunication with admin")
subtitle = _('Service temporary close')
section = ""
icon = 'bi bi-file-earmark-medical'
success_url = reverse_lazy('idhub:user_dashboard')
def get(self, request, *args, **kwargs):
if cache.get("KEY_DIDS"):
return redirect(self.success_url)
return super().get(request, *args, **kwargs)
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
)
self.subtitle += ": {}".format(self.object.type)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
url_ca = reverse_lazy('idhub:user_credential_pdf', args=[self.object.id, 'ca'])
url_es = reverse_lazy('idhub:user_credential_pdf', args=[self.object.id, 'es'])
context.update({
'object': self.object,
'url_ca': url_ca,
'url_es': url_es,
})
return context
class CredentialPdfView(MyWallet, TemplateView):
template_name = "certificates/{}_{}.html"
template_name = "certificates/{}_{}.html"
subtitle = _('Credential management')
icon = 'bi bi-patch-check-fill'
file_name = "certificate.pdf"
def get(self, request, *args, **kwargs):
if not cache.get("KEY_DIDS"):
return redirect(reverse_lazy('idhub:user_dashboard'))
pk = kwargs['pk']
lang = kwargs.get('lang', 'ca')
self.user = self.request.user
self.object = get_object_or_404(
VerificableCredential,
pk=pk,
eidas1_did__isnull=False,
user=self.request.user
)
self.credential_type = self.object.schema.file_schema.split(".json")[0]
self.template_name = self.template_name.format(
self.credential_type,
lang
)
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_img_sign(self):
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')
return img_sig
def get_img_header(self):
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')
return img_head
def get_img_footer(self):
path_img_foot = "idhub/static/images/4_Model_Certificat_html_941e7b967953b3f3.jpg"
img_foot= next(Path.cwd().glob(path_img_foot))
with open(img_foot, 'rb') as _f:
img_foot = base64.b64encode(_f.read()).decode('utf-8')
return img_foot
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(dict(self.object.get_datas()))
img_sig = self.get_img_sign()
img_head = self.get_img_header()
img_foot = self.get_img_footer()
qr = self.generate_qr_code(self.url_id)
issue_date = context.get('certificationDate', '')
membership_since = context.get('membershipSince', '')
membership_type = context.get('membershipType', '').lower()
context.update({
'object': self.object,
"image_signature": img_sig,
"image_header": img_head,
"image_footer": img_foot,
"issue_date": issue_date,
"membership_since": membership_since,
"membership_type": membership_type,
"qr": qr,
})
return context
def build_certificate(self):
try:
doc = self.render_to_response(context=self.get_context_data())
except Exception:
self.template_name = "certificates/4_Model_Certificat_ca.html"
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
if not did:
return None, None
key_material = json.loads(did.get_key_material())
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, 75, 450, 100)
)
)
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
)
data = self.object.get_data()
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.get_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, *args, **kwargs):
response = super().get(*args, **kwargs)
if not DID.objects.filter(user=self.request.user).exists():
return redirect(reverse_lazy('idhub:user_dids_new'))
return response
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
self.if_credentials = VerificableCredential.objects.filter(
user=self.request.user,
status=VerificableCredential.Status.ENABLED.value,
).exists()
kwargs['user'] = self.request.user
kwargs['lang'] = self.request.LANGUAGE_CODE
domain = "{}://{}".format(self.request.scheme, self.request.get_host())
kwargs['domain'] = domain
kwargs['if_credentials'] = self.if_credentials
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(self, *args, **kwargs):
response = super().get(*args, **kwargs)
creds_enable = VerificableCredential.objects.filter(
user=self.request.user,
status=VerificableCredential.Status.ENABLED.value,
).exists()
if not self.if_credentials and creds_enable:
return redirect(reverse_lazy('idhub:user_credentials_request'))
return response
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
self.if_credentials = VerificableCredential.objects.filter(
user=self.request.user,
status=VerificableCredential.Status.ISSUED.value,
).exists()
kwargs['user'] = self.request.user
kwargs['if_credentials'] = self.if_credentials
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
form.instance.set_did()
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):
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)