diff --git a/idhub/admin/forms.py b/idhub/admin/forms.py index aa453ce..fd57e64 100644 --- a/idhub/admin/forms.py +++ b/idhub/admin/forms.py @@ -1,23 +1,19 @@ -import csv import json -import copy import base64 import jsonschema import pandas as pd -from pyhanko.sign import signers - +from nacl.exceptions import CryptoError from django import forms from django.core.cache import cache from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError -from utils import credtools, certs +from utils import certs from idhub.models import ( DID, File_datas, Membership, Schemas, - Service, UserRol, VerificableCredential, ) @@ -51,6 +47,37 @@ class TermsConditionsForm2(forms.Form): return +class EncryptionKeyForm(forms.Form): + key = forms.CharField( + label=_("Key for encrypt the secrets of all system"), + required=True + ) + + def clean(self): + data = self.cleaned_data + self._key = data["key"] + if not DID.objects.exists(): + return data + + did = DID.objects.first() + cache.set("KEY_DIDS", self._key, None) + try: + did.get_key_material() + except CryptoError: + cache.set("KEY_DIDS", None) + txt = _("Key no valid!") + raise ValidationError(txt) + + return data + + def save(self, commit=True): + + if commit: + cache.set("KEY_DIDS", self._key, None) + + return + + class TermsConditionsForm(forms.Form): accept_privacy = forms.BooleanField( widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}), @@ -133,7 +160,7 @@ class ImportForm(forms.Form): self.fields['did'].choices = [ (x.did, x.label) for x in dids.filter(eidas1=False) ] - self.fields['schema'].choices = [ + self.fields['schema'].choices = [(0, _('Select one'))] + [ (x.id, x.name) for x in Schemas.objects.filter() ] if dids.filter(eidas1=True).exists(): @@ -197,6 +224,9 @@ class ImportForm(forms.Form): if not data_pd: self.exception("This file is empty!") + if not self._schema: + return data + for n in range(df.last_valid_index()+1): row = {} for k in data_pd.keys(): @@ -382,7 +412,6 @@ class ImportCertificateForm(forms.Form): return data def new_did(self): - cert = self.pfx_file keys = { "cert": base64.b64encode(self.pfx_file).decode('utf-8'), "passphrase": self._pss diff --git a/idhub/admin/views.py b/idhub/admin/views.py index 25ac28d..8a9bb63 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -1,9 +1,6 @@ import os import json -import logging -import pandas as pd from pathlib import Path -from jsonschema import validate from smtplib import SMTPException from django_tables2 import SingleTableView @@ -18,7 +15,6 @@ from django.views.generic.edit import ( UpdateView, ) 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 @@ -28,12 +24,13 @@ from idhub_auth.forms import ProfileForm from idhub.mixins import AdminView, Http403 from idhub.email.views import NotifyActivateUserByEmail from idhub.admin.forms import ( + EncryptionKeyForm, + ImportCertificateForm, ImportForm, MembershipForm, TermsConditionsForm, SchemaForm, - UserRolForm, - ImportCertificateForm, + UserRolForm ) from idhub.admin.tables import ( DashboardTable, @@ -79,7 +76,27 @@ class TermsAndConditionsView(AdminView, FormView): return kwargs def form_valid(self, form): - user = form.save() + form.save() + return super().form_valid(form) + + +class EncryptionKeyView(AdminView, FormView): + template_name = "idhub/admin/encryption_key.html" + title = _('Encryption Key') + section = "" + subtitle = _('Encryption Key') + icon = 'bi bi-key' + form_class = EncryptionKeyForm + success_url = reverse_lazy('idhub:admin_dashboard') + + def get(self, request, *args, **kwargs): + if self.admin_validated: + return redirect(self.success_url) + + return super().get(request, *args, **kwargs) + + def form_valid(self, form): + form.save() return super().form_valid(form) @@ -649,7 +666,7 @@ class CredentialJsonView(Credentials): VerificableCredential, pk=pk, ) - response = HttpResponse(self.object.data, content_type="application/json") + response = HttpResponse(self.object.get_data(), content_type="application/json") response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json") return response @@ -730,7 +747,7 @@ class DidRegisterView(Credentials, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did(cache.get("KEY_DIDS")) + form.instance.set_did() form.save() messages.success(self.request, _('DID created successfully')) Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance) @@ -752,7 +769,7 @@ class DidEditView(Credentials, UpdateView): return super().get(request, *args, **kwargs) def form_valid(self, form): - user = form.save() + form.save() messages.success(self.request, _('DID updated successfully')) return super().form_valid(form) diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index e8abf19..15f74ea 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -23,6 +23,8 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): ADMIN_EMAIL = config('ADMIN_EMAIL', 'admin@example.org') ADMIN_PASSWORD = config('ADMIN_PASSWORD', '1234') + KEY_DIDS = config('KEY_DIDS', '1234') + cache.set("KEY_DIDS", KEY_DIDS, None) self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD) if settings.CREATE_TEST_USERS: @@ -43,21 +45,17 @@ class Command(BaseCommand): def create_admin_users(self, email, password): su = User.objects.create_superuser(email=email, password=password) - su.set_encrypted_sensitive_data(password) + su.set_encrypted_sensitive_data() su.save() - key = su.decrypt_sensitive_data(password) - key_dids = {su.id: key} - cache.set("KEY_DIDS", key_dids, None) - self.create_defaults_dids(su, key) + self.create_defaults_dids(su) def create_users(self, email, password): u = User.objects.create(email=email, password=password) u.set_password(password) - u.set_encrypted_sensitive_data(password) + u.set_encrypted_sensitive_data() u.save() - key = u.decrypt_sensitive_data(password) - self.create_defaults_dids(u, key) + self.create_defaults_dids(u) def create_organizations(self, name, url): @@ -72,9 +70,10 @@ class Command(BaseCommand): org1.my_client_secret = org2.client_secret org1.save() org2.save() - def create_defaults_dids(self, u, password): + + def create_defaults_dids(self, u): did = DID(label="Default", user=u, type=DID.Types.WEB) - did.set_did(password) + did.set_did() did.save() def create_schemas(self): diff --git a/idhub/mixins.py b/idhub/mixins.py index b2b3fbe..ad1379b 100644 --- a/idhub/mixins.py +++ b/idhub/mixins.py @@ -12,8 +12,8 @@ class Http403(PermissionDenied): default_detail = _('Permission denied. User is not authenticated') default_code = 'forbidden' - def __init__(self, detail=None, code=None): - if detail is not None: + def __init__(self, details=None, code=None): + if details is not None: self.detail = details or self.default_details if code is not None: self.code = code or self.default_code @@ -22,15 +22,30 @@ class Http403(PermissionDenied): class UserView(LoginRequiredMixin): login_url = "/login/" wallet = False + admin_validated = False path_terms = [ 'admin_terms_and_conditions', 'user_terms_and_conditions', 'user_gdpr', + 'user_waiting', + 'user_waiting', + 'encryption_key', ] def get(self, request, *args, **kwargs): self.admin_validated = cache.get("KEY_DIDS") response = super().get(request, *args, **kwargs) + + if not self.admin_validated: + actual_path = resolve(self.request.path).url_name + if not self.request.user.is_admin: + if actual_path != 'user_waiting': + return redirect(reverse_lazy("idhub:user_waiting")) + + if self.request.user.is_admin: + if actual_path != 'encryption_key': + return redirect(reverse_lazy("idhub:encryption_key")) + url = self.check_gdpr() return url or response diff --git a/idhub/models.py b/idhub/models.py index ae61c56..7d6a1c6 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -9,7 +9,6 @@ from django.conf import settings from django.core.cache import cache from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ -from nacl import secret from utils.idhub_ssikit import ( generate_did_controller_key, @@ -34,26 +33,27 @@ class Event(models.Model): EV_DID_CREATED = 9, "DID created" EV_DID_DELETED = 10, "DID deleted" EV_CREDENTIAL_DELETED_BY_USER = 11, "Credential deleted by user" - EV_CREDENTIAL_DELETED = 12, "Credential deleted" - EV_CREDENTIAL_ISSUED_FOR_USER = 13, "Credential issued for user" - EV_CREDENTIAL_ISSUED = 14, "Credential issued" - EV_CREDENTIAL_PRESENTED_BY_USER = 15, "Credential presented by user" - EV_CREDENTIAL_PRESENTED = 16, "Credential presented" - EV_CREDENTIAL_ENABLED = 17, "Credential enabled" - EV_CREDENTIAL_CAN_BE_REQUESTED = 18, "Credential available" - EV_CREDENTIAL_REVOKED_BY_ADMIN = 19, "Credential revoked by admin" - EV_CREDENTIAL_REVOKED = 20, "Credential revoked" - EV_ROLE_CREATED_BY_ADMIN = 21, "Role created by admin" - EV_ROLE_MODIFIED_BY_ADMIN = 22, "Role modified by admin" - EV_ROLE_DELETED_BY_ADMIN = 23, "Role deleted by admin" - EV_SERVICE_CREATED_BY_ADMIN = 24, "Service created by admin" - EV_SERVICE_MODIFIED_BY_ADMIN = 25, "Service modified by admin" - EV_SERVICE_DELETED_BY_ADMIN = 26, "Service deleted by admin" - EV_ORG_DID_CREATED_BY_ADMIN = 27, "Organisational DID created by admin" - EV_ORG_DID_DELETED_BY_ADMIN = 28, "Organisational DID deleted by admin" - EV_USR_DEACTIVATED_BY_ADMIN = 29, "User deactivated" - EV_USR_ACTIVATED_BY_ADMIN = 30, "User activated" - EV_USR_SEND_VP = 31, "User send Verificable Presentation" + EV_CREDENTIAL_DELETED_BY_ADMIN = 12, "Credential deleted by admin" + EV_CREDENTIAL_DELETED = 13, "Credential deleted" + EV_CREDENTIAL_ISSUED_FOR_USER = 14, "Credential issued for user" + EV_CREDENTIAL_ISSUED = 15, "Credential issued" + EV_CREDENTIAL_PRESENTED_BY_USER = 16, "Credential presented by user" + EV_CREDENTIAL_PRESENTED = 17, "Credential presented" + EV_CREDENTIAL_ENABLED = 18, "Credential enabled" + EV_CREDENTIAL_CAN_BE_REQUESTED = 19, "Credential available" + EV_CREDENTIAL_REVOKED_BY_ADMIN = 20, "Credential revoked by admin" + EV_CREDENTIAL_REVOKED = 21, "Credential revoked" + EV_ROLE_CREATED_BY_ADMIN = 22, "Role created by admin" + EV_ROLE_MODIFIED_BY_ADMIN = 23, "Role modified by admin" + EV_ROLE_DELETED_BY_ADMIN = 24, "Role deleted by admin" + EV_SERVICE_CREATED_BY_ADMIN = 25, "Service created by admin" + EV_SERVICE_MODIFIED_BY_ADMIN = 26, "Service modified by admin" + EV_SERVICE_DELETED_BY_ADMIN = 27, "Service deleted by admin" + EV_ORG_DID_CREATED_BY_ADMIN = 28, "Organisational DID created by admin" + EV_ORG_DID_DELETED_BY_ADMIN = 29, "Organisational DID deleted by admin" + EV_USR_DEACTIVATED_BY_ADMIN = 30, "User deactivated" + EV_USR_ACTIVATED_BY_ADMIN = 31, "User activated" + EV_USR_SEND_VP = 32, "User send Verificable Presentation" created = models.DateTimeField(_("Date"), auto_now=True) message = models.CharField(_("Description"), max_length=350) @@ -99,9 +99,8 @@ class Event(models.Model): @classmethod def set_EV_DATA_UPDATE_REQUESTED_BY_USER(cls, user): msg = _("The user '{username}' has request the update of the following information: ") - msg += "['field1':'value1', 'field2':'value2'>,...]".format( - username=user.username, - ) + msg += "['field1':'value1', 'field2':'value2'>,...]" + msg = msg.format(username=user.username) cls.objects.create( type=cls.Types.EV_DATA_UPDATE_REQUESTED_BY_USER, message=msg, @@ -444,11 +443,11 @@ class DID(models.Model): # JSON-serialized DID document didweb_document = models.TextField() - def get_key_material(self, password): - return self.user.decrypt_data(self.key_material, password) + def get_key_material(self): + return self.user.decrypt_data(self.key_material) - def set_key_material(self, value, password): - self.key_material = self.user.encrypt_data(value, password) + def set_key_material(self, value): + self.key_material = self.user.encrypt_data(value) @property def is_organization_did(self): @@ -456,9 +455,9 @@ class DID(models.Model): return True return False - def set_did(self, password): + def set_did(self): new_key_material = generate_did_controller_key() - self.set_key_material(new_key_material, password) + self.set_key_material(new_key_material) if self.type == self.Types.KEY: self.did = keydid_from_controller_key(new_key_material) @@ -621,17 +620,14 @@ class VerificableCredential(models.Model): return True return False - def get_data(self, password): + def get_data(self): if not self.data: return "" - if self.eidas1_did: - return self.data - - return self.user.decrypt_data(self.data, password) + return self.user.decrypt_data(self.data) - def set_data(self, value, password): - self.data = self.user.encrypt_data(value, password) + def set_data(self, value): + self.data = self.user.encrypt_data(value) def get_description(self): return self.schema._description or '' @@ -652,32 +648,24 @@ class VerificableCredential(models.Model): data = json.loads(self.csv_data).items() return data - def issue(self, did, password, domain=settings.DOMAIN.strip("/")): + def issue(self, did, domain=settings.DOMAIN.strip("/")): if self.status == self.Status.ISSUED: return self.subject_did = did self.issued_on = datetime.datetime.now().astimezone(pytz.utc) - issuer_pass = cache.get("KEY_DIDS") - # issuer_pass = self.user.decrypt_data( - # cache.get("KEY_DIDS"), - # settings.SECRET_KEY, - # ) # hash of credential without sign self.hash = hashlib.sha3_256(self.render(domain).encode()).hexdigest() data = sign_credential( self.render(domain), - self.issuer_did.get_key_material(issuer_pass) + self.issuer_did.get_key_material() ) valid, reason = verify_credential(data) if not valid: return - if self.eidas1_did: - self.data = data - else: - self.data = self.user.encrypt_data(data, password) + self.data = self.user.encrypt_data(data) self.status = self.Status.ISSUED diff --git a/idhub/templates/idhub/admin/issue_credentials.html b/idhub/templates/idhub/admin/issue_credentials.html index ac93171..3c29289 100644 --- a/idhub/templates/idhub/admin/issue_credentials.html +++ b/idhub/templates/idhub/admin/issue_credentials.html @@ -38,7 +38,7 @@ {% trans 'Issuance date' %}: