Merge pull request 'encrypt2' (#145) from encrypt2 into release

Reviewed-on: https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub/pulls/145
This commit is contained in:
cayop 2024-02-22 17:57:10 +00:00
commit 4f562162bf
25 changed files with 370 additions and 218 deletions

View File

@ -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,38 @@ 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)
cache.set("KEY_DIDS", None)
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 +161,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 +225,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():
@ -226,18 +257,21 @@ class ImportForm(forms.Form):
except jsonschema.exceptions.ValidationError as err:
msg = "line {}: {}".format(line+1, err)
return self.exception(msg)
# try:
# check = credtools.validate_json(row, self.json_schema)
# if check is not True:
# raise ValidationError("Not valid row")
# except Exception as e:
user, new = User.objects.get_or_create(email=row.get('email'))
if new:
self.users.append(user)
user.set_encrypted_sensitive_data()
user.save()
self.create_defaults_dids(user)
return user
def create_defaults_dids(self, user):
did = DID(label="Default", user=user, type=DID.Types.WEB)
did.set_did()
did.save()
def create_credential(self, user, row):
bcred = VerificableCredential.objects.filter(
user=user,
@ -382,7 +416,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
@ -397,8 +430,7 @@ class ImportCertificateForm(forms.Form):
type=DID.Types.KEY
)
pw = cache.get("KEY_DIDS")
self._did.set_key_material(key_material, pw)
self._did.set_key_material(key_material)
def save(self, commit=True):

View File

@ -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,22 +15,23 @@ 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
from django.core.cache import cache
from utils import credtools
from idhub_auth.models import User
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 +77,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 cache.get("KEY_DIDS"):
return redirect(self.success_url)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
form.save()
return super().form_valid(form)
@ -295,7 +313,11 @@ class PeopleRegisterView(NotifyActivateUserByEmail, People, CreateView):
return self.success_url
def form_valid(self, form):
user = form.save()
super().form_valid(form)
user = form.instance
user.set_encrypted_sensitive_data()
user.save()
self.create_defaults_dids(user)
messages.success(self.request, _('The account was created successfully'))
Event.set_EV_USR_REGISTERED(user)
Event.set_EV_USR_WELCOME(user)
@ -307,6 +329,11 @@ class PeopleRegisterView(NotifyActivateUserByEmail, People, CreateView):
messages.error(self.request, e)
return super().form_valid(form)
def create_defaults_dids(self, user):
did = DID(label="Default", user=user, type=DID.Types.WEB)
did.set_did()
did.save()
class PeopleMembershipRegisterView(People, FormView):
template_name = "idhub/admin/people_membership_register.html"
@ -679,7 +706,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
@ -714,15 +741,10 @@ class DeleteCredentialsView(Credentials):
VerificableCredential,
pk=pk,
)
status = [
VerificableCredential.Status.REVOKED,
VerificableCredential.Status.ISSUED
]
if self.object.status in status:
self.object.delete()
messages.success(self.request, _('Credential deleted successfully'))
Event.set_EV_CREDENTIAL_DELETED(self.object)
Event.set_EV_CREDENTIAL_DELETED_BY_ADMIN(self.object)
self.object.delete()
messages.success(self.request, _('Credential deleted successfully'))
Event.set_EV_CREDENTIAL_DELETED(self.object)
Event.set_EV_CREDENTIAL_DELETED_BY_ADMIN(self.object)
return redirect(self.success_url)
@ -760,7 +782,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)
@ -782,7 +804,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)

View File

@ -5,13 +5,12 @@ import json
from pathlib import Path
from utils import credtools
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.core.cache import cache
from decouple import config
from idhub.models import DID, Schemas
from oidc4vp.models import Organization
from promotion.models import Promotion
User = get_user_model()
@ -23,6 +22,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')
cache.set("KEY_DIDS", KEY_DIDS, None)
self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD)
if settings.CREATE_TEST_USERS:
@ -43,21 +44,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,15 +69,14 @@ 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):
schemas_files = os.listdir(settings.SCHEMAS_DIR)
schemas = [x for x in schemas_files
if not Schemas.objects.filter(file_schema=x).exists()]
for x in schemas_files:
if Schemas.objects.filter(file_schema=x).exists():
continue

View File

@ -0,0 +1,56 @@
import logging
from urllib.parse import urlparse
from django.conf import settings
from django.template import loader
from django.core.mail import EmailMultiAlternatives
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
logger = logging.getLogger(__name__)
User = get_user_model()
class Command(BaseCommand):
help = "Send mails to the admins for add general key"
subject_template_name = 'idhub/admin/registration/start_app_admin_subject.txt'
email_template_name = 'idhub/admin/registration/start_app_admin_email.txt'
html_email_template_name = 'idhub/admin/registration/start_app_admin_email.html'
def handle(self, *args, **kwargs):
for user in User.objects.filter(is_admin=True):
self.send_email(user)
def send_email(self, user):
"""
Send a email when a user is activated.
"""
parsed_url = urlparse(settings.RESPONSE_URI)
domain = f"{parsed_url.scheme}://{parsed_url.netloc}/"
context = {
"domain": domain,
}
subject = loader.render_to_string(self.subject_template_name, context)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
body = loader.render_to_string(self.email_template_name, context)
from_email = settings.DEFAULT_FROM_EMAIL
to_email = user.email
email_message = EmailMultiAlternatives(
subject, body, from_email, [to_email])
html_email = loader.render_to_string(self.html_email_template_name, context)
email_message.attach_alternative(html_email, 'text/html')
try:
if settings.ENABLE_EMAIL:
email_message.send()
return
logger.warning(to_email)
logger.warning(body)
except Exception as err:
logger.error(err)
return

View File

@ -1,6 +1,5 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import views as auth_views
from django.core.exceptions import PermissionDenied
from django.urls import reverse_lazy, resolve
from django.shortcuts import redirect
@ -12,8 +11,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 +21,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

View File

@ -6,10 +6,8 @@ import datetime
from collections import OrderedDict
from django.db import models
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 +32,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 +98,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 +442,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 +454,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 +619,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 ''
@ -649,35 +644,28 @@ class VerificableCredential(models.Model):
return self.Status(self.status).label
def get_datas(self):
data = json.loads(self.csv_data).items()
return data
data = self.render()
credential_subject = ujson.loads(data).get("credentialSubject", {})
return credential_subject.items()
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
@ -714,7 +702,7 @@ class VerificableCredential(models.Model):
context.update(d)
return context
def render(self, domain):
def render(self, domain=""):
context = self.get_context(domain)
template_name = 'credentials/{}'.format(
self.schema.file_schema

View File

@ -46,7 +46,7 @@
class="btn btn-primary form-control" id="submit-id-submit">
</div>
</form>
<div id="login-footer" class="mt-3 d-none">
<div id="login-footer" class="mt-3">
<a href="{% url 'idhub:password_reset' %}" data-toggle="modal" data-target="#forgotPasswordModal">{% trans "Forgot your password? Click here to recover" %}</a>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends "idhub/base_admin.html" %}
{% load i18n %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
<div class="row">
<div class="col-sm-4">
{% bootstrap_form form %}
</div>
</div>
<div class="form-actions-no-box">
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
</form>
{% endblock %}

View File

@ -12,11 +12,8 @@
<div class="col text-end">
{% if object.get_status == 'Issued' %}
<a class="btn btn-yellow" class="btn btn-orange" data-bs-toggle="modal" data-bs-target="#confirm-revoke" href="javascript:void()">{% trans 'Revoke' %}</a>
<a class="btn btn-orange" class="btn btn-orange" data-bs-toggle="modal" data-bs-target="#confirm-delete" href="javascript:void()">{% trans 'Delete' %}</a>
{% endif %}
{% if object.get_status == 'Revoked' %}
<a class="btn btn-orange" class="btn btn-orange" data-bs-toggle="modal" data-bs-target="#confirm-delete" href="javascript:void()">{% trans 'Delete' %}</a>
{% endif %}
</div>
</div>
<div class="row">
@ -38,7 +35,7 @@
<strong>{% trans 'Issuance date' %}:</strong>
</div>
<div class="col bg-light text-secondary">
{{ object.issuer_on|default_if_none:"" }}
{{ object.issued_on|default_if_none:"" }}
</div>
</div>
<div class="row mt-3">
@ -49,11 +46,13 @@
{{ object.get_status}}
</div>
</div>
{% if object.issued_on %}
<div class="row mt-3">
<div class="col text-center">
<a class="btn btn-green-admin" href="{% url 'idhub:admin_credential_json' object.id %}">{% trans 'View in JSON format' %}</a>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Modal Revoke -->

View File

@ -0,0 +1,9 @@
Hola, se ha reiniciado el servicio de Idhub: {{ domain }}
<br />
no será plenamente funcional hasta que no introduzcas la contraseña de encriptacion del sistema.
<br />
<br />
Por favor entra en la aplicacion como administrador y añade esta contraseña.
<br />
Muchas gracias

View File

@ -0,0 +1,7 @@
Hola, se ha reiniciado el servicio de Idhub: {{ domain }}
no será plenamente funcional hasta que no introduzcas la contraseña de encriptacion del sistema.
Por favor entra en la aplicacion como administrador y añade esta contraseña.
Muchas gracias

View File

@ -0,0 +1 @@
Reinicio de IdHub

View File

@ -25,7 +25,7 @@
<strong>{% trans 'Issuance date' %}:</strong>
</div>
<div class="col bg-light text-secondary">
{{ object.issuer_on|default_if_none:"" }}
{{ object.issued_on|default_if_none:"" }}
</div>
</div>
<div class="row mt-3">
@ -38,6 +38,7 @@
</div>
</div>
</div>
{% if object.issued_on %}
<div class="row mt-3">
{% if object.eidas1_did and admin_validated %}
<div class="col text-center">
@ -47,5 +48,6 @@
<div class="col text-center">
<a class="btn btn-green-user" href="{% url 'idhub:user_credential_json' object.id %}">{% trans 'View credential in JSON format' %}</a>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "idhub/base.html" %}
{% load i18n %}
{% block content %}
<h3>
<i class="{{ icon }}"></i>
{{ subtitle }}
</h3>
{% trans 'Please contact the administrator to open the service.' %}
{% endblock %}

View File

@ -6,6 +6,7 @@ from unittest.mock import MagicMock
from django.conf import settings
from django.test import TestCase
from django.urls import reverse
from django.core.cache import cache
from django.core.exceptions import FieldError
from idhub_auth.models import User
@ -15,6 +16,7 @@ from idhub.models import Event, Membership, Rol, UserRol, Service, Schemas
class AdminDashboardTableTest(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
self.admin_user = User.objects.create_superuser(
email='adminuser@example.org',
password='adminpass12')
@ -75,6 +77,7 @@ class AdminDashboardTableTest(TestCase):
class UserTableTest(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
self.user1 = User.objects.create(email="user1@example.com")
self.user2 = User.objects.create(email="user2@example.com")
Membership.objects.create(user=self.user1,
@ -106,6 +109,7 @@ class UserTableTest(TestCase):
class TemplateTableTest(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
self.table = TemplateTable(Schemas.objects.all())
self.create_schemas(amount=3)

View File

@ -1,4 +1,5 @@
from django.test import TestCase, RequestFactory
from django.core.cache import cache
from django.urls import reverse
from idhub_auth.models import User
@ -9,6 +10,7 @@ from idhub.admin.views import PeopleListView
class AdminDashboardViewTest(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
self.user = User.objects.create_user(
email='normaluser@example.org',
password='testpass12',
@ -99,6 +101,7 @@ class AdminDashboardViewTest(TestCase):
class PeopleListViewTest(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
# Set up a RequestFactory to create mock requests
self.factory = RequestFactory()
@ -144,6 +147,7 @@ class PeopleListViewTest(TestCase):
class UserDashboardViewTests(TestCase):
def setUp(self):
cache.set("KEY_DIDS", '1234', None)
# Create test users
self.admin_user = User.objects.create_superuser('admin@example.org', 'password')
self.admin_user.accept_gdpr=True

View File

@ -88,6 +88,9 @@ urlpatterns = [
path('user/terms/', views_user.TermsAndConditionsView.as_view(),
name='user_terms_and_conditions'),
path('waiting/', views_user.WaitingView.as_view(),
name='user_waiting'),
# Admin
path('admin/dashboard/', views_admin.DashboardView.as_view(),
name='admin_dashboard'),
@ -173,6 +176,7 @@ urlpatterns = [
name='admin_terms_and_conditions'),
path('admin/import/new', views_admin.ImportAddView.as_view(),
name='admin_import_add'),
path('admin/enc/', views_admin.EncryptionKeyView.as_view(), name='encryption_key'),
path('admin/auth/<uuid:admin2fauth>', views_admin.DobleFactorAuthView.as_view(),
name='admin_2fauth'),
path('admin/auth/2f/', DobleFactorSendView.as_view(), name='confirm_send_2f'),

View File

@ -1,3 +1,5 @@
import logging
from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
@ -6,6 +8,9 @@ from oidc4vp.models import Organization
from idhub_auth.models import User
logger = logging.getLogger(__name__)
class ProfileForm(forms.ModelForm):
MANDATORY_FIELDS = ['first_name', 'last_name', 'email']
@ -81,7 +86,6 @@ class RequestCredentialForm(forms.Form):
self.user = kwargs.pop('user', None)
self.lang = kwargs.pop('lang', None)
self._domain = kwargs.pop('domain', None)
self.password = kwargs.pop('password', None)
super().__init__(*args, **kwargs)
self.fields['did'].choices = [
(x.did, x.label) for x in DID.objects.filter(user=self.user)
@ -109,9 +113,9 @@ class RequestCredentialForm(forms.Form):
did = did[0]
cred = cred[0]
try:
if self.password:
cred.issue(did, self.password, domain=self._domain)
except Exception:
cred.issue(did, domain=self._domain)
except Exception as err:
logger.error(err)
return
if commit:

View File

@ -1,8 +1,6 @@
import os
import json
import base64
import qrcode
import logging
import datetime
import weasyprint
import qrcode.image.svg
@ -23,6 +21,7 @@ from django.views.generic.edit import (
)
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
@ -34,10 +33,7 @@ from idhub.user.tables import (
DIDTable,
CredentialsTable
)
from django.core.cache import cache
from django.conf import settings
from idhub.user.forms import (
ProfileForm,
RequestCredentialForm,
DemandAuthorizationForm,
TermsConditionsForm
@ -176,10 +172,25 @@ class TermsAndConditionsView(UserView, FormView):
return kwargs
def form_valid(self, form):
user = form.save()
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')
@ -209,7 +220,8 @@ class CredentialPdfView(MyWallet, TemplateView):
file_name = "certificate.pdf"
def get(self, request, *args, **kwargs):
self.admin_validated = cache.get("KEY_DIDS")
if not cache.get("KEY_DIDS"):
return redirect(reverse_lazy('idhub:user_dashboard'))
pk = kwargs['pk']
self.user = self.request.user
self.object = get_object_or_404(
@ -235,7 +247,7 @@ class CredentialPdfView(MyWallet, TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
this_folder = str(Path.cwd())
# 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:
@ -297,10 +309,9 @@ class CredentialPdfView(MyWallet, TemplateView):
def get_pfx_data(self):
did = self.object.eidas1_did
pw = self.admin_validated
if not did or not pw:
if not did:
return None, None
key_material = json.loads(did.get_key_material(pw))
key_material = json.loads(did.get_key_material())
cert = key_material.get("cert")
passphrase = key_material.get("passphrase")
if cert and passphrase:
@ -352,14 +363,7 @@ class CredentialJsonView(MyWallet, TemplateView):
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)
data = self.object.get_data()
response = HttpResponse(data, content_type="application/json")
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
return response
@ -374,7 +378,7 @@ class PublicCredentialJsonView(View):
hash=pk,
eidas1_did__isnull=False,
)
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
@ -388,8 +392,8 @@ class CredentialsRequestView(MyWallet, FormView):
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
if not self.admin_validated:
return redirect(reverse_lazy('idhub:user_dashboard'))
if not cache.get("KEY_DIDS"):
return redirect(reverse_lazy('idhub:user_waiting'))
return response
def get_form_kwargs(self):
@ -398,15 +402,6 @@ class CredentialsRequestView(MyWallet, FormView):
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):
@ -483,11 +478,7 @@ class DidRegisterView(MyWallet, CreateView):
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.instance.set_did()
form.save()
messages.success(self.request, _('DID created successfully'))
@ -511,7 +502,7 @@ class DidEditView(MyWallet, 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)

View File

@ -6,7 +6,6 @@ import zlib
import pyroaring
from django.conf import settings
from django.core.cache import cache
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
@ -18,7 +17,6 @@ from django.http import HttpResponseRedirect, HttpResponse, Http404
from idhub.models import DID, VerificableCredential
from idhub.email.views import NotifyActivateUserByEmail
from trustchain_idhub import settings
logger = logging.getLogger(__name__)
@ -46,30 +44,20 @@ class LoginView(auth_views.LoginView):
def form_valid(self, form):
user = form.get_user()
password = form.cleaned_data.get("password")
auth_login(self.request, user)
sensitive_data_encryption_key = user.decrypt_sensitive_data(password)
if user.is_anonymous:
return redirect(reverse_lazy("idhub:login"))
if not user.is_anonymous and user.is_admin:
admin_dashboard = reverse_lazy('idhub:admin_dashboard')
self.extra_context['success_url'] = admin_dashboard
# encryption_key = user.encrypt_data(
# sensitive_data_encryption_key,
# settings.SECRET_KEY
# )
# cache.set("KEY_DIDS", encryption_key, None)
cache.set("KEY_DIDS", sensitive_data_encryption_key, None)
if user.is_admin:
if settings.ENABLE_2FACTOR_AUTH:
self.request.session["2fauth"] = str(uuid.uuid4())
return redirect(reverse_lazy('idhub:confirm_send_2f'))
self.request.session["key_did"] = user.encrypt_data(
sensitive_data_encryption_key,
user.password+self.request.session._session_key
)
admin_dashboard = reverse_lazy('idhub:admin_dashboard')
self.extra_context['success_url'] = admin_dashboard
return HttpResponseRedirect(self.extra_context['success_url'])
return redirect(self.extra_context['success_url'])
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
@ -80,7 +68,6 @@ class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
password = form.cleaned_data.get("new_password1")
user = form.user
user.set_password(password)
user.set_encrypted_sensitive_data(password)
user.save()
return HttpResponseRedirect(self.success_url)

View File

@ -1,7 +1,7 @@
import nacl
import base64
from nacl import pwhash
from nacl import pwhash, secret
from django.db import models
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
@ -95,21 +95,24 @@ class User(AbstractBaseUser):
roles.append(r.name)
return ", ".join(set(roles))
def derive_key_from_password(self, password):
def derive_key_from_password(self, password=None):
if not password:
password = cache.get("KEY_DIDS").encode('utf-8')
kdf = pwhash.argon2i.kdf
ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE
mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE
return kdf(
nacl.secret.SecretBox.KEY_SIZE,
secret.SecretBox.KEY_SIZE,
password,
self.get_salt(),
opslimit=ops,
memlimit=mem
)
def decrypt_sensitive_data(self, password, data=None):
sb_key = self.derive_key_from_password(password.encode('utf-8'))
sb = nacl.secret.SecretBox(sb_key)
def decrypt_sensitive_data(self, data=None):
sb_key = self.derive_key_from_password()
sb = secret.SecretBox(sb_key)
if not data:
data = self.get_encrypted_sensitive_data()
if not isinstance(data, bytes):
@ -117,9 +120,9 @@ class User(AbstractBaseUser):
return sb.decrypt(data).decode('utf-8')
def encrypt_sensitive_data(self, password, data):
sb_key = self.derive_key_from_password(password.encode('utf-8'))
sb = nacl.secret.SecretBox(sb_key)
def encrypt_sensitive_data(self, data):
sb_key = self.derive_key_from_password()
sb = secret.SecretBox(sb_key)
if not isinstance(data, bytes):
data = data.encode('utf-8')
@ -134,32 +137,33 @@ class User(AbstractBaseUser):
def get_encrypted_sensitive_data(self):
return base64.b64decode(self.encrypted_sensitive_data.encode('utf-8'))
def set_encrypted_sensitive_data(self, password):
def set_encrypted_sensitive_data(self):
key = base64.b64encode(nacl.utils.random(64))
self.set_salt()
key_crypted = self.encrypt_sensitive_data(password, key)
key_crypted = self.encrypt_sensitive_data(key)
self.encrypted_sensitive_data = key_crypted
def encrypt_data(self, data, password):
sb = self.get_secret_box(password)
def encrypt_data(self, data):
sb = self.get_secret_box()
value_enc = sb.encrypt(data.encode('utf-8'))
return base64.b64encode(value_enc).decode('utf-8')
def decrypt_data(self, data, password):
sb = self.get_secret_box(password)
def decrypt_data(self, data):
sb = self.get_secret_box()
value = base64.b64decode(data.encode('utf-8'))
return sb.decrypt(value).decode('utf-8')
def get_secret_box(self, password):
pw = base64.b64decode(password.encode('utf-8')*4)
sb_key = self.derive_key_from_password(pw)
return nacl.secret.SecretBox(sb_key)
def get_secret_box(self):
sb_key = self.derive_key_from_password()
return secret.SecretBox(sb_key)
def change_password(self, old_password, new_password):
sensitive_data = self.decrypt_sensitive_data(old_password)
self.encrypted_sensitive_data = self.encrypt_sensitive_data(
new_password,
sensitive_data
)
self.set_password(new_password)
def change_password_key(self, new_password):
data = self.decrypt_sensitive_data()
sb_key = self.derive_key_from_password(new_password)
sb = secret.SecretBox(sb_key)
if not isinstance(data, bytes):
data = data.encode('utf-8')
encrypted_data = base64.b64encode(sb.encrypt(data)).decode('utf-8')
self.encrypted_sensitive_data = encrypted_data

View File

@ -1,14 +1,11 @@
import json
import requests
from django import forms
from django.conf import settings
from django.template.loader import get_template
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from utils.idhub_ssikit import create_verifiable_presentation
from oidc4vp.models import Organization
from idhub.models import VerificableCredential
@ -19,7 +16,6 @@ class AuthorizeForm(forms.Form):
self.user = kwargs.pop('user', None)
self.org = kwargs.pop('org', None)
self.code = kwargs.pop('code', None)
self.pw = kwargs.pop('pw', None)
self.presentation_definition = kwargs.pop('presentation_definition', [])
self.subject_did = None
@ -53,7 +49,6 @@ class AuthorizeForm(forms.Form):
cred = self.user.decrypt_data(
c.data,
self.pw
)
self.subject_did = c.subject_did
self.list_credentials.append(cred)
@ -85,5 +80,5 @@ class AuthorizeForm(forms.Form):
"verifiable_credential_list": vc_list
}
unsigned_vp = vp_template.render(context)
key_material = did.get_key_material(self.pw)
key_material = did.get_key_material()
self.vp = create_verifiable_presentation(key_material, unsigned_vp)

View File

@ -5,7 +5,6 @@ import secrets
from django.conf import settings
from django.http import QueryDict
from django.utils.translation import gettext_lazy as _
from django.shortcuts import get_object_or_404
from idhub_auth.models import User
from django.db import models
from utils.idhub_ssikit import verify_presentation

View File

@ -16,7 +16,6 @@ from idhub.mixins import UserView
from idhub.models import Event
from oidc4vp.forms import AuthorizeForm
from utils.idhub_ssikit import verify_presentation
class AuthorizeView(UserView, FormView):
@ -39,16 +38,11 @@ class AuthorizeView(UserView, FormView):
kwargs['user'] = self.request.user
try:
vps = json.loads(self.request.GET.get('presentation_definition'))
except:
except Exception:
vps = []
kwargs['presentation_definition'] = vps
kwargs["org"] = self.get_org()
kwargs["code"] = self.request.GET.get('code')
enc_pw = self.request.session["key_did"]
kwargs['pw'] = self.request.user.decrypt_data(
enc_pw,
self.request.user.password+self.request.session._session_key
)
return kwargs
def get_form(self, form_class=None):
@ -64,7 +58,7 @@ class AuthorizeView(UserView, FormView):
return redirect(self.success_url)
try:
authorization = authorization.json()
except:
except Exception:
messages.error(self.request, _("Error sending credential!"))
return redirect(self.success_url)
@ -148,7 +142,6 @@ class VerifyView(View):
if len(auth_data) == 2 and auth_data[0].lower() == 'basic':
decoded_auth = base64.b64decode(auth_data[1]).decode('utf-8')
client_id, client_secret = decoded_auth.split(':', 1)
org_url = request.GET.get('demand_uri')
org = get_object_or_404(
Organization,
client_id=client_id,

View File

@ -212,13 +212,11 @@ LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {"class": "logging.StreamHandler"},
"console": {"level": "DEBUG", "class": "logging.StreamHandler"},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
},
"root": {
"handlers": ["console"],
"level": "DEBUG",
}
}