add predefined_token in initial_datas #6

Merged
pedro merged 11 commits from predefined_token into release 2025-01-29 17:09:50 +00:00
13 changed files with 142 additions and 43 deletions

View file

@ -31,11 +31,14 @@ IDHUB_ADMIN_USER='admin'
IDHUB_ADMIN_PASSWD='admin' IDHUB_ADMIN_PASSWD='admin'
IDHUB_ADMIN_EMAIL='admin@example.org' IDHUB_ADMIN_EMAIL='admin@example.org'
IDHUB_SUPPORTED_CREDENTIALS="['CourseCredential', 'EOperatorClaim', 'FederationMembership', 'FinancialVulnerabilityCredential', 'MembershipCard', 'Snapshot']"
# this option needs to be set to 'n' to be able to make work idhub in docker # this option needs to be set to 'n' to be able to make work idhub in docker
# by default it is set to 'y' to facilitate idhub dev when outside docker # by default it is set to 'y' to facilitate idhub dev when outside docker
IDHUB_SYNC_ORG_DEV='n' IDHUB_SYNC_ORG_DEV='n'
# TODO that is only for testing # TODO that is only for testing/demo purposes
IDHUB_ENABLE_EMAIL=false IDHUB_ENABLE_EMAIL=false
IDHUB_ENABLE_2FACTOR_AUTH=false IDHUB_ENABLE_2FACTOR_AUTH=false
IDHUB_ENABLE_DOMAIN_CHECKER=false IDHUB_ENABLE_DOMAIN_CHECKER=false
IDHUB_PREDEFINED_TOKEN='27f944ce-3d58-4f48-b068-e4aa95f97c95'

View file

@ -15,6 +15,7 @@ services:
- ENABLE_EMAIL=${IDHUB_ENABLE_EMAIL:-true} - ENABLE_EMAIL=${IDHUB_ENABLE_EMAIL:-true}
- ENABLE_2FACTOR_AUTH=${IDHUB_ENABLE_2FACTOR_AUTH:-true} - ENABLE_2FACTOR_AUTH=${IDHUB_ENABLE_2FACTOR_AUTH:-true}
- ENABLE_DOMAIN_CHECKER=${IDHUB_ENABLE_DOMAIN_CHECKER:-true} - ENABLE_DOMAIN_CHECKER=${IDHUB_ENABLE_DOMAIN_CHECKER:-true}
- PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
- SECRET_KEY=${IDHUB_SECRET_KEY:-publicsecretisnotsecureVtmKBfxpVV47PpBCF2Nzz2H6qnbd} - SECRET_KEY=${IDHUB_SECRET_KEY:-publicsecretisnotsecureVtmKBfxpVV47PpBCF2Nzz2H6qnbd}
- STATIC_ROOT=${IDHUB_STATIC_ROOT:-/static/} - STATIC_ROOT=${IDHUB_STATIC_ROOT:-/static/}
- MEDIA_ROOT=${IDHUB_MEDIA_ROOT:-/media/} - MEDIA_ROOT=${IDHUB_MEDIA_ROOT:-/media/}
@ -26,7 +27,7 @@ services:
- EMAIL_PORT=${IDHUB_EMAIL_PORT} - EMAIL_PORT=${IDHUB_EMAIL_PORT}
- EMAIL_USE_TLS=${IDHUB_EMAIL_USE_TLS} - EMAIL_USE_TLS=${IDHUB_EMAIL_USE_TLS}
- EMAIL_BACKEND=${IDHUB_EMAIL_BACKEND} - EMAIL_BACKEND=${IDHUB_EMAIL_BACKEND}
- SUPPORTED_CREDENTIALS=['CourseCredential', 'EOperatorClaim', 'FederationMembership', 'FinancialVulnerabilityCredential', 'MembershipCard', 'Snapshot'] - SUPPORTED_CREDENTIALS=${IDHUB_SUPPORTED_CREDENTIALS:-}
- SYNC_ORG_DEV=${IDHUB_SYNC_ORG_DEV} - SYNC_ORG_DEV=${IDHUB_SYNC_ORG_DEV}
ports: ports:
- ${IDHUB_PORT:-9001}:${IDHUB_PORT:-9001} - ${IDHUB_PORT:-9001}:${IDHUB_PORT:-9001}

View file

@ -15,6 +15,7 @@ main() {
cp -v .env.example .env cp -v .env.example .env
echo "WARNING: .env was not there, .env.example was copied, this only happens once" echo "WARNING: .env was not there, .env.example was copied, this only happens once"
fi fi
. ./.env
docker compose down -v docker compose down -v
if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then

View file

@ -45,8 +45,10 @@ deployment_strategy() {
echo "INFO detected NEW deployment" echo "INFO detected NEW deployment"
./manage.py migrate ./manage.py migrate
printf "This is DEVELOPMENT/PILOTS_EARLY DEPLOYMENT: including demo hardcoded data\n creating initial Datas\n" >&2 printf "This is DEVELOPMENT/PILOTS_EARLY DEPLOYMENT: including demo hardcoded data\n" >&2
./manage.py initial_datas
PREDEFINED_TOKEN="${PREDEFINED_TOKEN:-}"
./manage.py demo_data "${PREDEFINED_TOKEN}"
if [ "${OIDC_ORGS:-}" ]; then if [ "${OIDC_ORGS:-}" ]; then
config_oidc4vp config_oidc4vp

1
examples/keys_did.json Normal file
View file

@ -0,0 +1 @@
{"label": "DEMO", "key_material": "{\"kty\": \"OKP\", \"crv\": \"Ed25519\", \"x\": \"IRqDfIumhbKKHhqMjOngikQmGoT1cZ6LPP-JjXa8CsY\", \"d\": \"AZXUEnJYFbGcn3Ebzy3vQWYFzx6rdnoHKilaMYUWuHA\", \"kid\": \"Generated\"}"}

View file

@ -36,16 +36,16 @@ class TermsConditionsForm2(forms.Form):
if data.get("accept"): if data.get("accept"):
self.user.accept_gdpr = True self.user.accept_gdpr = True
else: else:
self.user.accept_gdpr = False self.user.accept_gdpr = False
return data return data
def save(self, commit=True): def save(self, commit=True):
if commit: if commit:
self.user.save() self.user.save()
return self.user return self.user
return return
class EncryptionKeyForm(forms.Form): class EncryptionKeyForm(forms.Form):
@ -80,8 +80,8 @@ class EncryptionKeyForm(forms.Form):
did = DID.objects.create(label='Default', type=DID.Types.WEB) did = DID.objects.create(label='Default', type=DID.Types.WEB)
did.set_did() did.set_did()
did.save() did.save()
return return
class TermsConditionsForm(forms.Form): class TermsConditionsForm(forms.Form):
@ -131,16 +131,16 @@ class TermsConditionsForm(forms.Form):
if privacy and legal and cookies: if privacy and legal and cookies:
self.user.accept_gdpr = True self.user.accept_gdpr = True
else: else:
self.user.accept_gdpr = False self.user.accept_gdpr = False
return data return data
def save(self, commit=True): def save(self, commit=True):
if commit: if commit:
self.user.save() self.user.save()
return self.user return self.user
return return
class ImportForm(forms.Form): class ImportForm(forms.Form):
@ -197,7 +197,7 @@ class ImportForm(forms.Form):
eidas1=True, eidas1=True,
did=eidas1 did=eidas1
).first() ).first()
return data return data
def clean_schema(self): def clean_schema(self):
@ -288,15 +288,15 @@ class ImportForm(forms.Form):
def save(self, commit=True): def save(self, commit=True):
table = [] table = []
for k, v in self.rows.items(): for k, v in self.rows.items():
table.append(self.create_credential(k, v)) table.append(self.create_credential(k, v))
if commit: if commit:
for cred in table: for cred in table:
cred.save() cred.save()
File_datas.objects.create(file_name=self.file_name) File_datas.objects.create(file_name=self.file_name)
return table return table
return return
def validate_jsonld(self, line, row): def validate_jsonld(self, line, row):
try: try:
@ -355,7 +355,7 @@ class ImportForm(forms.Form):
class SchemaForm(forms.Form): class SchemaForm(forms.Form):
file_template = forms.FileField(label=_("File template")) file_template = forms.FileField(label=_("File template"))
class MembershipForm(forms.ModelForm): class MembershipForm(forms.ModelForm):
class Meta: class Meta:
@ -376,7 +376,7 @@ class MembershipForm(forms.ModelForm):
if members.filter(start_date__lte=start_date, end_date=None).exists(): if members.filter(start_date__lte=start_date, end_date=None).exists():
msg = _("This membership already exists!") msg = _("This membership already exists!")
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
if (start_date and end_date): if (start_date and end_date):
if start_date > end_date: if start_date > end_date:
msg = _("The end date is less than the start date") msg = _("The end date is less than the start date")
@ -399,8 +399,8 @@ class MembershipForm(forms.ModelForm):
if members.exists(): if members.exists():
msg = _("This membership already exists!") msg = _("This membership already exists!")
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
return end_date return end_date
@ -417,7 +417,7 @@ class UserRolForm(forms.ModelForm):
choices = self.fields['service'].choices choices = self.fields['service'].choices
choices.queryset = choices.queryset.exclude(users__user=user) choices.queryset = choices.queryset.exclude(users__user=user)
self.fields['service'].choices = choices self.fields['service'].choices = choices
def clean_service(self): def clean_service(self):
data = super().clean() data = super().clean()
service = UserRol.objects.filter( service = UserRol.objects.filter(

View file

@ -7,22 +7,45 @@ from utils import credtools
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from decouple import config from django.core.cache import cache
from idhub.models import Schemas from django.urls import reverse
from pyvckit.did import (
generate_did,
gen_did_document,
)
from idhub.models import Schemas, DID
from oidc4vp.models import Organization from oidc4vp.models import Organization
from webhook.models import Token
User = get_user_model() User = get_user_model()
class Command(BaseCommand): class Command(BaseCommand):
help = "Insert minimum datas for the project" help = "Insert minimum data for the project"
DOMAIN = settings.DOMAIN DOMAIN = settings.DOMAIN
OIDC_ORGS = settings.OIDC_ORGS OIDC_ORGS = settings.OIDC_ORGS
def add_arguments(self, parser):
parser.add_argument('predefined_token', nargs='?', default='', type=str, help='predefined token')
parser.add_argument('predefined_did', nargs='?', default='', type=str, help='predefined did')
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
ADMIN_EMAIL = settings.INITIAL_ADMIN_EMAIL ADMIN_EMAIL = settings.INITIAL_ADMIN_EMAIL
ADMIN_PASSWORD = settings.INITIAL_ADMIN_PASSWORD ADMIN_PASSWORD = settings.INITIAL_ADMIN_PASSWORD
self.predefined_token = kwargs['predefined_token']
self.predefined_did = kwargs['predefined_did']
# on demo situation, encrypted vault is hardcoded with password DEMO
cache.set("KEY_DIDS", "DEMO", None)
self.org = Organization.objects.create(
name=self.DOMAIN,
domain=self.DOMAIN,
main=True
)
self.org.set_encrypted_sensitive_data()
self.org.save()
self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD) self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD)
if settings.CREATE_TEST_USERS: if settings.CREATE_TEST_USERS:
@ -30,12 +53,6 @@ class Command(BaseCommand):
user = 'user{}@example.org'.format(u) user = 'user{}@example.org'.format(u)
self.create_users(user, '1234') self.create_users(user, '1234')
self.org = Organization.objects.create(
name=self.DOMAIN,
domain=self.DOMAIN,
main=True
)
if self.OIDC_ORGS: if self.OIDC_ORGS:
self.create_organizations() self.create_organizations()
@ -45,6 +62,61 @@ class Command(BaseCommand):
su = User.objects.create_superuser(email=email, password=password) su = User.objects.create_superuser(email=email, password=password)
su.save() su.save()
if self.predefined_token:
tk = Token.objects.filter(token=self.predefined_token).first()
if not tk:
Token.objects.create(token=self.predefined_token)
self.create_default_did()
def create_default_did(self):
fdid = self.open_example_did()
if not fdid:
return
did = DID(type=DID.Types.WEB)
new_key_material = fdid.get("key_material", "")
label = fdid.get("label", "")
if not new_key_material:
return
did.set_key_material(new_key_material)
if label:
did.label = label
if did.type == did.Types.KEY:
did.did = generate_did(new_key_material)
elif did.type == did.Types.WEB:
url = "https://{}".format(settings.DOMAIN)
path = reverse("idhub:serve_did", args=["a"])
if path:
path = path.split("/a/did.json")[0]
url = "https://{}/{}".format(settings.DOMAIN, path)
did.did = generate_did(new_key_material, url)
key = json.loads(new_key_material)
url, did.didweb_document = gen_did_document(did.did, key)
did.save()
def open_example_did(self):
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
didweb_path = os.path.join(BASE_DIR, "examples", "keys_did.json")
if self.predefined_did:
didweb_path = self.predefined_did
data = ''
with open(didweb_path) as _file:
try:
data = json.loads(_file.read())
except Exception:
pass
return data
def create_users(self, email, password): def create_users(self, email, password):
u = User.objects.create(email=email, password=password) u = User.objects.create(email=email, password=password)

View file

@ -684,6 +684,14 @@ class VerificableCredential(models.Model):
if self.status == self.Status.ISSUED: if self.status == self.Status.ISSUED:
return return
supported = False
for name in self.schema.get_schema.get("name"):
if name.get("value") in settings.SUPPORTED_CREDENTIALS:
supported = True
if not supported:
return
self.subject_did = did self.subject_did = did
self.issued_on = datetime.datetime.now().astimezone(pytz.utc) self.issued_on = datetime.datetime.now().astimezone(pytz.utc)

View file

@ -179,8 +179,8 @@ class TermsAndConditionsView(UserView, FormView):
class WaitingView(UserView, TemplateView): class WaitingView(UserView, TemplateView):
template_name = "idhub/user/waiting.html" template_name = "idhub/user/waiting.html"
title = _("Comunication with admin") title = _("Comunication with admin required")
subtitle = _('Service temporary close') subtitle = _('Service temporarily closed')
section = "" section = ""
icon = 'bi bi-file-earmark-medical' icon = 'bi bi-file-earmark-medical'
success_url = reverse_lazy('idhub:user_dashboard') success_url = reverse_lazy('idhub:user_dashboard')

View file

@ -125,7 +125,7 @@ class User(AbstractBaseUser):
sb = secret.SecretBox(sb_key) sb = secret.SecretBox(sb_key)
if not isinstance(data, bytes): if not isinstance(data, bytes):
data = data.encode('utf-8') data = data.encode('utf-8')
return base64.b64encode(sb.encrypt(data)).decode('utf-8') return base64.b64encode(sb.encrypt(data)).decode('utf-8')
def get_salt(self): def get_salt(self):
@ -166,6 +166,6 @@ class User(AbstractBaseUser):
sb = secret.SecretBox(sb_key) sb = secret.SecretBox(sb_key)
if not isinstance(data, bytes): if not isinstance(data, bytes):
data = data.encode('utf-8') data = data.encode('utf-8')
encrypted_data = base64.b64encode(sb.encrypt(data)).decode('utf-8') encrypted_data = base64.b64encode(sb.encrypt(data)).decode('utf-8')
self.encrypted_sensitive_data = encrypted_data self.encrypted_sensitive_data = encrypted_data

View file

@ -2796,11 +2796,11 @@ msgid "Data Protection"
msgstr "Protecció de dades" msgstr "Protecció de dades"
#: idhub/user/views.py:183 #: idhub/user/views.py:183
msgid "Comunication with admin" msgid "Comunication with admin required"
msgstr "Comunicació amb l'admin" msgstr "Es requereix comunicació amb l'admin"
#: idhub/user/views.py:184 #: idhub/user/views.py:184
msgid "Service temporary close" msgid "Service temporarily closed"
msgstr "Tancament temporal del servei" msgstr "Tancament temporal del servei"
#: idhub/user/views.py:407 #: idhub/user/views.py:407

View file

@ -2789,11 +2789,11 @@ msgid "Data Protection"
msgstr "Proteccion de datos" msgstr "Proteccion de datos"
#: idhub/user/views.py:183 #: idhub/user/views.py:183
msgid "Comunication with admin" msgid "Comunication with admin required"
msgstr "Comunicación con el admin" msgstr "Se requiere comunicación con el admin"
#: idhub/user/views.py:184 #: idhub/user/views.py:184
msgid "Service temporary close" msgid "Service temporarily closed"
msgstr "Cierre temporal del servicio" msgstr "Cierre temporal del servicio"
#: idhub/user/views.py:407 #: idhub/user/views.py:407

View file

@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic.edit import DeleteView from django.views.generic.edit import DeleteView
from django.views.generic.base import View from django.views.generic.base import View
from django.core.cache import cache
from django.http import JsonResponse from django.http import JsonResponse
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from pyvckit.verify import verify_vp, verify_vc from pyvckit.verify import verify_vp, verify_vc
@ -20,6 +21,10 @@ from webhook.tables import TokensTable
@csrf_exempt @csrf_exempt
def webhook_verify(request): def webhook_verify(request):
if request.method == 'POST': if request.method == 'POST':
user = User.objects.filter(is_admin=True).first()
if not cache.get("KEY_DIDS") or not user.accept_gdpr:
return JsonResponse({'error': 'Temporary out of service'}, status=400)
auth_header = request.headers.get('Authorization') auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '): if not auth_header or not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Invalid or missing token'}, status=401) return JsonResponse({'error': 'Invalid or missing token'}, status=401)
@ -56,6 +61,10 @@ def webhook_verify(request):
@csrf_exempt @csrf_exempt
def webhook_issue(request): def webhook_issue(request):
if request.method == 'POST': if request.method == 'POST':
user = User.objects.filter(is_admin=True).first()
if not cache.get("KEY_DIDS") or not user.accept_gdpr:
return JsonResponse({'error': 'Temporary out of service'}, status=400)
auth_header = request.headers.get('Authorization') auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '): if not auth_header or not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Invalid or missing token'}, status=401) return JsonResponse({'error': 'Invalid or missing token'}, status=401)
@ -89,7 +98,6 @@ def webhook_issue(request):
if not schema: if not schema:
return JsonResponse({'error': 'Invalid credential'}, status=400) return JsonResponse({'error': 'Invalid credential'}, status=400)
user = User.objects.filter(is_admin=True).first()
cred = VerificableCredential( cred = VerificableCredential(
csv_data=vc, csv_data=vc,
issuer_did=did, issuer_did=did,
@ -100,6 +108,9 @@ def webhook_issue(request):
cred.set_type() cred.set_type()
vc_signed = cred.issue(did, domain=request.get_host(), save=save) vc_signed = cred.issue(did, domain=request.get_host(), save=save)
if not vc_signed:
return JsonResponse({'error': 'Invalid credential'}, status=400)
return JsonResponse({'status': 'success', "data": vc_signed}, status=200) return JsonResponse({'status': 'success', "data": vc_signed}, status=200)
return JsonResponse({'status': 'fail'}, status=200) return JsonResponse({'status': 'fail'}, status=200)