diff --git a/apiregiter.py b/apiregiter.py new file mode 100644 index 0000000..e794181 --- /dev/null +++ b/apiregiter.py @@ -0,0 +1,17 @@ +import uuid +import hashlib + + +class Iota: + """ + Framework for simulate the comunication with IOTA DLT + """ + + def issue_did(self): + u = str(uuid.uuid4()).encode() + d = hashlib.sha3_256(u).hexdigest() + did = "did:iota:{}".format(d) + return did + + +iota = Iota() diff --git a/examples/import_credentials.csv b/examples/import_credentials.csv new file mode 100644 index 0000000..4a06714 --- /dev/null +++ b/examples/import_credentials.csv @@ -0,0 +1,2 @@ +name email membershipType +Pepe user1@example.org individual diff --git a/idhub/admin.py b/idhub/admin.py deleted file mode 100644 index 049dca3..0000000 --- a/idhub/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from .models import AppUser - -admin.site.register(AppUser) diff --git a/idhub/admin/forms.py b/idhub/admin/forms.py index fc66f14..22b4954 100644 --- a/idhub/admin/forms.py +++ b/idhub/admin/forms.py @@ -1,27 +1,9 @@ from django import forms -from django.contrib.auth.models import User -from idhub.models import Rol -class ProfileForm(forms.ModelForm): - MANDATORY_FIELDS = ['first_name', 'last_name', 'email', 'username'] - - class Meta: - model = User - fields = ('first_name', 'last_name', 'email') +class ImportForm(forms.Form): + file_import = forms.FileField() -class MembershipForm(forms.ModelForm): - MANDATORY_FIELDS = ['type'] - - -class RolForm(forms.ModelForm): - MANDATORY_FIELDS = ['name'] - - -class ServiceForm(forms.ModelForm): - MANDATORY_FIELDS = ['domain', 'rol'] - - -class UserRolForm(forms.ModelForm): - MANDATORY_FIELDS = ['service'] +class SchemaForm(forms.Form): + file_template = forms.FileField() diff --git a/idhub/admin/views.py b/idhub/admin/views.py index 13031b0..9e5b4e7 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -1,22 +1,35 @@ +import os +import csv +import json +import copy import logging +import pandas as pd +from pathlib import Path +from jsonschema import validate from smtplib import SMTPException +from django.conf import settings from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateView -from django.views.generic.edit import UpdateView, CreateView -from django.contrib.auth.models import User +from django.views.generic.edit import UpdateView, CreateView, DeleteView 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 idhub.models import Membership, Rol, Service, UserRol +from apiregiter import iota +from idhub_auth.models import User from idhub.mixins import AdminView from idhub.email.views import NotifyActivateUserByEmail -from idhub.admin.forms import ( - ProfileForm, - MembershipForm, - RolForm, - ServiceForm, - UserRolForm +from idhub.admin.forms import ImportForm, SchemaForm +from idhub.models import ( + DID, + File_datas, + Membership, + Rol, + Service, + Schemas, + UserRol, + VerificableCredential, ) @@ -42,9 +55,9 @@ class Credentials(AdminView, TemplateView): section = "Credentials" -class Schemes(AdminView, TemplateView): - title = _("Schemes Management") - section = "Schemes" +class SchemasMix(AdminView, TemplateView): + title = _("Templates Management") + section = "Templates" class ImportExport(AdminView, TemplateView): @@ -118,8 +131,7 @@ class AdminPeopleDeleteView(AdminPeopleView): class AdminPeopleEditView(AdminPeopleView, UpdateView): template_name = "idhub/admin/user_edit.html" - from_class = ProfileForm - fields = ('first_name', 'last_name', 'email', 'username') + fields = ('first_name', 'last_name', 'email') success_url = reverse_lazy('idhub:admin_people_list') @@ -128,8 +140,7 @@ class AdminPeopleRegisterView(NotifyActivateUserByEmail, People, CreateView): subtitle = _('People Register') icon = 'bi bi-person' model = User - from_class = ProfileForm - fields = ('first_name', 'last_name', 'email', 'username') + fields = ('first_name', 'last_name', 'email') success_url = reverse_lazy('idhub:admin_people_list') def get_success_url(self): @@ -155,7 +166,6 @@ class AdminPeopleMembershipRegisterView(People, CreateView): subtitle = _('People add membership') icon = 'bi bi-person' model = Membership - from_class = MembershipForm fields = ('type', 'start_date', 'end_date') success_url = reverse_lazy('idhub:admin_people_list') @@ -193,7 +203,6 @@ class AdminPeopleMembershipEditView(People, CreateView): subtitle = _('People add membership') icon = 'bi bi-person' model = Membership - from_class = MembershipForm fields = ('type', 'start_date', 'end_date') success_url = reverse_lazy('idhub:admin_people_list') @@ -232,7 +241,6 @@ class AdminPeopleRolRegisterView(People, CreateView): subtitle = _('Add Rol to User') icon = 'bi bi-person' model = UserRol - from_class = UserRolForm fields = ('service',) def get(self, request, *args, **kwargs): @@ -263,7 +271,6 @@ class AdminPeopleRolEditView(People, CreateView): subtitle = _('Edit Rol to User') icon = 'bi bi-person' model = UserRol - from_class = UserRolForm fields = ('service',) def get_form_kwargs(self): @@ -311,7 +318,6 @@ class AdminRolRegisterView(AccessControl, CreateView): subtitle = _('Add Rol') icon = '' model = Rol - from_class = RolForm fields = ('name',) success_url = reverse_lazy('idhub:admin_roles') object = None @@ -322,7 +328,6 @@ class AdminRolEditView(AccessControl, CreateView): subtitle = _('Edit Rol') icon = '' model = Rol - from_class = RolForm fields = ('name',) success_url = reverse_lazy('idhub:admin_roles') @@ -362,7 +367,6 @@ class AdminServiceRegisterView(AccessControl, CreateView): subtitle = _('Add Service') icon = '' model = Service - from_class = ServiceForm fields = ('domain', 'description', 'rol') success_url = reverse_lazy('idhub:admin_services') object = None @@ -373,7 +377,6 @@ class AdminServiceEditView(AccessControl, CreateView): subtitle = _('Edit Service') icon = '' model = Service - from_class = ServiceForm fields = ('domain', 'description', 'rol') success_url = reverse_lazy('idhub:admin_services') @@ -401,11 +404,31 @@ class AdminCredentialsView(Credentials): subtitle = _('Credentials list') icon = '' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'credentials': VerificableCredential.objects, + }) + return context -class AdminIssueCredentialsView(Credentials): + +class AdminCredentialView(Credentials): template_name = "idhub/admin/issue_credentials.html" - subtitle = _('Issuance of Credentials') + subtitle = _('Change status of Credential') icon = '' + model = VerificableCredential + + 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 get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'object': self.object, + }) + return context class AdminRevokeCredentialsView(Credentials): @@ -414,12 +437,89 @@ class AdminRevokeCredentialsView(Credentials): icon = '' -class AdminWalletIdentitiesView(Credentials): - template_name = "idhub/admin/wallet_identities.html" +class AdminDidsView(Credentials): + template_name = "idhub/admin/dids.html" subtitle = _('Organization Identities (DID)') icon = 'bi bi-patch-check-fill' wallet = True + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'dids': DID.objects, + }) + return context + +class AdminDidRegisterView(Credentials, CreateView): + template_name = "idhub/admin/did_register.html" + subtitle = _('Add a new Organization Identities (DID)') + icon = 'bi bi-patch-check-fill' + wallet = True + model = DID + fields = ('did', 'label') + success_url = reverse_lazy('idhub:admin_dids') + object = None + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['initial'] = { + 'did': iota.issue_did() + } + return kwargs + + def get_form(self): + form = super().get_form() + form.fields['did'].required = False + form.fields['did'].disabled = True + return form + + def form_valid(self, form): + form.save() + messages.success(self.request, _('DID created successfully')) + return super().form_valid(form) + + +class AdminDidEditView(Credentials, UpdateView): + template_name = "idhub/admin/did_register.html" + subtitle = _('Organization Identities (DID)') + icon = 'bi bi-patch-check-fill' + wallet = True + model = DID + fields = ('did', 'label') + success_url = reverse_lazy('idhub:admin_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 get_form(self): + form = super().get_form() + form.fields['did'].required = False + form.fields['did'].disabled = True + return form + + def form_valid(self, form): + user = form.save() + messages.success(self.request, _('DID updated successfully')) + return super().form_valid(form) + + +class AdminDidDeleteView(Credentials, DeleteView): + subtitle = _('Organization Identities (DID)') + icon = 'bi bi-patch-check-fill' + wallet = True + model = DID + success_url = reverse_lazy('idhub:admin_dids') + + def get(self, request, *args, **kwargs): + self.pk = kwargs['pk'] + self.object = get_object_or_404(self.model, pk=self.pk) + self.object.delete() + messages.success(self.request, _('DID delete successfully')) + + return redirect(self.success_url) + class AdminWalletCredentialsView(Credentials): template_name = "idhub/admin/wallet_credentials.html" @@ -435,22 +535,136 @@ class AdminWalletConfigIssuesView(Credentials): wallet = True -class AdminSchemesView(Schemes): - template_name = "idhub/admin/schemes.html" - subtitle = _('Schemes List') +class AdminSchemasView(SchemasMix): + template_name = "idhub/admin/schemas.html" + subtitle = _('Template List') icon = '' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'schemas': Schemas.objects, + }) + return context -class AdminSchemesImportView(Schemes): - template_name = "idhub/admin/schemes_import.html" - subtitle = _('Import Schemes') + +class AdminSchemasDeleteView(SchemasMix): + + def get(self, request, *args, **kwargs): + self.pk = kwargs['pk'] + self.object = get_object_or_404(Schemas, pk=self.pk) + self.object.delete() + + return redirect('idhub:admin_schemas') + + +class AdminSchemasDownloadView(SchemasMix): + + def get(self, request, *args, **kwargs): + self.pk = kwargs['pk'] + self.object = get_object_or_404(Schemas, pk=self.pk) + + response = HttpResponse(self.object.data, content_type="application/json") + response['Content-Disposition'] = 'inline; filename={}'.format(self.object.file_schema) + return response + + +class AdminSchemasNewView(SchemasMix): + template_name = "idhub/admin/schemas_new.html" + subtitle = _('Upload Template') + icon = '' + success_url = reverse_lazy('idhub:admin_schemas') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'form': SchemaForm(), + }) + return context + + def post(self, request, *args, **kwargs): + form = SchemaForm(request.POST, request.FILES) + if form.is_valid(): + schema = self.handle_uploaded_file() + if not schema: + messages.error(request, _("There are some errors in the file")) + return super().get(request, *args, **kwargs) + return redirect(self.success_url) + else: + return super().get(request, *args, **kwargs) + + return super().post(request, *args, **kwargs) + + def handle_uploaded_file(self): + f = self.request.FILES.get('file_template') + if not f: + return + file_name = f.name + if Schemas.objects.filter(file_schema=file_name).exists(): + messages.error(self.request, _("This template already exists!")) + return + try: + data = f.read().decode('utf-8') + json.loads(data) + except Exception: + messages.error(self.request, _('This is not a schema valid!')) + return + schema = Schemas.objects.create(file_schema=file_name, data=data) + schema.save() + return schema + + +class AdminSchemasImportView(SchemasMix): + template_name = "idhub/admin/schemas_import.html" + subtitle = _('Import Template') icon = '' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'schemas': self.get_schemas(), + }) + return context -class AdminSchemesExportView(Schemes): - template_name = "idhub/admin/schemes_export.html" - subtitle = _('Export Schemes') - icon = '' + def get_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()] + return schemas + + +class AdminSchemasImportAddView(SchemasMix): + + def get(self, request, *args, **kwargs): + file_name = kwargs['file_schema'] + schemas_files = os.listdir(settings.SCHEMAS_DIR) + if not file_name in schemas_files: + messages.error(self.request, f"The schema {file_name} not exist!") + return redirect('idhub:admin_schemas_import') + + schema = self.create_schema(file_name) + if schema: + messages.success(self.request, _("The schema add successfully!")) + return redirect('idhub:admin_schemas_import') + + def create_schema(self, file_name): + data = self.open_file(file_name) + try: + json.loads(data) + except Exception: + messages.error(self.request, _('This is not a schema valid!')) + return + schema = Schemas.objects.create(file_schema=file_name, data=data) + schema.save() + return schema + + def open_file(self, file_name): + data = '' + filename = Path(settings.SCHEMAS_DIR).joinpath(file_name) + with filename.open() as schema_file: + data = schema_file.read() + + return data class AdminImportView(ImportExport): @@ -458,8 +672,114 @@ class AdminImportView(ImportExport): subtitle = _('Import') icon = '' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'dates': File_datas.objects, + }) + return context -class AdminExportView(ImportExport): - template_name = "idhub/admin/export.html" - subtitle = _('Export') + +class AdminImportStep2View(ImportExport): + template_name = "idhub/admin/import_step2.html" + subtitle = _('Import') icon = '' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'schemas': Schemas.objects, + }) + return context + + +class AdminImportStep3View(ImportExport): + template_name = "idhub/admin/import_step3.html" + subtitle = _('Import') + icon = '' + success_url = reverse_lazy('idhub:admin_import') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'form': ImportForm(), + }) + return context + + def post(self, request, *args, **kwargs): + self.pk = kwargs['pk'] + self.schema = get_object_or_404(Schemas, pk=self.pk) + form = ImportForm(request.POST, request.FILES) + if form.is_valid(): + result = self.handle_uploaded_file() + if not result: + messages.error(request, _("There are some errors in the file")) + return super().get(request, *args, **kwargs) + return redirect(self.success_url) + else: + return super().get(request, *args, **kwargs) + + return super().post(request, *args, **kwargs) + + def handle_uploaded_file(self): + f = self.request.FILES.get('file_import') + if not f: + messages.error(self.request, _("There aren't file")) + return + + file_name = f.name + if File_datas.objects.filter(file_name=file_name, success=True).exists(): + messages.error(self.request, _("This file already exists!")) + return + + self.json_schema = json.loads(self.schema.data) + df = pd.read_csv (f, delimiter="\t", quotechar='"', quoting=csv.QUOTE_ALL) + data_pd = df.fillna('').to_dict() + rows = {} + + if not data_pd: + File_datas.objects.create(file_name=file_name, success=False) + return + + for n in range(df.last_valid_index()+1): + row = {} + for k in data_pd.keys(): + row[k] = data_pd[k][n] + + user = self.validate(n, row) + if not user: + File_datas.objects.create(file_name=file_name, success=False) + return + + rows[user] = row + + File_datas.objects.create(file_name=file_name) + for k, v in rows.items(): + self.create_credential(k, v) + + return True + + def validate(self, line, row): + try: + validate(instance=row, schema=self.json_schema) + except Exception as e: + messages.error(self.request, "line {}: {}".format(line+1, e)) + return + + user = User.objects.filter(email=row.get('email')) + if not user: + txt = _('The user not exist!') + messages.error(self.request, "line {}: {}".format(line+1, txt)) + return + + return user.first() + + def create_credential(self, user, row): + d = copy.copy(self.json_schema) + d['instance'] = row + return VerificableCredential.objects.create( + verified=False, + user=user, + data=json.dumps(d) + ) + diff --git a/idhub/management/__init__.py b/idhub/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/idhub/management/commands/__init__.py b/idhub/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py new file mode 100644 index 0000000..71506c6 --- /dev/null +++ b/idhub/management/commands/initial_datas.py @@ -0,0 +1,39 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth import get_user_model +from idhub.models import Organization + + +User = get_user_model() + + +class Command(BaseCommand): + help = "Insert minimum datas for the project" + + def handle(self, *args, **kwargs): + admin = 'admin@example.org' + pw_admin = '1234' + + user = 'user1@example.org' + pw_user = '1234' + organization = [ + ("ExO", "https://verify.exo.cat"), + ("Somos Connexión", "https://verify.somosconexion.coop") + ] + + # self.create_admin_users(admin, pw_admin) + self.create_users(user, pw_user) + for o in organization: + self.create_organizations(*o) + + def create_admin_users(self, email, password): + User.objects.create_superuser(email=email, password=password) + + + def create_users(self, email, password): + u= User.objects.create(email=email, password=password) + u.set_password(password) + u.save() + + + def create_organizations(self, name, url): + Organization.objects.create(name=name, url=url) diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py index 81b98e5..fe6fc19 100644 --- a/idhub/migrations/0001_initial.py +++ b/idhub/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-10-16 09:41 +# Generated by Django 4.2.5 on 2023-11-02 15:08 from django.conf import settings from django.db import migrations, models @@ -14,115 +14,247 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="VCTemplate", + name='File_datas', fields=[ ( - "id", + 'id', models.BigAutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name="ID", + verbose_name='ID', ), ), - ("wkit_template_id", models.CharField(max_length=250)), - ("data", models.TextField()), + ('file_name', models.CharField(max_length=250)), + ('success', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now=True)), ], ), migrations.CreateModel( - name="VerifiableCredential", + name='Organization', fields=[ ( - "id", + 'id', models.BigAutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name="ID", + verbose_name='ID', ), ), - ("id_string", models.CharField(max_length=250)), - ("verified", models.BooleanField()), - ("created_on", models.DateTimeField()), - ("did_issuer", models.CharField(max_length=250)), - ("did_subject", models.CharField(max_length=250)), - ("data", models.TextField()), + ('name', models.CharField(max_length=250)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="vcredentials", - to=settings.AUTH_USER_MODEL, + 'url', + models.CharField( + help_text='Url where to send the presentation', max_length=250 ), ), ], ), migrations.CreateModel( - name="Membership", + name='Rol', fields=[ ( - "id", + 'id', models.BigAutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name="ID", + verbose_name='ID', ), ), + ('name', models.CharField(max_length=250)), + ], + ), + migrations.CreateModel( + name='Schemas', + fields=[ ( - "type", + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('file_schema', models.CharField(max_length=250)), + ('data', models.TextField()), + ('created_at', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='Service', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('domain', models.CharField(max_length=250)), + ('description', models.CharField(max_length=250)), + ('rol', models.ManyToManyField(to='idhub.rol')), + ], + ), + migrations.CreateModel( + name='VCTemplate', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('wkit_template_id', models.CharField(max_length=250)), + ('data', models.TextField()), + ], + ), + migrations.CreateModel( + name='VerificableCredential', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('id_string', models.CharField(max_length=250)), + ('verified', models.BooleanField()), + ('created_on', models.DateTimeField(auto_now=True)), + ('issuer_on', models.DateTimeField(null=True)), + ('did_issuer', models.CharField(max_length=250)), + ('did_subject', models.CharField(max_length=250)), + ('data', models.TextField()), + ( + 'status', models.PositiveSmallIntegerField( - choices=[(1, "Beneficiary"), (2, "Employee"), (3, "Partner")], - verbose_name="Type of membership", + choices=[ + (1, 'Enabled'), + (2, 'Issued'), + (3, 'Revoked'), + (4, 'Expired'), + ], + default=1, ), ), ( - "start_date", - models.DateField( - blank=True, - help_text="What date did the membership start?", - null=True, - verbose_name="Start date", - ), - ), - ( - "end_date", - models.DateField( - blank=True, - help_text="What date did the membership end?", - null=True, - verbose_name="End date", - ), - ), - ( - "user", + 'user', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="memberships", + related_name='vcredentials', to=settings.AUTH_USER_MODEL, ), ), ], ), migrations.CreateModel( - name="DID", + name='UserRol', fields=[ ( - "id", + 'id', models.BigAutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name="ID", + verbose_name='ID', ), ), - ("did_string", models.CharField(max_length=250)), - ("label", models.CharField(max_length=50)), ( - "user", + 'service', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="dids", + related_name='users', + to='idhub.service', + ), + ), + ( + 'user', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='roles', + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name='Membership', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'type', + models.PositiveSmallIntegerField( + choices=[(1, 'Beneficiary'), (2, 'Employee'), (3, 'Partner')], + verbose_name='Type of membership', + ), + ), + ( + 'start_date', + models.DateField( + blank=True, + help_text='What date did the membership start?', + null=True, + verbose_name='Start date', + ), + ), + ( + 'end_date', + models.DateField( + blank=True, + help_text='What date did the membership end?', + null=True, + verbose_name='End date', + ), + ), + ( + 'user', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='memberships', + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name='DID', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('created_at', models.DateTimeField(auto_now=True)), + ('did', models.CharField(max_length=250, unique=True)), + ('label', models.CharField(max_length=50)), + ( + 'user', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='dids', to=settings.AUTH_USER_MODEL, ), ), diff --git a/idhub/migrations/0002_rol.py b/idhub/migrations/0002_rol.py deleted file mode 100644 index 41f8aba..0000000 --- a/idhub/migrations/0002_rol.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.5 on 2023-10-17 11:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("idhub", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="Rol", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=250)), - ], - ), - ] diff --git a/idhub/migrations/0003_service.py b/idhub/migrations/0003_service.py deleted file mode 100644 index 2c908de..0000000 --- a/idhub/migrations/0003_service.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 4.2.5 on 2023-10-17 13:29 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ("idhub", "0002_rol"), - ] - - operations = [ - migrations.CreateModel( - name="Service", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("domain", models.CharField(max_length=250)), - ("description", models.CharField(max_length=250)), - ( - "rol", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="services", - to="idhub.rol", - ), - ), - ], - ), - ] diff --git a/idhub/migrations/0004_userrol.py b/idhub/migrations/0004_userrol.py deleted file mode 100644 index 5955a70..0000000 --- a/idhub/migrations/0004_userrol.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 4.2.5 on 2023-10-17 14:24 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("idhub", "0003_service"), - ] - - operations = [ - migrations.CreateModel( - name="UserRol", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "service", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="users", - to="idhub.service", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="roles", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/idhub/migrations/0005_remove_service_rol_service_rol.py b/idhub/migrations/0005_remove_service_rol_service_rol.py deleted file mode 100644 index a383ca4..0000000 --- a/idhub/migrations/0005_remove_service_rol_service_rol.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.5 on 2023-10-19 13:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("idhub", "0004_userrol"), - ] - - operations = [ - migrations.RemoveField( - model_name="service", - name="rol", - ), - migrations.AddField( - model_name="service", - name="rol", - field=models.ManyToManyField(to="idhub.rol"), - ), - ] diff --git a/idhub/mixins.py b/idhub/mixins.py index c888327..68b7344 100644 --- a/idhub/mixins.py +++ b/idhub/mixins.py @@ -26,7 +26,7 @@ class UserView(LoginRequiredMixin): class AdminView(UserView): def get(self, request, *args, **kwargs): - if not request.user.is_superuser: + if not request.user.is_admin: url = reverse_lazy('idhub:user_dashboard') return redirect(url) diff --git a/idhub/models.py b/idhub/models.py index 3c187b3..8a805d4 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -1,15 +1,8 @@ +import json +import requests from django.db import models from django.utils.translation import gettext_lazy as _ -from django.contrib.auth.models import User - - -# class AppUser(models.Model): - # Ya incluye "first_name", "last_name", "email", y "date_joined" heredando de la clase User de django. - # Falta ver que más información hay que añadir a nuestros usuarios, como los roles etc. - # django_user = models.OneToOneField(DjangoUser, on_delete=models.CASCADE) - - # Extra data, segun entidad/organizacion - # pass +from idhub_auth.models import User # class Event(models.Model): @@ -20,35 +13,103 @@ from django.contrib.auth.models import User class DID(models.Model): - did_string = models.CharField(max_length=250) + created_at = models.DateTimeField(auto_now=True) + did = models.CharField(max_length=250, unique=True) label = models.CharField(max_length=50) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='dids', + null=True, ) # kind = "KEY|WEB" + @property + def is_organization_did(self): + if not self.user: + return True + return False + + +class Schemas(models.Model): + file_schema = models.CharField(max_length=250) + data = models.TextField() + created_at = models.DateTimeField(auto_now=True) + + @property + def get_schema(self): + if not self.data: + return {} + return json.loads(self.data) + + def name(self): + return self.get_schema.get('name', '') + + def description(self): + return self.get_schema.get('description', '') + + +class VerificableCredential(models.Model): + """ + Definition of Verificable Credentials + """ + class Status(models.IntegerChoices): + ENABLED = 1, _("Enabled") + ISSUED = 2, _("Issued") + REVOKED = 3, _("Revoked") + EXPIRED = 4, _("Expired") -class VerifiableCredential(models.Model): id_string = models.CharField(max_length=250) verified = models.BooleanField() - created_on = models.DateTimeField() + created_on = models.DateTimeField(auto_now=True) + issuer_on = models.DateTimeField(null=True) did_issuer = models.CharField(max_length=250) did_subject = models.CharField(max_length=250) + data = models.TextField() + status = models.PositiveSmallIntegerField( + choices=Status.choices, + default=Status.ENABLED + ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='vcredentials', ) - data = models.TextField() + @property + def get_schema(self): + if not self.data: + return {} + return json.loads(self.data) + + def type(self): + return self.get_schema.get('name', '') + + def description(self): + return self.get_schema.get('description', '') + + def get_status(self): + return self.Status(self.status).label + + def get_datas(self): + data = json.loads(self.data).get('instance').items() + return data + + def get_issued(self, did): + self.status = self.Status.ISSUED + self.did_subject = did class VCTemplate(models.Model): wkit_template_id = models.CharField(max_length=250) data = models.TextField() +class File_datas(models.Model): + file_name = models.CharField(max_length=250) + success = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now=True) + + class Membership(models.Model): """ This model represent the relation of this user with the ecosystem. @@ -100,7 +161,7 @@ class Service(models.Model): return ", ".join([x.name for x in self.rol.all()]) def __str__(self): - return "{} -> {}".format(self.domain, self.rol.name) + return "{} -> {}".format(self.domain, self.get_roles()) class UserRol(models.Model): @@ -114,3 +175,18 @@ class UserRol(models.Model): on_delete=models.CASCADE, related_name='users', ) + + +class Organization(models.Model): + name = models.CharField(max_length=250) + url = models.CharField( + help_text=_("Url where to send the presentation"), + max_length=250 + ) + + def __str__(self): + return self.name + + def send(self, cred): + return + requests.post(self.url, data=cred.data) \ No newline at end of file diff --git a/idhub/templates/idhub/admin/credentials.html b/idhub/templates/idhub/admin/credentials.html index f5849fd..0d7e84c 100644 --- a/idhub/templates/idhub/admin/credentials.html +++ b/idhub/templates/idhub/admin/credentials.html @@ -6,4 +6,34 @@ {{ subtitle }} +
+
+
+ + + + + + + + + + + + + {% for f in credentials.all %} + + + + + + + + + {% endfor %} + +
{{ f.type }}{{ f.description }}{{ f.issue_on }}{{ f.get_status }}{{ f.user.email }}{% trans 'View' %}
+
+
+
{% endblock %} diff --git a/idhub/templates/idhub/admin/did_register.html b/idhub/templates/idhub/admin/did_register.html new file mode 100644 index 0000000..a4fe719 --- /dev/null +++ b/idhub/templates/idhub/admin/did_register.html @@ -0,0 +1,34 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +
+
+ {% bootstrap_form form %} +
+
+
+ {% translate "Cancel" %} + +
+ +
+{% endblock %} diff --git a/idhub/templates/idhub/admin/dids.html b/idhub/templates/idhub/admin/dids.html new file mode 100644 index 0000000..00765af --- /dev/null +++ b/idhub/templates/idhub/admin/dids.html @@ -0,0 +1,60 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+ + + + + + + + + + + + {% for d in dids.all %} + + + + + + + + {% endfor %} + +
{{ d.created_at }}{{ d.label }}{{ d.did }}
+ +
+
+
+ +{% for d in dids.all %} + +{% endfor %} +{% endblock %} diff --git a/idhub/templates/idhub/admin/import.html b/idhub/templates/idhub/admin/import.html index f5849fd..a1b77be 100644 --- a/idhub/templates/idhub/admin/import.html +++ b/idhub/templates/idhub/admin/import.html @@ -6,4 +6,31 @@ {{ subtitle }} +
+
+
+ + + + + + + + + + {% for f in dates.all %} + + + + + + {% endfor %} + +
{{ f.created_at }}{{ f.file_name }}{% if f.success %}{% else %}{% endif %}
+ +
+
+
{% endblock %} diff --git a/idhub/templates/idhub/admin/import_step2.html b/idhub/templates/idhub/admin/import_step2.html new file mode 100644 index 0000000..f18821f --- /dev/null +++ b/idhub/templates/idhub/admin/import_step2.html @@ -0,0 +1,34 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+ + + + + + + + + + + {% for schema in schemas.all %} + + + + + + {% endfor %} + +
{{ schema.created_at }}{{ schema.file_schema }}{% trans 'Import Dates' %}
+
+
+
+{% endblock %} diff --git a/idhub/templates/idhub/admin/import_step3.html b/idhub/templates/idhub/admin/import_step3.html new file mode 100644 index 0000000..7cb6270 --- /dev/null +++ b/idhub/templates/idhub/admin/import_step3.html @@ -0,0 +1,32 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} +
+ {% translate "Cancel" %} + +
+ +
+{% endblock %} diff --git a/idhub/templates/idhub/admin/issue_credentials.html b/idhub/templates/idhub/admin/issue_credentials.html index f5849fd..ccb48a4 100644 --- a/idhub/templates/idhub/admin/issue_credentials.html +++ b/idhub/templates/idhub/admin/issue_credentials.html @@ -2,8 +2,55 @@ {% load i18n %} {% block content %} -

- - {{ subtitle }} -

+
+
+

+ + {{ subtitle }} +

+
+
+ {% if object.get_status == 'Issued' %} + {% trans 'Revoke' %} + {% trans 'Delete' %} + {% endif %} +
+
+
+
+
+
+ {% for k, v in object.get_datas %} +
+
+ {{ k|capfirst }}: +
+
+ {{ v }} +
+
+ {% endfor %} +
+
+ Date of Issue: +
+
+ {{ object.issuer_on|default_if_none:"" }} +
+
+
+
+ Status: +
+
+ {{ object.get_status}} +
+
+ +
+
{% endblock %} diff --git a/idhub/templates/idhub/admin/people.html b/idhub/templates/idhub/admin/people.html index ff19975..2df8577 100644 --- a/idhub/templates/idhub/admin/people.html +++ b/idhub/templates/idhub/admin/people.html @@ -12,7 +12,7 @@ - + @@ -23,7 +23,7 @@ {{ user.last_name }} {{ user.first_name }} - {{ user.username }} + {{ user.email }} {% for m in user.memberships.all %} {{ m.get_type }} diff --git a/idhub/templates/idhub/admin/schemas.html b/idhub/templates/idhub/admin/schemas.html new file mode 100644 index 0000000..2aa2e88 --- /dev/null +++ b/idhub/templates/idhub/admin/schemas.html @@ -0,0 +1,62 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+ + + + + + + + + + + + + {% for schema in schemas.all %} + + + + + + + + + {% endfor %} + +
{{ schema.created_at }}{{ schema.file_schema }}{{ schema.name }}{{ schema.description }}{% trans 'View' %}
+ +
+
+
+ +{% for schema in schemas.all %} + +{% endfor %} +{% endblock %} diff --git a/idhub/templates/idhub/admin/schemes.html b/idhub/templates/idhub/admin/schemas_export.html similarity index 100% rename from idhub/templates/idhub/admin/schemes.html rename to idhub/templates/idhub/admin/schemas_export.html diff --git a/idhub/templates/idhub/admin/schemas_import.html b/idhub/templates/idhub/admin/schemas_import.html new file mode 100644 index 0000000..b20608b --- /dev/null +++ b/idhub/templates/idhub/admin/schemas_import.html @@ -0,0 +1,34 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+ + + + + + + + + {% for schema in schemas %} + + + + + {% endfor %} + +
{{ schema }}
+ +
+
+
+{% endblock %} diff --git a/idhub/templates/idhub/admin/schemas_new.html b/idhub/templates/idhub/admin/schemas_new.html new file mode 100644 index 0000000..83ad176 --- /dev/null +++ b/idhub/templates/idhub/admin/schemas_new.html @@ -0,0 +1,32 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} +
+ {% translate "Cancel" %} + +
+ +
+{% endblock %} diff --git a/idhub/templates/idhub/admin/schemes_export.html b/idhub/templates/idhub/admin/schemes_export.html deleted file mode 100644 index f5849fd..0000000 --- a/idhub/templates/idhub/admin/schemes_export.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "idhub/base_admin.html" %} -{% load i18n %} - -{% block content %} -

- - {{ subtitle }} -

-{% endblock %} diff --git a/idhub/templates/idhub/admin/schemes_import.html b/idhub/templates/idhub/admin/schemes_import.html deleted file mode 100644 index f5849fd..0000000 --- a/idhub/templates/idhub/admin/schemes_import.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "idhub/base_admin.html" %} -{% load i18n %} - -{% block content %} -

- - {{ subtitle }} -

-{% endblock %} diff --git a/idhub/templates/idhub/admin/user.html b/idhub/templates/idhub/admin/user.html index 3bee80f..410146f 100644 --- a/idhub/templates/idhub/admin/user.html +++ b/idhub/templates/idhub/admin/user.html @@ -109,7 +109,7 @@ diff --git a/idhub/templates/idhub/user/credential.html b/idhub/templates/idhub/user/credential.html new file mode 100644 index 0000000..4593adf --- /dev/null +++ b/idhub/templates/idhub/user/credential.html @@ -0,0 +1,46 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+
+ {% for k, v in object.get_datas %} +
+
+ {{ k|capfirst }}: +
+
+ {{ v }} +
+
+ {% endfor %} +
+
+ Date of Issue: +
+
+ {{ object.issuer_on|default_if_none:"" }} +
+
+
+
+ Status: +
+
+ {{ object.get_status}} +
+
+ +
+
+{% endblock %} diff --git a/idhub/templates/idhub/user/credentials.html b/idhub/templates/idhub/user/credentials.html index d6520a0..f6f694d 100644 --- a/idhub/templates/idhub/user/credentials.html +++ b/idhub/templates/idhub/user/credentials.html @@ -2,4 +2,40 @@ {% load i18n %} {% block content %} +

+ + {{ subtitle }} +

+
+
+
+ + + + + + + + + + + + {% for f in credentials.all %} + + + + + + + + {% endfor %} + +
{{ f.type }}{{ f.description }}{{ f.issue_on }}{{ f.get_status }} + + + +
+
+
+
{% endblock %} diff --git a/idhub/templates/idhub/user/credentials_presentation.html b/idhub/templates/idhub/user/credentials_presentation.html index d6520a0..b94e3c8 100644 --- a/idhub/templates/idhub/user/credentials_presentation.html +++ b/idhub/templates/idhub/user/credentials_presentation.html @@ -2,4 +2,33 @@ {% load i18n %} {% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +
+
+ {% bootstrap_form form %} +
+
+
+ {% trans "Cancel" %} + +
+ +
{% endblock %} diff --git a/idhub/templates/idhub/user/credentials_request.html b/idhub/templates/idhub/user/credentials_request.html new file mode 100644 index 0000000..6dc720d --- /dev/null +++ b/idhub/templates/idhub/user/credentials_request.html @@ -0,0 +1,34 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +
+
+ {% bootstrap_form form %} +
+
+
+ {% trans "Cancel" %} + +
+ +
+{% endblock %} diff --git a/idhub/templates/idhub/user/credentials_required.html b/idhub/templates/idhub/user/credentials_required.html deleted file mode 100644 index d6520a0..0000000 --- a/idhub/templates/idhub/user/credentials_required.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "idhub/base.html" %} -{% load i18n %} - -{% block content %} -{% endblock %} diff --git a/idhub/templates/idhub/user/dashboard.html b/idhub/templates/idhub/user/dashboard.html index 8fa4a87..29154f4 100644 --- a/idhub/templates/idhub/user/dashboard.html +++ b/idhub/templates/idhub/user/dashboard.html @@ -2,6 +2,10 @@ {% load i18n %} {% block content %} +

+ + {{ subtitle }} +

diff --git a/idhub/templates/idhub/user/did_register.html b/idhub/templates/idhub/user/did_register.html new file mode 100644 index 0000000..b2e1111 --- /dev/null +++ b/idhub/templates/idhub/user/did_register.html @@ -0,0 +1,34 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} + +{% csrf_token %} +{% if form.errors %} + +{% endif %} +
+
+ {% bootstrap_form form %} +
+
+
+ {% trans "Cancel" %} + +
+ + +{% endblock %} diff --git a/idhub/templates/idhub/user/dids.html b/idhub/templates/idhub/user/dids.html new file mode 100644 index 0000000..eae3db6 --- /dev/null +++ b/idhub/templates/idhub/user/dids.html @@ -0,0 +1,60 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+
+
+
+
+ + + + + + + + + + + {% for d in dids.all %} + + + + + + + + {% endfor %} + +
{{ d.created_at }}{{ d.label }}{{ d.did }}
+
+ {% translate "Add Identity" %} +
+
+ + + +{% for d in dids.all %} + +{% endfor %} +{% endblock %} diff --git a/idhub/templates/idhub/user/gdpr.html b/idhub/templates/idhub/user/gdpr.html index d6520a0..1ac25fd 100644 --- a/idhub/templates/idhub/user/gdpr.html +++ b/idhub/templates/idhub/user/gdpr.html @@ -2,4 +2,8 @@ {% load i18n %} {% block content %} +

+ + {{ subtitle }} +

{% endblock %} diff --git a/idhub/templates/idhub/user/identities.html b/idhub/templates/idhub/user/identities.html deleted file mode 100644 index d6520a0..0000000 --- a/idhub/templates/idhub/user/identities.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "idhub/base.html" %} -{% load i18n %} - -{% block content %} -{% endblock %} diff --git a/idhub/templates/idhub/user/profile.html b/idhub/templates/idhub/user/profile.html index fb129ba..da1597a 100644 --- a/idhub/templates/idhub/user/profile.html +++ b/idhub/templates/idhub/user/profile.html @@ -2,6 +2,18 @@ {% load i18n %} {% block content %} +
+
+

+ + {{ subtitle }} +

+
+
+ {% trans 'ARCO Forms' %} + {% trans 'Notice of Privacy' %} +
+
{% load django_bootstrap5 %}
{% csrf_token %} @@ -25,4 +37,36 @@
+
+ +
+
+
+ + + + + + + + + + + {% for membership in object.memberships.all %} + + + + + + + {% endfor %} + +
{{ membership.get_type }}{{ membership.start_date|default:'' }}{{ membership.end_date|default:'' }} + + + +
+
+
+
{% endblock %} diff --git a/idhub/templates/idhub/user/roles.html b/idhub/templates/idhub/user/roles.html index e439a01..0295b53 100644 --- a/idhub/templates/idhub/user/roles.html +++ b/idhub/templates/idhub/user/roles.html @@ -2,6 +2,10 @@ {% load i18n %} {% block content %} +

+ + {{ subtitle }} +

@@ -16,7 +20,7 @@ {% for rol in user.roles.all %} - {{ rol.service.rol.name }} + {{ rol.service.get_roles }} {{ rol.service.description }} {{ rol.service.domain }} diff --git a/idhub/urls.py b/idhub/urls.py index 6254243..eeb6b09 100644 --- a/idhub/urls.py +++ b/idhub/urls.py @@ -67,13 +67,23 @@ urlpatterns = [ name='user_roles'), path('user/gdpr/', views_user.UserGDPRView.as_view(), name='user_gdpr'), - path('user/identities/', views_user.UserIdentitiesView.as_view(), - name='user_identities'), + path('user/identities/', views_user.UserDidsView.as_view(), + name='user_dids'), + path('user/dids/new/', views_user.UserDidRegisterView.as_view(), + name='user_dids_new'), + path('user/dids//', views_user.UserDidEditView.as_view(), + name='user_dids_edit'), + path('user/dids//del/', views_user.UserDidDeleteView.as_view(), + name='user_dids_del'), path('user/credentials/', views_user.UserCredentialsView.as_view(), name='user_credentials'), - path('user/credentials_required/', - views_user.UserCredentialsRequiredView.as_view(), - name='user_credentials_required'), + path('user/credentials/', views_user.UserCredentialView.as_view(), + name='user_credential'), + path('user/credentials//json', views_user.UserCredentialJsonView.as_view(), + name='user_credential_json'), + path('user/credentials/request/', + views_user.UserCredentialsRequestView.as_view(), + name='user_credentials_request'), path('user/credentials_presentation/', views_user.UserCredentialsPresentationView.as_view(), name='user_credentials_presentation'), @@ -123,26 +133,40 @@ urlpatterns = [ name='admin_service_del'), path('admin/credentials/', views_admin.AdminCredentialsView.as_view(), name='admin_credentials'), - path('admin/credentials/new/', views_admin.AdminIssueCredentialsView.as_view(), - name='admin_credentials_new'), + path('admin/credentials//', views_admin.AdminCredentialView.as_view(), + name='admin_credential'), path('admin/credentials/revoke/', views_admin.AdminRevokeCredentialsView.as_view(), name='admin_credentials_revoke'), - path('admin/wallet/identities/', views_admin.AdminWalletIdentitiesView.as_view(), - name='admin_wallet_identities'), + path('admin/wallet/identities/', views_admin.AdminDidsView.as_view(), + name='admin_dids'), + path('admin/dids/new/', views_admin.AdminDidRegisterView.as_view(), + name='admin_dids_new'), + path('admin/dids//', views_admin.AdminDidEditView.as_view(), + name='admin_dids_edit'), + path('admin/dids//del/', views_admin.AdminDidDeleteView.as_view(), + name='admin_dids_del'), path('admin/wallet/credentials/', views_admin.AdminWalletCredentialsView.as_view(), name='admin_wallet_credentials'), path('admin/wallet/config/issue/', views_admin.AdminWalletConfigIssuesView.as_view(), name='admin_wallet_config_issue'), path('admin/wallet/config/issue/', views_admin.AdminWalletConfigIssuesView.as_view(), name='admin_wallet_config_issue'), - path('admin/schemes/', views_admin.AdminSchemesView.as_view(), - name='admin_schemes'), - path('admin/schemes/import', views_admin.AdminSchemesImportView.as_view(), - name='admin_schemes_import'), - path('admin/schemes/export/', views_admin.AdminSchemesExportView.as_view(), - name='admin_schemes_export'), + path('admin/schemas/', views_admin.AdminSchemasView.as_view(), + name='admin_schemas'), + path('admin/schemas//del/', views_admin.AdminSchemasDeleteView.as_view(), + name='admin_schemas_del'), + path('admin/schemas//', views_admin.AdminSchemasDownloadView.as_view(), + name='admin_schemas_download'), + path('admin/schemas/new', views_admin.AdminSchemasNewView.as_view(), + name='admin_schemas_new'), + path('admin/schemas/import', views_admin.AdminSchemasImportView.as_view(), + name='admin_schemas_import'), + path('admin/schemas/import/', views_admin.AdminSchemasImportAddView.as_view(), + name='admin_schemas_import_add'), path('admin/import', views_admin.AdminImportView.as_view(), name='admin_import'), - path('admin/export/', views_admin.AdminExportView.as_view(), - name='admin_export'), + path('admin/import/new', views_admin.AdminImportStep2View.as_view(), + name='admin_import_step2'), + path('admin/import//', views_admin.AdminImportStep3View.as_view(), + name='admin_import_step3'), ] diff --git a/idhub/user/forms.py b/idhub/user/forms.py index 2293aac..a8f6997 100644 --- a/idhub/user/forms.py +++ b/idhub/user/forms.py @@ -1,5 +1,7 @@ from django import forms -from django.contrib.auth.models import User +from idhub_auth.models import User +from idhub.models import DID, VerificableCredential, Organization + class ProfileForm(forms.ModelForm): @@ -7,4 +9,86 @@ class ProfileForm(forms.ModelForm): class Meta: model = User - fields = ('first_name', 'last_name', 'email') \ No newline at end of file + fields = ('first_name', 'last_name', 'email') + + +class RequestCredentialForm(forms.Form): + did = forms.ChoiceField(choices=[]) + credential = forms.ChoiceField(choices=[]) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) + super().__init__(*args, **kwargs) + self.fields['did'].choices = [ + (x.did, x.label) for x in DID.objects.filter(user=self.user) + ] + self.fields['credential'].choices = [ + (x.id, x.type()) for x in VerificableCredential.objects.filter( + user=self.user, + status=VerificableCredential.Status.ENABLED + ) + ] + + def save(self, commit=True): + did = DID.objects.filter( + user=self.user, + did=self.data['did'] + ) + cred = VerificableCredential.objects.filter( + user=self.user, + id=self.data['credential'], + status=VerificableCredential.Status.ENABLED + ) + if not all([cred.exists(), did.exists()]): + return + + did = did[0].did + cred = cred[0] + cred.get_issued(did) + + if commit: + cred.save() + return cred + + return + + + +class CredentialPresentationForm(forms.Form): + organization = forms.ChoiceField(choices=[]) + credential = forms.ChoiceField(choices=[]) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) + super().__init__(*args, **kwargs) + self.fields['organization'].choices = [ + (x.id, x.name) for x in Organization.objects.filter() + ] + self.fields['credential'].choices = [ + (x.id, x.type()) for x in VerificableCredential.objects.filter( + user=self.user, + status=VerificableCredential.Status.ISSUED + ) + ] + + def save(self, commit=True): + org = Organization.objects.filter( + id=self.data['organization'] + ) + cred = VerificableCredential.objects.filter( + user=self.user, + id=self.data['credential'], + status=VerificableCredential.Status.ISSUED + ) + if not all([org.exists(), cred.exists()]): + return + + org =org[0] + cred = cred[0] + + if commit: + org.send(cred) + return cred + + return + diff --git a/idhub/user/views.py b/idhub/user/views.py index 95316ed..5071968 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -1,12 +1,21 @@ import logging from django.utils.translation import gettext_lazy as _ -from django.views.generic.edit import UpdateView +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 idhub.user.forms import ProfileForm +from apiregiter import iota +from idhub.user.forms import ProfileForm, RequestCredentialForm, CredentialPresentationForm from idhub.mixins import UserView +from idhub.models import DID, VerificableCredential class MyProfile(UserView): @@ -14,7 +23,7 @@ class MyProfile(UserView): section = "MyProfile" -class MyWallet(UserView, TemplateView): +class MyWallet(UserView): title = _("My Wallet") section = "MyWallet" @@ -51,25 +60,178 @@ class UserGDPRView(MyProfile, TemplateView): icon = 'bi bi-file-earmark-medical' -class UserIdentitiesView(MyWallet): - template_name = "idhub/user/identities.html" - subtitle = _('Identities (DID)') - icon = 'bi bi-patch-check-fill' - - -class UserCredentialsView(MyWallet): +class UserCredentialsView(MyWallet, TemplateView): template_name = "idhub/user/credentials.html" subtitle = _('Credentials') icon = 'bi bi-patch-check-fill' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'credentials': VerificableCredential.objects, + }) + return context -class UserCredentialsRequiredView(MyWallet): - template_name = "idhub/user/credentials_required.html" - subtitle = _('Credentials required') + +class UserCredentialView(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) -class UserCredentialsPresentationView(MyWallet): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'object': self.object, + }) + return context + + +class UserCredentialJsonView(MyWallet, TemplateView): + + def get(self, request, *args, **kwargs): + pk = kwargs['pk'] + self.object = get_object_or_404( + VerificableCredential, + pk=pk, + user=self.request.user + ) + response = HttpResponse(self.object.data, content_type="application/json") + response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json") + return response + + +class UserCredentialsRequestView(MyWallet, FormView): + template_name = "idhub/user/credentials_request.html" + subtitle = _('Credentials request') + icon = 'bi bi-patch-check-fill' + form_class = RequestCredentialForm + success_url = reverse_lazy('idhub:user_credentials') + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + cred = form.save() + if cred: + messages.success(self.request, _("The credential was required successfully!")) + else: + messages.error(self.request, _("Not exists the credential!")) + return super().form_valid(form) + + +class UserCredentialsPresentationView(MyWallet, FormView): template_name = "idhub/user/credentials_presentation.html" subtitle = _('Credentials Presentation') icon = 'bi bi-patch-check-fill' + form_class = CredentialPresentationForm + success_url = reverse_lazy('idhub:user_credentials') + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['user'] = self.request.user + return kwargs + + def form_valid(self, form): + cred = form.save() + if cred: + messages.success(self.request, _("The credential was presented successfully!")) + else: + messages.error(self.request, _("Error sending credential!")) + return super().form_valid(form) + + +class UserDidsView(MyWallet, TemplateView): + template_name = "idhub/user/dids.html" + subtitle = _('Identities (DID)') + 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 + + +class UserDidRegisterView(MyWallet, CreateView): + template_name = "idhub/user/did_register.html" + subtitle = _('Add a new Identities (DID)') + icon = 'bi bi-patch-check-fill' + wallet = True + model = DID + fields = ('did', 'label') + success_url = reverse_lazy('idhub:user_dids') + object = None + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['initial'] = { + 'did': iota.issue_did(), + 'user': self.request.user + } + return kwargs + + def get_form(self): + form = super().get_form() + form.fields['did'].required = False + form.fields['did'].disabled = True + return form + + def form_valid(self, form): + form.instance.user = self.request.user + form.save() + messages.success(self.request, _('DID created successfully')) + return super().form_valid(form) + + +class UserDidEditView(MyWallet, UpdateView): + template_name = "idhub/user/did_register.html" + subtitle = _('Identities (DID)') + icon = 'bi bi-patch-check-fill' + wallet = True + model = DID + fields = ('did', '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 get_form(self): + form = super().get_form() + form.fields['did'].required = False + form.fields['did'].disabled = True + return form + + def form_valid(self, form): + user = form.save() + messages.success(self.request, _('DID updated successfully')) + return super().form_valid(form) + + +class UserDidDeleteView(MyWallet, DeleteView): + subtitle = _('Identities (DID)') + 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) + self.object.delete() + messages.success(self.request, _('DID delete successfully')) + + return redirect(self.success_url) diff --git a/idhub_auth/__init__.py b/idhub_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/idhub_auth/admin.py b/idhub_auth/admin.py new file mode 100644 index 0000000..cb7f452 --- /dev/null +++ b/idhub_auth/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.contrib.auth import get_user_model + + +User = get_user_model() + +admin.site.register(User) diff --git a/idhub_auth/apps.py b/idhub_auth/apps.py new file mode 100644 index 0000000..3439cf8 --- /dev/null +++ b/idhub_auth/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'idhub_auth' diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py new file mode 100644 index 0000000..2196067 --- /dev/null +++ b/idhub_auth/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.5 on 2023-11-02 15:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('password', models.CharField(max_length=128, verbose_name='password')), + ( + 'last_login', + models.DateTimeField( + blank=True, null=True, verbose_name='last login' + ), + ), + ( + 'email', + models.EmailField( + max_length=255, unique=True, verbose_name='email address' + ), + ), + ('is_active', models.BooleanField(default=True)), + ('is_admin', models.BooleanField(default=False)), + ('first_name', models.CharField(blank=True, max_length=255, null=True)), + ('last_name', models.CharField(blank=True, max_length=255, null=True)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/idhub_auth/migrations/__init__.py b/idhub_auth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/idhub_auth/models.py b/idhub_auth/models.py new file mode 100644 index 0000000..64f5491 --- /dev/null +++ b/idhub_auth/models.py @@ -0,0 +1,74 @@ +from django.db import models +from django.contrib.auth.models import BaseUserManager, AbstractBaseUser + + +class UserManager(BaseUserManager): + def create_user(self, email, password=None): + """ + Creates and saves a User with the given email, date of + birth and password. + """ + if not email: + raise ValueError("Users must have an email address") + + user = self.model( + email=self.normalize_email(email), + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None): + """ + Creates and saves a superuser with the given email, date of + birth and password. + """ + user = self.create_user( + email, + password=password, + ) + user.is_admin = True + user.save(using=self._db) + return user + + +class User(AbstractBaseUser): + email = models.EmailField( + verbose_name="email address", + max_length=255, + unique=True, + ) + is_active = models.BooleanField(default=True) + is_admin = models.BooleanField(default=False) + first_name = models.CharField(max_length=255, blank=True, null=True) + last_name = models.CharField(max_length=255, blank=True, null=True) + + objects = UserManager() + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + def __str__(self): + return self.email + + def has_perm(self, perm, obj=None): + "Does the user have a specific permission?" + # Simplest possible answer: Yes, always + return True + + def has_module_perms(self, app_label): + "Does the user have permissions to view the app `app_label`?" + # Simplest possible answer: Yes, always + return True + + @property + def is_staff(self): + "Is the user a member of staff?" + # Simplest possible answer: All admins are staff + return self.is_admin + + @property + def username(self): + "Is the email of the user" + return self.email diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e3b8acf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[tool.black] +skip-string-normalization = true +target-version = ['py311'] + +[tool.isort] +profile = "black" diff --git a/requirements.txt b/requirements.txt index b1a0f3f..4281ff1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,7 @@ django-bootstrap5==23.3 django-extensions==3.2.3 black==23.9.1 python-decouple==3.8 +jsonschema==4.19.1 +pandas==2.1.1 +requests==2.31.0 + diff --git a/schemas/member-schema.json b/schemas/member-schema.json new file mode 100644 index 0000000..38199be --- /dev/null +++ b/schemas/member-schema.json @@ -0,0 +1,21 @@ + { + "$id": "https://pangea.org/schemas/member-credential-schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "name": "MemberCredential", + "description": "MemberCredential using JsonSchemaCredential", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "membershipType": { + "type": "string", + "enum": ["individual", "organization"] + } + }, + "required": ["name", "email", "membershipType"] + } diff --git a/schemas/member.json b/schemas/member.json new file mode 100644 index 0000000..845ab50 --- /dev/null +++ b/schemas/member.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "https://example.com/credentials/3734", + "type": ["VerifiableCredential", "JsonSchemaCredential"], + "issuer": "https://pangea.org/issuers/10", + "issuanceDate": "2023-09-01T19:23:24Z", + "credentialSchema": { + "id": "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json", + "type": "JsonSchema", + "digestSRI": "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW" + }, + "credentialSubject": { + "id": "https://pangea.org/schemas/member-credential-schema.json", + "type": "JsonSchema", + "jsonSchema": { + "$id": "https://pangea.org/schemas/member-credential-schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "name": "MemberCredential", + "description": "MemberCredential using JsonSchemaCredential", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "membershipType": { + "type": "string", + "enum": ["individual", "organization"] + } + }, + "required": ["name", "email", "membershipType"] + } + } +} diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py index d00dea2..93ad79b 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -70,6 +70,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django_extensions', 'django_bootstrap5', + 'idhub_auth', 'idhub' ] @@ -160,6 +161,7 @@ MEDIA_URL = '/media/' STATIC_ROOT = config('STATIC_ROOT') MEDIA_ROOT = config('MEDIA_ROOT', default="idhub/upload") FIXTURE_DIRS = (os.path.join(BASE_DIR, 'fixtures'),) +SCHEMAS_DIR = os.path.join(BASE_DIR, 'schemas') # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field @@ -176,3 +178,4 @@ MESSAGE_TAGS = { messages.ERROR: 'alert-danger', } +AUTH_USER_MODEL = 'idhub_auth.User'