diff --git a/examples/organizations.csv b/examples/organizations.csv index 61f0f82..2f447ef 100644 --- a/examples/organizations.csv +++ b/examples/organizations.csv @@ -1,2 +1,5 @@ -"ExO";"https://verify.exo.cat" -"Somos Connexión";"https://verify.somosconexion.coop" +"pangea.org";"https://idhub1.demo.pangea.org/oidc4vp/" +"somconnexio.coop";"https://idhub2.demo.pangea.org/oidc4vp/" +"exo.cat";"https://verify.exo.cat" +"local 9000";"http://localhost:9000/oidc4vp/" +"local 8000";"http://localhost:8000/oidc4vp/" diff --git a/idhub/admin/forms.py b/idhub/admin/forms.py index 4682579..2649f4b 100644 --- a/idhub/admin/forms.py +++ b/idhub/admin/forms.py @@ -19,9 +19,9 @@ from idhub_auth.models import User class ImportForm(forms.Form): - did = forms.ChoiceField(choices=[]) - schema = forms.ChoiceField(choices=[]) - file_import = forms.FileField() + did = forms.ChoiceField(label=_("Did"), choices=[]) + schema = forms.ChoiceField(label=_("Schema"), choices=[]) + file_import = forms.FileField(label=_("File import")) def __init__(self, *args, **kwargs): self._schema = None @@ -139,7 +139,7 @@ class ImportForm(forms.Form): class SchemaForm(forms.Form): - file_template = forms.FileField() + file_template = forms.FileField(label=_("File template")) class MembershipForm(forms.ModelForm): diff --git a/idhub/admin/tables.py b/idhub/admin/tables.py index 7ce7d43..79c22ba 100644 --- a/idhub/admin/tables.py +++ b/idhub/admin/tables.py @@ -1,4 +1,5 @@ import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ from idhub.models import Rol, Event from idhub_auth.models import User @@ -18,10 +19,6 @@ class RolesTable(tables.Table): class DashboardTable(tables.Table): - type = tables.Column(verbose_name="Event") - message = tables.Column(verbose_name="Description") - created = tables.Column(verbose_name="Date") - class Meta: model = Event template_name = "idhub/custom_table.html" diff --git a/idhub/admin/views.py b/idhub/admin/views.py index f8fd6d0..b6dcbc8 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -629,7 +629,7 @@ class DidsView(Credentials): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update({ - 'dids': DID.objects, + 'dids': DID.objects.filter(user=self.request.user), }) return context @@ -772,11 +772,14 @@ class SchemasNewView(SchemasMix): return try: data = f.read().decode('utf-8') - assert credtools.validate_schema(json.loads(data)) + ldata = json.loads(data) + assert credtools.validate_schema(ldata) + name = ldata.get('name') + assert name except Exception: messages.error(self.request, _('This is not a valid schema!')) return - schema = Schemas.objects.create(file_schema=file_name, data=data) + schema = Schemas.objects.create(file_schema=file_name, data=data, type=name) schema.save() return schema @@ -817,11 +820,14 @@ class SchemasImportAddView(SchemasMix): def create_schema(self, file_name): data = self.open_file(file_name) try: - json.loads(data) + ldata = json.loads(data) + assert credtools.validate_schema(ldata) + name = ldata.get('name') + assert name except Exception: messages.error(self.request, _('This is not a valid schema!')) return - schema = Schemas.objects.create(file_schema=file_name, data=data) + schema = Schemas.objects.create(file_schema=file_name, data=data, type=name) schema.save() return schema diff --git a/idhub/email/views.py b/idhub/email/views.py index 83d10cc..72e0daa 100644 --- a/idhub/email/views.py +++ b/idhub/email/views.py @@ -1,3 +1,5 @@ +import logging + from django.conf import settings from django.template import loader from django.core.mail import EmailMultiAlternatives @@ -7,6 +9,9 @@ from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +logger = logging.getLogger(__name__) + + class NotifyActivateUserByEmail: def get_email_context(self, user): """ @@ -49,10 +54,11 @@ class NotifyActivateUserByEmail: email_message.attach_alternative(html_email, 'text/html') try: if settings.DEVELOPMENT: - print(to_email) - print(body) + logger.warning(to_email) + logger.warning(body) return email_message.send() - except Exception: + except Exception as err: + logger.error(err) return diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index fd644e9..62c048a 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -1,11 +1,16 @@ import os import csv +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.contrib.auth import get_user_model from decouple import config -from idhub.models import Organization +from idhub.models import DID, Schemas +from oidc4vp.models import Organization +from promotion.models import Promotion User = get_user_model() @@ -29,6 +34,10 @@ class Command(BaseCommand): f = csv.reader(csvfile, delimiter=';', quotechar='"') for r in f: self.create_organizations(r[0].strip(), r[1].strip()) + self.sync_credentials_organizations("pangea.org", "somconnexio.coop") + self.sync_credentials_organizations("local 8000", "local 9000") + self.create_defaults_dids() + self.create_schemas() def create_admin_users(self, email, password): su = User.objects.create_superuser(email=email, password=password) @@ -44,4 +53,48 @@ class Command(BaseCommand): def create_organizations(self, name, url): - Organization.objects.create(name=name, url=url) + Organization.objects.create(name=name, response_uri=url) + + def sync_credentials_organizations(self, test1, test2): + org1 = Organization.objects.get(name=test1) + org2 = Organization.objects.get(name=test2) + org2.my_client_id = org1.client_id + org2.my_client_secret = org1.client_secret + org1.my_client_id = org2.client_id + org1.my_client_secret = org2.client_secret + org1.save() + org2.save() + + def create_defaults_dids(self): + for u in User.objects.all(): + did = DID(label="Default", user=u) + 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 + self._create_schemas(x) + + def _create_schemas(self, file_name): + data = self.open_file(file_name) + try: + ldata = json.loads(data) + assert credtools.validate_schema(ldata) + name = ldata.get('name') + assert name + except Exception: + return + Schemas.objects.create(file_schema=file_name, data=data, type=name) + + 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 diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py index 5bd4f31..751af85 100644 --- a/idhub/migrations/0001_initial.py +++ b/idhub/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2024-01-04 15:12 +# Generated by Django 4.2.5 on 2024-01-04 16:59 from django.conf import settings from django.db import migrations, models @@ -26,7 +26,7 @@ class Migration(migrations.Migration): ), ), ('created_at', models.DateTimeField(auto_now=True)), - ('label', models.CharField(max_length=50)), + ('label', models.CharField(max_length=50, verbose_name='Label')), ('did', models.CharField(max_length=250)), ('_key_material', models.BinaryField(max_length=250)), ( @@ -57,27 +57,6 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now=True)), ], ), - migrations.CreateModel( - name='Organization', - fields=[ - ( - 'id', - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID', - ), - ), - ('name', models.CharField(max_length=250)), - ( - 'url', - models.CharField( - help_text='Url where to send the presentation', max_length=250 - ), - ), - ], - ), migrations.CreateModel( name='Rol', fields=[ @@ -111,6 +90,7 @@ class Migration(migrations.Migration): verbose_name='ID', ), ), + ('type', models.CharField(max_length=250)), ('file_schema', models.CharField(max_length=250)), ('data', models.TextField()), ('created_at', models.DateTimeField(auto_now=True)), @@ -168,8 +148,7 @@ class Migration(migrations.Migration): ('verified', models.BooleanField()), ('created_on', models.DateTimeField(auto_now=True)), ('issued_on', models.DateTimeField(null=True)), - ('subject_did', models.CharField(max_length=250)), - ('_data', models.BinaryField()), + ('data', models.TextField()), ('csv_data', models.TextField()), ( 'status', @@ -199,6 +178,15 @@ class Migration(migrations.Migration): to='idhub.schemas', ), ), + ( + 'subject_did', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='subject_credentials', + to='idhub.did', + ), + ), ( 'user', models.ForeignKey( @@ -268,8 +256,11 @@ class Migration(migrations.Migration): verbose_name='ID', ), ), - ('created', models.DateTimeField(auto_now=True)), - ('message', models.CharField(max_length=350)), + ('created', models.DateTimeField(auto_now=True, verbose_name='Date')), + ( + 'message', + models.CharField(max_length=350, verbose_name='Description'), + ), ( 'type', models.PositiveSmallIntegerField( @@ -307,7 +298,8 @@ class Migration(migrations.Migration): (28, 'Organisational DID deleted by admin'), (29, 'User deactivated'), (30, 'User activated'), - ] + ], + verbose_name='Event', ), ), ( @@ -339,6 +331,7 @@ class Migration(migrations.Migration): on_delete=django.db.models.deletion.CASCADE, related_name='users', to='idhub.service', + verbose_name='Service', ), ), ( diff --git a/idhub/models.py b/idhub/models.py index 05b163a..30ad551 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -1,6 +1,5 @@ import json import pytz -import requests import datetime from django.db import models from django.conf import settings @@ -50,9 +49,10 @@ class Event(models.Model): EV_USR_DEACTIVATED_BY_ADMIN = 29, "User deactivated" EV_USR_ACTIVATED_BY_ADMIN = 30, "User activated" - created = models.DateTimeField(auto_now=True) - message = models.CharField(max_length=350) + created = models.DateTimeField(_("Date"), auto_now=True) + message = models.CharField(_("Description"), max_length=350) type = models.PositiveSmallIntegerField( + _("Event"), choices=Types.choices, ) user = models.ForeignKey( @@ -407,7 +407,7 @@ class Event(models.Model): class DID(models.Model): created_at = models.DateTimeField(auto_now=True) - label = models.CharField(max_length=50) + label = models.CharField(_("Label"), max_length=50) did = models.CharField(max_length=250) # In JWK format. Must be stored as-is and passed whole to library functions. # Example key material: @@ -459,6 +459,7 @@ class DID(models.Model): class Schemas(models.Model): + type = models.CharField(max_length=250) file_schema = models.CharField(max_length=250) data = models.TextField() created_at = models.DateTimeField(auto_now=True) @@ -490,11 +491,7 @@ class VerificableCredential(models.Model): verified = models.BooleanField() created_on = models.DateTimeField(auto_now=True) issued_on = models.DateTimeField(null=True) - subject_did = models.CharField(max_length=250) - # CHANGED: `data` to `_data`, datatype from TextField to BinaryField and the rendered VC is now stored encrypted. - # TODO: verify that BinaryField can hold arbitrary amounts of data (max_length = ???) - data = None - _data = models.BinaryField() + data = models.TextField() csv_data = models.TextField() status = models.PositiveSmallIntegerField( choices=Status.choices, @@ -505,6 +502,12 @@ class VerificableCredential(models.Model): on_delete=models.CASCADE, related_name='vcredentials', ) + subject_did = models.ForeignKey( + DID, + on_delete=models.CASCADE, + related_name='subject_credentials', + null=True + ) issuer_did = models.ForeignKey( DID, on_delete=models.CASCADE, @@ -517,36 +520,16 @@ class VerificableCredential(models.Model): ) def get_data(self): - key_dids = cache.get("KEY_DIDS", {}) - if not key_dids.get(user.id): - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(key_dids[user.id]) - return sb.decrypt(self._data) + return self.user.decrypt_data(self.data) def set_data(self, value): - key_dids = cache.get("KEY_DIDS", {}) - if not key_dids.get(user.id): - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(key_dids[user.id]) - self._data = sb.encrypt(value) - - @property - def get_schema(self): - if not self.data: - return {} - return json.loads(self.data) + self.data = self.user.encrypt_data(value) def type(self): - if self.data: - return self.get_schema.get('type')[-1] - - return self.schema.name() + return self.schema.type def description(self): - if not self.data: - return self.schema.description() - - for des in self.get_schema.get('description', []): + for des in json.loads(self.render()).get('description', []): if settings.LANGUAGE_CODE == des.get('lang'): return des.get('value', '') return '' @@ -572,13 +555,15 @@ class VerificableCredential(models.Model): def get_context(self): d = json.loads(self.csv_data) - format = "%Y-%m-%dT%H:%M:%SZ" - issuance_date = self.issued_on.strftime(format) + issuance_date = '' + if self.issued_on: + format = "%Y-%m-%dT%H:%M:%SZ" + issuance_date = self.issued_on.strftime(format) context = { 'vc_id': self.id, 'issuer_did': self.issuer_did.did, - 'subject_did': self.subject_did, + 'subject_did': self.subject_did and self.subject_did.did or '', 'issuance_date': issuance_date, 'first_name': self.user.first_name, 'last_name': self.user.last_name, @@ -677,24 +662,10 @@ class UserRol(models.Model): ) service = models.ForeignKey( Service, + verbose_name=_("Service"), on_delete=models.CASCADE, related_name='users', ) class Meta: unique_together = ('user', 'service',) - - -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) diff --git a/idhub/templates/credentials/exo.json b/idhub/templates/credentials/exo.json new file mode 100644 index 0000000..1aee10b --- /dev/null +++ b/idhub/templates/credentials/exo.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "name": "https://schema.org/name", + "email": "https://schema.org/email", + "membershipType": "https://schema.org/memberOf", + "individual": "https://schema.org/Person", + "organization": "https://schema.org/Organization", + "Member": "https://schema.org/Member", + "startDate": "https://schema.org/startDate", + "jsonSchema": "https://schema.org/jsonSchema", + "street_address": "https://schema.org/streetAddress", + "connectivity_option_list": "https://schema.org/connectivityOptionList", + "$ref": "https://schema.org/jsonSchemaRef" + } + ], + "id": "{{ vc_id }}", + "type": ["VerifiableCredential", "HomeConnectivitySurveyCredential"], + "issuer": "{{ issuer_did }}", + "issuanceDate": "{{ issuance_date }}", + "credentialSubject": { + "id": "{{ subject_did }}", + "street_address": "{{ street_address }}", + "connectivity_option_list": "{{ connectivity_option_list }}", + "jsonSchema": { + "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json" + } + } +} diff --git a/idhub/templates/credentials/openarms.json b/idhub/templates/credentials/openarms.json new file mode 100644 index 0000000..e2ecbc6 --- /dev/null +++ b/idhub/templates/credentials/openarms.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "name": "https://schema.org/name", + "email": "https://schema.org/email", + "membershipType": "https://schema.org/memberOf", + "individual": "https://schema.org/Person", + "organization": "https://schema.org/Organization", + "Member": "https://schema.org/Member", + "startDate": "https://schema.org/startDate", + "jsonSchema": "https://schema.org/jsonSchema", + "destination_country": "https://schema.org/destinationCountry", + "offboarding_date": "https://schema.org/offboardingDate", + "$ref": "https://schema.org/jsonSchemaRef" + } + ], + "id": "{{ vc_id }}", + "type": ["VerifiableCredential", "MigrantRescueCredential"], + "issuer": "{{ issuer_did }}", + "issuanceDate": "{{ issuance_date }}", + "credentialSubject": { + "id": "{{ subject_did }}", + "name": "{{ name }}", + "country_of_origin": "{{ country_of_origin }}", + "rescue_date": "{{ rescue_date }}", + "destination_country": "{{ destination_country }}", + "offboarding_date": "{{ offboarding_date }}", + "jsonSchema": { + "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json" + } + } +} diff --git a/idhub/templates/credentials/paremanel.json b/idhub/templates/credentials/paremanel.json new file mode 100644 index 0000000..a40396b --- /dev/null +++ b/idhub/templates/credentials/paremanel.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "name": "https://schema.org/name", + "email": "https://schema.org/email", + "membershipType": "https://schema.org/memberOf", + "individual": "https://schema.org/Person", + "organization": "https://schema.org/Organization", + "Member": "https://schema.org/Member", + "startDate": "https://schema.org/startDate", + "jsonSchema": "https://schema.org/jsonSchema", + "street_address": "https://schema.org/streetAddress", + "financial_vulnerability_score": "https://schema.org/financialVulnerabilityScore", + "$ref": "https://schema.org/jsonSchemaRef" + } + ], + "id": "{{ vc_id }}", + "type": ["VerifiableCredential", "FinancialSituationCredential"], + "issuer": "{{ issuer_did }}", + "issuanceDate": "{{ issuance_date }}", + "credentialSubject": { + "id": "{{ subject_did }}", + "street_address": "{{ street_address }}", + "financial_vulnerability_score": "{{ financial_vulnerability_score }}", + "jsonSchema": { + "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/UNDEF.json" + } + } +} diff --git a/idhub/templates/idhub/admin/roles.html b/idhub/templates/idhub/admin/roles.html index 25bfd93..d3d6593 100644 --- a/idhub/templates/idhub/admin/roles.html +++ b/idhub/templates/idhub/admin/roles.html @@ -24,7 +24,7 @@ {{ rol.name }} {{ rol.description|default:""}} - + {% endfor %} diff --git a/idhub/templates/idhub/admin/services.html b/idhub/templates/idhub/admin/services.html index cea981d..639d1c1 100644 --- a/idhub/templates/idhub/admin/services.html +++ b/idhub/templates/idhub/admin/services.html @@ -26,7 +26,7 @@ {{ service.description }} {{ service.get_roles }} - + {% endfor %} diff --git a/idhub/templates/idhub/admin/user.html b/idhub/templates/idhub/admin/user.html index aacd509..fbb04d1 100644 --- a/idhub/templates/idhub/admin/user.html +++ b/idhub/templates/idhub/admin/user.html @@ -21,7 +21,7 @@
- First Name: + {% trans "First name" %}:
{{ object.first_name|default:'' }} @@ -29,7 +29,7 @@
- Last Name: + {% trans "Last name" %}:
{{ object.last_name|default:'' }} @@ -37,7 +37,7 @@
- Email: + {% trans "Email address" %}:
{{ object.email }} diff --git a/idhub/templates/idhub/admin/user_edit.html b/idhub/templates/idhub/admin/user_edit.html index 9bc0a48..5198cca 100644 --- a/idhub/templates/idhub/admin/user_edit.html +++ b/idhub/templates/idhub/admin/user_edit.html @@ -52,7 +52,7 @@ {{ membership.start_date|default:'' }} {{ membership.end_date|default:'' }} - + {% endfor %} @@ -84,7 +84,7 @@ {{ rol.service.description }} {{ rol.service.domain }} - + {% endfor %} diff --git a/idhub/templates/idhub/base.html b/idhub/templates/idhub/base.html index c8d985b..390cb33 100644 --- a/idhub/templates/idhub/base.html +++ b/idhub/templates/idhub/base.html @@ -115,7 +115,7 @@ @@ -139,7 +139,7 @@

{{ title }}

- +
diff --git a/idhub/templates/idhub/base_admin.html b/idhub/templates/idhub/base_admin.html index 1253806..c97f165 100644 --- a/idhub/templates/idhub/base_admin.html +++ b/idhub/templates/idhub/base_admin.html @@ -171,7 +171,7 @@

{{ title }}

- +
diff --git a/idhub/urls.py b/idhub/urls.py index f7df70d..69acc22 100644 --- a/idhub/urls.py +++ b/idhub/urls.py @@ -20,6 +20,7 @@ from django.urls import path, reverse_lazy from .views import LoginView, PasswordResetConfirmView from .admin import views as views_admin from .user import views as views_user +# from .verification_portal import views as views_verification_portal app_name = 'idhub' @@ -87,9 +88,9 @@ urlpatterns = [ path('user/credentials/request/', views_user.CredentialsRequestView.as_view(), name='user_credentials_request'), - path('user/credentials_presentation/', - views_user.CredentialsPresentationView.as_view(), - name='user_credentials_presentation'), + path('user/credentials_presentation/demand', + views_user.DemandAuthorizationView.as_view(), + name='user_demand_authorization'), # Admin path('admin/dashboard/', views_admin.DashboardView.as_view(), @@ -174,4 +175,7 @@ urlpatterns = [ name='admin_import'), path('admin/import/new', views_admin.ImportAddView.as_view(), name='admin_import_add'), + + # path('verification_portal/verify/', views_verification_portal.verify, + # name="verification_portal_verify") ] diff --git a/idhub/user/forms.py b/idhub/user/forms.py index 53a1149..5ac04ad 100644 --- a/idhub/user/forms.py +++ b/idhub/user/forms.py @@ -1,7 +1,11 @@ +import requests from django import forms -from idhub_auth.models import User -from idhub.models import DID, VerificableCredential, Organization +from django.conf import settings +from django.utils.translation import gettext_lazy as _ +from idhub_auth.models import User +from idhub.models import DID, VerificableCredential +from oidc4vp.models import Organization class ProfileForm(forms.ModelForm): @@ -13,8 +17,8 @@ class ProfileForm(forms.ModelForm): class RequestCredentialForm(forms.Form): - did = forms.ChoiceField(choices=[]) - credential = forms.ChoiceField(choices=[]) + did = forms.ChoiceField(label=_("Did"), choices=[]) + credential = forms.ChoiceField(label=_("Credential"), choices=[]) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) @@ -42,7 +46,7 @@ class RequestCredentialForm(forms.Form): if not all([cred.exists(), did.exists()]): return - did = did[0].did + did = did[0] cred = cred[0] try: cred.issue(did) @@ -56,42 +60,30 @@ class RequestCredentialForm(forms.Form): return - -class CredentialPresentationForm(forms.Form): - organization = forms.ChoiceField(choices=[]) - credential = forms.ChoiceField(choices=[]) +class DemandAuthorizationForm(forms.Form): + organization = forms.ChoiceField(label=_("Organization"), 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 - ) + (x.id, x.name) for x in Organization.objects.filter() + if x.response_uri != settings.RESPONSE_URI ] def save(self, commit=True): self.org = Organization.objects.filter( id=self.data['organization'] ) - self.cred = VerificableCredential.objects.filter( - user=self.user, - id=self.data['credential'], - status=VerificableCredential.Status.ISSUED - ) - if not all([self.org.exists(), self.cred.exists()]): + if not self.org.exists(): return self.org = self.org[0] - self.cred = self.cred[0] if commit: - self.org.send(self.cred) - return self.cred + url = self.org.demand_authorization() + if url.status_code == 200: + return url.json().get('redirect_uri') return diff --git a/idhub/user/views.py b/idhub/user/views.py index 482b40e..e6e28dc 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -12,7 +12,11 @@ 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, RequestCredentialForm, CredentialPresentationForm +from idhub.user.forms import ( + ProfileForm, + RequestCredentialForm, + DemandAuthorizationForm +) from idhub.mixins import UserView from idhub.models import DID, VerificableCredential, Event @@ -76,8 +80,11 @@ class CredentialsView(MyWallet, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + creds = VerificableCredential.objects.filter( + user=self.request.user + ) context.update({ - 'credentials': VerificableCredential.objects, + 'credentials': creds, }) return context @@ -136,17 +143,20 @@ class CredentialsRequestView(MyWallet, FormView): messages.success(self.request, _("The credential was issued successfully!")) Event.set_EV_CREDENTIAL_ISSUED_FOR_USER(cred) Event.set_EV_CREDENTIAL_ISSUED(cred) + url = self.request.session.pop('next_url', None) + if url: + return redirect(url) else: messages.error(self.request, _("The credential does not exist!")) return super().form_valid(form) -class CredentialsPresentationView(MyWallet, FormView): +class DemandAuthorizationView(MyWallet, FormView): template_name = "idhub/user/credentials_presentation.html" subtitle = _('Credential presentation') icon = 'bi bi-patch-check-fill' - form_class = CredentialPresentationForm - success_url = reverse_lazy('idhub:user_credentials') + form_class = DemandAuthorizationForm + success_url = reverse_lazy('idhub:user_demand_authorization') def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -154,11 +164,17 @@ class CredentialsPresentationView(MyWallet, FormView): return kwargs def form_valid(self, form): - cred = form.save() - if cred: - Event.set_EV_CREDENTIAL_PRESENTED_BY_USER(cred, form.org) - Event.set_EV_CREDENTIAL_PRESENTED(cred, form.org) - messages.success(self.request, _("The credential was presented successfully!")) + try: + authorization = form.save() + except Exception: + txt = _("Problems connecting with {url}").format( + url=form.org.response_uri + ) + messages.error(self.request, txt) + return super().form_valid(form) + + if authorization: + return redirect(authorization) else: messages.error(self.request, _("Error sending credential!")) return super().form_valid(form) diff --git a/idhub/verification_portal/__init__.py b/idhub/verification_portal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/idhub/verification_portal/models.py b/idhub/verification_portal/models.py new file mode 100644 index 0000000..0bd203a --- /dev/null +++ b/idhub/verification_portal/models.py @@ -0,0 +1,24 @@ +from django.db import models + + +class VPVerifyRequest(models.Model): + """ + `nonce` is an opaque random string used to lookup verification requests. URL-safe. + Example: "UPBQ3JE2DGJYHP5CPSCRIGTHRTCYXMQPNQ" + `expected_credentials` is a JSON list of credential types that must be present in this VP. + Example: ["FinancialSituationCredential", "HomeConnectivitySurveyCredential"] + `expected_contents` is a JSON object that places optional constraints on the contents of the + returned VP. + Example: [{"FinancialSituationCredential": {"financial_vulnerability_score": "7"}}] + `action` is (for now) a JSON object describing the next steps to take if this verification + is successful. For example "send mail to with and " + Example: {"action": "send_mail", "params": {"to": "orders@somconnexio.coop", "subject": "New client", "body": ...} + `response` is a URL that the user's wallet will redirect the user to. + `submitted_on` is used (by a cronjob) to purge old entries that didn't complete verification + """ + nonce = models.CharField(max_length=50) + expected_credentials = models.CharField(max_length=255) + expected_contents = models.TextField() + action = models.TextField() + response_or_redirect = models.CharField(max_length=255) + submitted_on = models.DateTimeField(auto_now=True) diff --git a/idhub/verification_portal/views.py b/idhub/verification_portal/views.py new file mode 100644 index 0000000..486f4f7 --- /dev/null +++ b/idhub/verification_portal/views.py @@ -0,0 +1,49 @@ +import json + +from django.core.mail import send_mail +from django.http import HttpResponse, HttpResponseRedirect + +from utils.idhub_ssikit import verify_presentation +from .models import VPVerifyRequest +from django.shortcuts import get_object_or_404 +from more_itertools import flatten, unique_everseen + + +def verify(request): + assert request.method == "POST" + # TODO: incorporate request.POST["presentation_submission"] as schema definition + (presentation_valid, _) = verify_presentation(request.POST["vp_token"]) + if not presentation_valid: + raise Exception("Failed to verify signature on the given Verifiable Presentation.") + vp = json.loads(request.POST["vp_token"]) + nonce = vp["nonce"] + # "vr" = verification_request + vr = get_object_or_404(VPVerifyRequest, nonce=nonce) # TODO: return meaningful error, not 404 + # Get a list of all included verifiable credential types + included_credential_types = unique_everseen(flatten([ + vc["type"] for vc in vp["verifiableCredential"] + ])) + # Check that it matches what we requested + for requested_vc_type in json.loads(vr.expected_credentials): + if requested_vc_type not in included_credential_types: + raise Exception("You're missing some credentials we requested!") # TODO: return meaningful error + # Perform whatever action we have to do + action = json.loads(vr.action) + if action["action"] == "send_mail": + subject = action["params"]["subject"] + to_email = action["params"]["to"] + from_email = "noreply@verifier-portal" + body = request.POST["vp-token"] + send_mail( + subject, + body, + from_email, + [to_email] + ) + elif action["action"] == "something-else": + pass + else: + raise Exception("Unknown action!") + # OK! Your verifiable presentation was successfully presented. + return HttpResponseRedirect(vr.response_or_redirect) + diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py index fa771d4..d9ff2f7 100644 --- a/idhub_auth/forms.py +++ b/idhub_auth/forms.py @@ -16,7 +16,7 @@ class ProfileForm(forms.ModelForm): def clean_first_name(self): first_name = super().clean()['first_name'] match = r'^[a-zA-ZäöüßÄÖÜáéíóúàèìòùÀÈÌÒÙÁÉÍÓÚßñÑçÇ\s\'-]+' - if not re.match(match, first_name): + if not re.fullmatch(match, first_name): txt = _("The string must contain only characters and spaces") raise forms.ValidationError(txt) @@ -25,7 +25,7 @@ class ProfileForm(forms.ModelForm): def clean_last_name(self): last_name = super().clean()['last_name'] match = r'^[a-zA-ZäöüßÄÖÜáéíóúàèìòùÀÈÌÒÙÁÉÍÓÚßñÑçÇ\s\'-]+' - if not re.match(match, last_name): + if not re.fullmatch(match, last_name): txt = _("The string must contain only characters and spaces") raise forms.ValidationError(txt) diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py index 5655a6f..8ea6578 100644 --- a/idhub_auth/migrations/0001_initial.py +++ b/idhub_auth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2024-01-04 15:12 +# Generated by Django 4.2.5 on 2024-01-04 16:59 from django.db import migrations, models @@ -31,13 +31,23 @@ class Migration(migrations.Migration): ( 'email', models.EmailField( - max_length=255, unique=True, verbose_name='email address' + 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)), + ( + 'first_name', + models.CharField( + blank=True, max_length=255, null=True, verbose_name='First name' + ), + ), + ( + 'last_name', + models.CharField( + blank=True, max_length=255, null=True, verbose_name='Last name' + ), + ), ('encrypted_sensitive_data', models.CharField(max_length=255)), ('salt', models.CharField(max_length=255)), ], diff --git a/idhub_auth/models.py b/idhub_auth/models.py index 7c3434d..aed2199 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -4,6 +4,7 @@ import base64 from nacl import pwhash from django.db import models from django.core.cache import cache +from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import BaseUserManager, AbstractBaseUser @@ -40,14 +41,14 @@ class UserManager(BaseUserManager): class User(AbstractBaseUser): email = models.EmailField( - verbose_name="email address", + _('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) + first_name = models.CharField(_("First name"), max_length=255, blank=True, null=True) + last_name = models.CharField(_("Last name"), max_length=255, blank=True, null=True) encrypted_sensitive_data = models.CharField(max_length=255) salt = models.CharField(max_length=255) @@ -144,4 +145,21 @@ class User(AbstractBaseUser): key_crypted = self.encrypt_sensitive_data(password, key) self.encrypted_sensitive_data = key_crypted + def encrypt_data(self, data): + sb = self.get_secret_box() + value = base64.b64encode(data.encode('utf-8')) + return sb.encrypt(data) + def decrypt_data(self, data): + sb = self.get_secret_box() + value = base64.b64decode(data.encode('utf-8')) + return sb.decrypt(data) + + def get_secret_box(self): + key_dids = cache.get("KEY_DIDS", {}) + if not key_dids.get(self.id): + err = "An attempt is made to access encrypted " + err += "data without having the key." + raise Exception(_(err)) + + return secret.SecretBox(key_dids[self.id]) diff --git a/locale/ca/LC_MESSAGES/django.mo b/locale/ca/LC_MESSAGES/django.mo new file mode 100644 index 0000000..965887f Binary files /dev/null and b/locale/ca/LC_MESSAGES/django.mo differ diff --git a/locale/ca_ES/LC_MESSAGES/django.po b/locale/ca/LC_MESSAGES/django.po similarity index 72% rename from locale/ca_ES/LC_MESSAGES/django.po rename to locale/ca/LC_MESSAGES/django.po index ff421ea..c8a7ede 100644 --- a/locale/ca_ES/LC_MESSAGES/django.po +++ b/locale/ca/LC_MESSAGES/django.po @@ -3,227 +3,231 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-11-21 14:37+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2023-11-27 20:37+0100\n" +"Last-Translator: Leandro Navarro \n" +"Language-Team: \n" +"Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.2.2\n" #: env/lib/python3.11/site-packages/bootstrap4/components.py:17 #: env/lib/python3.11/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3 #: env/lib/python3.11/site-packages/bootstrap4/templates/bootstrap4/messages.html:4 #: env/lib/python3.11/site-packages/django_bootstrap5/components.py:26 msgid "close" -msgstr "" +msgstr "tanca" #: env/lib/python3.11/site-packages/click/_termui_impl.py:518 #, python-brace-format msgid "{editor}: Editing failed" -msgstr "" +msgstr "{editor}: Error en l'edició" #: env/lib/python3.11/site-packages/click/_termui_impl.py:522 #, python-brace-format msgid "{editor}: Editing failed: {e}" -msgstr "" +msgstr "{editor}: Error en l'edició: {e}" #: env/lib/python3.11/site-packages/click/core.py:1120 msgid "Aborted!" -msgstr "" +msgstr "Interromput!" #: env/lib/python3.11/site-packages/click/core.py:1309 #: env/lib/python3.11/site-packages/click/decorators.py:559 msgid "Show this message and exit." -msgstr "" +msgstr "Mostra aquest missatge i surt." #: env/lib/python3.11/site-packages/click/core.py:1340 #: env/lib/python3.11/site-packages/click/core.py:1370 #, python-brace-format msgid "(Deprecated) {text}" -msgstr "" +msgstr "(Obsolet) {text}" #: env/lib/python3.11/site-packages/click/core.py:1387 msgid "Options" -msgstr "" +msgstr "Opcions" #: env/lib/python3.11/site-packages/click/core.py:1413 #, python-brace-format msgid "Got unexpected extra argument ({args})" msgid_plural "Got unexpected extra arguments ({args})" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "S'ha trobat un argument addicional inesperat ({args})" +msgstr[1] "S'han trobat uns arguments addicionals inesperats ({args})" #: env/lib/python3.11/site-packages/click/core.py:1429 msgid "DeprecationWarning: The command {name!r} is deprecated." -msgstr "" +msgstr "Advertència obsolescència: l'ordre {name!r} està obsoleta." #: env/lib/python3.11/site-packages/click/core.py:1636 msgid "Commands" -msgstr "" +msgstr "Ordres" #: env/lib/python3.11/site-packages/click/core.py:1668 msgid "Missing command." -msgstr "" +msgstr "Falta l'ordre." #: env/lib/python3.11/site-packages/click/core.py:1746 msgid "No such command {name!r}." -msgstr "" +msgstr "No hi ha aquesta comanda {name!r}." #: env/lib/python3.11/site-packages/click/core.py:2310 msgid "Value must be an iterable." -msgstr "" +msgstr "El valor ha de ser iterable." #: env/lib/python3.11/site-packages/click/core.py:2331 #, python-brace-format msgid "Takes {nargs} values but 1 was given." msgid_plural "Takes {nargs} values but {len} were given." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Pren {nargs} valors però s'ha donat 1." +msgstr[1] "Pren {nargs} valors però s'han donat {len}." #: env/lib/python3.11/site-packages/click/core.py:2778 #, python-brace-format msgid "env var: {var}" -msgstr "" +msgstr "env var: {var}" #: env/lib/python3.11/site-packages/click/core.py:2808 msgid "(dynamic)" -msgstr "" +msgstr "(dinàmic)" #: env/lib/python3.11/site-packages/click/core.py:2821 #, python-brace-format msgid "default: {default}" -msgstr "" +msgstr "per defecte: {default}" #: env/lib/python3.11/site-packages/click/core.py:2834 msgid "required" -msgstr "" +msgstr "obligatori" #: env/lib/python3.11/site-packages/click/decorators.py:465 #, python-format msgid "%(prog)s, version %(version)s" -msgstr "" +msgstr "%(prog)s, versió %(version)s" #: env/lib/python3.11/site-packages/click/decorators.py:528 msgid "Show the version and exit." -msgstr "" +msgstr "Mostra la versió i surt." #: env/lib/python3.11/site-packages/click/exceptions.py:44 #: env/lib/python3.11/site-packages/click/exceptions.py:80 #, python-brace-format msgid "Error: {message}" -msgstr "" +msgstr "Error: {message}" #: env/lib/python3.11/site-packages/click/exceptions.py:72 #, python-brace-format msgid "Try '{command} {option}' for help." -msgstr "" +msgstr "Proveu ‘{command} {option}’ per obtenir ajuda." #: env/lib/python3.11/site-packages/click/exceptions.py:121 #, python-brace-format msgid "Invalid value: {message}" -msgstr "" +msgstr "Valor no vàlid: {message}" #: env/lib/python3.11/site-packages/click/exceptions.py:123 #, python-brace-format msgid "Invalid value for {param_hint}: {message}" -msgstr "" +msgstr "Valor no vàlid per a {param_hint}: {message}" #: env/lib/python3.11/site-packages/click/exceptions.py:179 msgid "Missing argument" -msgstr "" +msgstr "Manca l'argument" #: env/lib/python3.11/site-packages/click/exceptions.py:181 msgid "Missing option" -msgstr "" +msgstr "Falta opció" #: env/lib/python3.11/site-packages/click/exceptions.py:183 msgid "Missing parameter" -msgstr "" +msgstr "Falta un paràmetre" #: env/lib/python3.11/site-packages/click/exceptions.py:185 #, python-brace-format msgid "Missing {param_type}" -msgstr "" +msgstr "Falta {param_type}" #: env/lib/python3.11/site-packages/click/exceptions.py:192 #, python-brace-format msgid "Missing parameter: {param_name}" -msgstr "" +msgstr "Falta el paràmetre: {param_name}" #: env/lib/python3.11/site-packages/click/exceptions.py:212 #, python-brace-format msgid "No such option: {name}" -msgstr "" +msgstr "No hi ha aquesta opció: {name}" #: env/lib/python3.11/site-packages/click/exceptions.py:224 #, python-brace-format msgid "Did you mean {possibility}?" msgid_plural "(Possible options: {possibilities})" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Voleu dir {possibility}?" +msgstr[1] "(Opcions possibles: {possibilities})" #: env/lib/python3.11/site-packages/click/exceptions.py:262 msgid "unknown error" -msgstr "" +msgstr "error desconegut" #: env/lib/python3.11/site-packages/click/exceptions.py:269 msgid "Could not open file {filename!r}: {message}" -msgstr "" +msgstr "No s'ha pogut obrir el fitxer {filename!r}: {message}" #: env/lib/python3.11/site-packages/click/parser.py:231 msgid "Argument {name!r} takes {nargs} values." -msgstr "" +msgstr "L'argument {name!r} pren {nargs} valors." #: env/lib/python3.11/site-packages/click/parser.py:413 msgid "Option {name!r} does not take a value." -msgstr "" +msgstr "L'opció {name!r} no pren cap valor." #: env/lib/python3.11/site-packages/click/parser.py:474 msgid "Option {name!r} requires an argument." msgid_plural "Option {name!r} requires {nargs} arguments." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "L'opció {name!r} requereix un argument." +msgstr[1] "L'opció {name!r} requereix {nargs} arguments." #: env/lib/python3.11/site-packages/click/shell_completion.py:319 msgid "Shell completion is not supported for Bash versions older than 4.4." msgstr "" +"La finalització de l'intèrpret d'ordres no és compatible amb les versions de " +"Bash anteriors a la 4.4." #: env/lib/python3.11/site-packages/click/shell_completion.py:326 msgid "Couldn't detect Bash version, shell completion is not supported." msgstr "" +"No s'ha pogut detectar la versió de Bash, la finalització de l'intèrpret " +"d'ordres no és compatible." #: env/lib/python3.11/site-packages/click/termui.py:158 msgid "Repeat for confirmation" -msgstr "" +msgstr "Repetiu per confirmar" #: env/lib/python3.11/site-packages/click/termui.py:174 msgid "Error: The value you entered was invalid." -msgstr "" +msgstr "Error: el valor que heu introduït no és vàlid." #: env/lib/python3.11/site-packages/click/termui.py:176 #, python-brace-format msgid "Error: {e.message}" -msgstr "" +msgstr "Error: {e.message}" #: env/lib/python3.11/site-packages/click/termui.py:187 msgid "Error: The two entered values do not match." -msgstr "" +msgstr "Error: els dos valors introduïts no coincideixen." #: env/lib/python3.11/site-packages/click/termui.py:243 msgid "Error: invalid input" -msgstr "" +msgstr "Error: entrada no vàlida" #: env/lib/python3.11/site-packages/click/termui.py:773 msgid "Press any key to continue..." -msgstr "" +msgstr "Premeu qualsevol tecla per continuar..." #: env/lib/python3.11/site-packages/click/types.py:266 #, python-brace-format @@ -231,182 +235,193 @@ msgid "" "Choose from:\n" "\t{choices}" msgstr "" +"Trieu entre:\n" +"\t{choices}" #: env/lib/python3.11/site-packages/click/types.py:298 msgid "{value!r} is not {choice}." msgid_plural "{value!r} is not one of {choices}." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "{value!r} no és {choice}." +msgstr[1] "{value!r} no és un dels {choices}." #: env/lib/python3.11/site-packages/click/types.py:392 msgid "{value!r} does not match the format {format}." msgid_plural "{value!r} does not match the formats {formats}." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "{value!r} no coincideix amb el format {format}." +msgstr[1] "{value!r} no coincideix amb els formats {formats}." #: env/lib/python3.11/site-packages/click/types.py:414 msgid "{value!r} is not a valid {number_type}." -msgstr "" +msgstr "{value!r} no és un {number_type} vàlid." #: env/lib/python3.11/site-packages/click/types.py:470 #, python-brace-format msgid "{value} is not in the range {range}." -msgstr "" +msgstr "{value} no es troba a l'interval {range}." #: env/lib/python3.11/site-packages/click/types.py:611 msgid "{value!r} is not a valid boolean." -msgstr "" +msgstr "{value!r} no és un booleà vàlid." #: env/lib/python3.11/site-packages/click/types.py:635 msgid "{value!r} is not a valid UUID." -msgstr "" +msgstr "{value!r} no és un UUID vàlid." #: env/lib/python3.11/site-packages/click/types.py:822 msgid "file" -msgstr "" +msgstr "arxiu" #: env/lib/python3.11/site-packages/click/types.py:824 msgid "directory" -msgstr "" +msgstr "directori" #: env/lib/python3.11/site-packages/click/types.py:826 msgid "path" -msgstr "" +msgstr "ruta" #: env/lib/python3.11/site-packages/click/types.py:877 msgid "{name} {filename!r} does not exist." -msgstr "" +msgstr "{name} {filename!r} no existeix." #: env/lib/python3.11/site-packages/click/types.py:886 msgid "{name} {filename!r} is a file." -msgstr "" +msgstr "{name} {filename!r} és un fitxer." #: env/lib/python3.11/site-packages/click/types.py:894 #, python-brace-format msgid "{name} '{filename}' is a directory." -msgstr "" +msgstr "{name} '{filename}' és un directori." #: env/lib/python3.11/site-packages/click/types.py:903 msgid "{name} {filename!r} is not readable." -msgstr "" +msgstr "{name} {filename!r} no es pot llegir." #: env/lib/python3.11/site-packages/click/types.py:912 msgid "{name} {filename!r} is not writable." -msgstr "" +msgstr "{name} {filename!r} no es pot escriure." #: env/lib/python3.11/site-packages/click/types.py:921 msgid "{name} {filename!r} is not executable." -msgstr "" +msgstr "{name} {filename!r} no és executable." #: env/lib/python3.11/site-packages/click/types.py:988 #, python-brace-format msgid "{len_type} values are required, but {len_value} was given." msgid_plural "{len_type} values are required, but {len_value} were given." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Es requereixen {len_type} valors, però s'ha donat {len_value}." +msgstr[1] "Es requereixen {len_type} valors, però s'han donat {len_value}." #: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 msgid "Messages" -msgstr "" +msgstr "Missatges" #: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 msgid "Site Maps" -msgstr "" +msgstr "Mapes del lloc" #: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 msgid "Static Files" -msgstr "" +msgstr "Fitxers estàtics" #: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 msgid "Syndication" -msgstr "" +msgstr "Sindicació" #. Translators: String used to replace omitted page numbers in elided page #. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. #: env/lib/python3.11/site-packages/django/core/paginator.py:30 msgid "…" -msgstr "" +msgstr "…" #: env/lib/python3.11/site-packages/django/core/paginator.py:50 msgid "That page number is not an integer" -msgstr "" +msgstr "Aquest número de pàgina no és un nombre enter" #: env/lib/python3.11/site-packages/django/core/paginator.py:52 msgid "That page number is less than 1" -msgstr "" +msgstr "Aquest número de pàgina és inferior a 1" #: env/lib/python3.11/site-packages/django/core/paginator.py:54 msgid "That page contains no results" -msgstr "" +msgstr "Aquesta pàgina no conté cap resultat" #: env/lib/python3.11/site-packages/django/core/validators.py:22 msgid "Enter a valid value." -msgstr "" +msgstr "Introduïu un valor vàlid." #: env/lib/python3.11/site-packages/django/core/validators.py:104 #: env/lib/python3.11/site-packages/django/forms/fields.py:752 msgid "Enter a valid URL." -msgstr "" +msgstr "Introduïu un URL vàlid." #: env/lib/python3.11/site-packages/django/core/validators.py:165 msgid "Enter a valid integer." -msgstr "" +msgstr "Introduïu un nombre enter vàlid." #: env/lib/python3.11/site-packages/django/core/validators.py:176 msgid "Enter a valid email address." -msgstr "" +msgstr "Introduïu una adreça electrònica vàlida." #. Translators: "letters" means latin letters: a-z and A-Z. #: env/lib/python3.11/site-packages/django/core/validators.py:259 msgid "" "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." msgstr "" +"Introduïu un \"slug\" vàlid format per lletres, números, guions baixos o " +"guions." #: env/lib/python3.11/site-packages/django/core/validators.py:267 msgid "" "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" +"Introduïu un \"slug\" vàlid format per lletres Unicode, números, guions " +"baixos o guions." #: env/lib/python3.11/site-packages/django/core/validators.py:279 #: env/lib/python3.11/site-packages/django/core/validators.py:287 #: env/lib/python3.11/site-packages/django/core/validators.py:316 msgid "Enter a valid IPv4 address." -msgstr "" +msgstr "Introduïu una adreça IPv4 vàlida." #: env/lib/python3.11/site-packages/django/core/validators.py:296 #: env/lib/python3.11/site-packages/django/core/validators.py:317 msgid "Enter a valid IPv6 address." -msgstr "" +msgstr "Introduïu una adreça IPv6 vàlida." #: env/lib/python3.11/site-packages/django/core/validators.py:308 #: env/lib/python3.11/site-packages/django/core/validators.py:315 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Introduïu una adreça IPv4 o IPv6 vàlida." #: env/lib/python3.11/site-packages/django/core/validators.py:351 msgid "Enter only digits separated by commas." -msgstr "" +msgstr "Introduïu només els dígits separats per comes." #: env/lib/python3.11/site-packages/django/core/validators.py:357 #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" +"Assegureu-vos que aquest valor sigui %(limit_value)s (és %(show_value)s)." #: env/lib/python3.11/site-packages/django/core/validators.py:392 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" +"Assegureu-vos que aquest valor sigui inferior o igual a %(limit_value)s." #: env/lib/python3.11/site-packages/django/core/validators.py:401 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" +"Assegureu-vos que aquest valor sigui superior o igual a %(limit_value)s." #: env/lib/python3.11/site-packages/django/core/validators.py:410 #, python-format msgid "Ensure this value is a multiple of step size %(limit_value)s." msgstr "" +"Assegureu-vos que aquest valor sigui un múltiple de la mida del pas " +"%(limit_value)s." #: env/lib/python3.11/site-packages/django/core/validators.py:420 #, python-format @@ -417,7 +432,11 @@ msgid_plural "" "Ensure this value has at least %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Assegureu-vos que aquest valor tingui com a mínim %(limit_value)d caràcter " +"(té %(show_value)d)." msgstr[1] "" +"Assegureu-vos que aquest valor tingui com a mínim %(limit_value)d caràcters " +"(té %(show_value)d)." #: env/lib/python3.11/site-packages/django/core/validators.py:438 #, python-format @@ -428,27 +447,31 @@ msgid_plural "" "Ensure this value has at most %(limit_value)d characters (it has " "%(show_value)d)." msgstr[0] "" +"Assegureu-vos que aquest valor tingui com a màxim %(limit_value)d caràcters " +"(té %(show_value)d)." msgstr[1] "" +"Assegureu-vos que aquest valor tingui com a màxim %(limit_value)d caràcters " +"(té %(show_value)d)." #: env/lib/python3.11/site-packages/django/core/validators.py:461 #: env/lib/python3.11/site-packages/django/forms/fields.py:347 #: env/lib/python3.11/site-packages/django/forms/fields.py:386 msgid "Enter a number." -msgstr "" +msgstr "Introduïu una xifra." #: env/lib/python3.11/site-packages/django/core/validators.py:463 #, python-format msgid "Ensure that there are no more than %(max)s digit in total." msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Assegureu-vos que no hi hagi més de %(max)s dígits en total." +msgstr[1] "Assegureu-vos que no hi hagi més de %(max)s dígits en total." #: env/lib/python3.11/site-packages/django/core/validators.py:468 #, python-format msgid "Ensure that there are no more than %(max)s decimal place." msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Assegureu-vos que no hi hagi més de %(max)s decimals." +msgstr[1] "Assegureu-vos que no hi hagi més de %(max)s decimals." #: env/lib/python3.11/site-packages/django/core/validators.py:473 #, python-format @@ -457,7 +480,9 @@ msgid "" msgid_plural "" "Ensure that there are no more than %(max)s digits before the decimal point." msgstr[0] "" +"Assegureu-vos que no hi hagi més de %(max)s dígits abans del punt decimal." msgstr[1] "" +"Assegureu-vos que no hi hagi més de %(max)s dígits abans del punt decimal." #: env/lib/python3.11/site-packages/django/core/validators.py:544 #, python-format @@ -465,43 +490,45 @@ msgid "" "File extension “%(extension)s” is not allowed. Allowed extensions are: " "%(allowed_extensions)s." msgstr "" +"L'extensió de fitxer “%(extension)s” no està permesa. Les extensions " +"permeses són: %(allowed_extensions)s." #: env/lib/python3.11/site-packages/django/core/validators.py:605 msgid "Null characters are not allowed." -msgstr "" +msgstr "No es permeten caràcters nuls." #: env/lib/python3.11/site-packages/django/db/models/base.py:1423 #: env/lib/python3.11/site-packages/django/forms/models.py:893 msgid "and" -msgstr "" +msgstr "i" #: env/lib/python3.11/site-packages/django/db/models/base.py:1425 #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "" +msgstr "%(model_name)s amb aquest %(field_labels)s ja existeix." #: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 #, python-format msgid "Constraint “%(name)s” is violated." -msgstr "" +msgstr "S'ha incomplert la restricció \"%(name)s\"." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 #, python-format msgid "Value %(value)r is not a valid choice." -msgstr "" +msgstr "El valor %(value)r no és una opció vàlida." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 msgid "This field cannot be null." -msgstr "" +msgstr "Aquest camp no pot ser nul." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 msgid "This field cannot be blank." -msgstr "" +msgstr "Aquest camp no pot estar en blanc." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "" +msgstr "%(model_name)s amb aquest %(field_label)s ja existeix." #. Translators: The 'lookup_type' is one of 'date', 'year' or #. 'month'. Eg: "Title must be unique for pub_date year" @@ -510,38 +537,39 @@ msgstr "" msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" +"%(field_label)s ha de ser únic per a %(date_field_label)s %(lookup_type)s." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 #, python-format msgid "Field of type: %(field_type)s" -msgstr "" +msgstr "Camp de tipus: %(field_type)s" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 #, python-format msgid "“%(value)s” value must be either True or False." -msgstr "" +msgstr "El valor \"%(value)s\" ha de ser Cert o Fals." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 #, python-format msgid "“%(value)s” value must be either True, False, or None." -msgstr "" +msgstr "El valor \"%(value)s\" ha de ser Cert, Fals o Cap." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 msgid "Boolean (Either True or False)" -msgstr "" +msgstr "Booleà (cert o fals)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 #, python-format msgid "String (up to %(max_length)s)" -msgstr "" +msgstr "Cadena (fins a %(max_length)s)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 msgid "String (unlimited)" -msgstr "" +msgstr "Cadena (il·limitada)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 msgid "Comma-separated integers" -msgstr "" +msgstr "Nombres enters separats per comes" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 #, python-format @@ -549,6 +577,8 @@ msgid "" "“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" +"El valor \"%(value)s\" té un format de data no vàlid. Ha de tenir el format " +"AAAA-MM-DD." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 @@ -557,10 +587,12 @@ msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" +"El valor \"%(value)s\" té el format correcte (AAAA-MM-DD), però no és una " +"data vàlida." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 msgid "Date (without time)" -msgstr "" +msgstr "Data (sense hora)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 #, python-format @@ -568,6 +600,8 @@ msgid "" "“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" +"El valor \"%(value)s\" té un format no vàlid. Ha de tenir el format AAAA-MM-" +"DD HH:MM[:ss[.uuuuuu]][TZ]." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 #, python-format @@ -575,19 +609,21 @@ msgid "" "“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]) but it is an invalid date/time." msgstr "" +"El valor “%(value)s” té el format correcte (AAAA-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]), però no és una data/hora vàlida." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 msgid "Date (with time)" -msgstr "" +msgstr "Data (amb hora)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 #, python-format msgid "“%(value)s” value must be a decimal number." -msgstr "" +msgstr "El valor “%(value)s” ha de ser un nombre decimal." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 msgid "Decimal number" -msgstr "" +msgstr "Nombre decimal" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 #, python-format @@ -595,83 +631,85 @@ msgid "" "“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." "uuuuuu] format." msgstr "" +"El valor \"%(value)s\" té un format no vàlid. Ha d'estar en format [DD] " +"[[HH:]MM:]ss[.uuuuuu]." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 msgid "Duration" -msgstr "" +msgstr "Durada" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 msgid "Email address" -msgstr "" +msgstr "Adreça de correu electrònic" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 msgid "File path" -msgstr "" +msgstr "Ruta al document" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 #, python-format msgid "“%(value)s” value must be a float." -msgstr "" +msgstr "El valor “%(value)s” ha de ser un valor flotant." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 msgid "Floating point number" -msgstr "" +msgstr "Nombre de coma flotant" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 #, python-format msgid "“%(value)s” value must be an integer." -msgstr "" +msgstr "El valor “%(value)s” ha de ser un nombre enter." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 msgid "Integer" -msgstr "" +msgstr "Enter" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 msgid "Big (8 byte) integer" -msgstr "" +msgstr "Enter gran (8 bytes)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 msgid "Small integer" -msgstr "" +msgstr "Enter petit" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 msgid "IPv4 address" -msgstr "" +msgstr "Adreça IPv4" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 msgid "IP address" -msgstr "" +msgstr "Adreça IP" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 #, python-format msgid "“%(value)s” value must be either None, True or False." -msgstr "" +msgstr "El valor \"%(value)s\" ha de ser Cap, Cert o Fals." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 msgid "Boolean (Either True, False or None)" -msgstr "" +msgstr "Booleà (cert, fals o cap)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 msgid "Positive big integer" -msgstr "" +msgstr "Enter gran positiu" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 msgid "Positive integer" -msgstr "" +msgstr "Enter positiu" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 msgid "Positive small integer" -msgstr "" +msgstr "Enter petit positiu" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 #, python-format msgid "Slug (up to %(max_length)s)" -msgstr "" +msgstr "Slug (fins a %(max_length)s)" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 msgid "Text" -msgstr "" +msgstr "Text" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 #, python-format @@ -679,6 +717,8 @@ msgid "" "“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" +"El valor \"%(value)s\" té un format no vàlid. Ha d'estar en format HH:MM[:" +"ss[.uuuuuu]]." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 #, python-format @@ -686,120 +726,123 @@ msgid "" "“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" +"El valor \"%(value)s\" té el format correcte (HH:MM[:ss[.uuuuuu]]), però no " +"és una hora vàlida." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 msgid "Time" -msgstr "" +msgstr "Hora" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 msgid "URL" -msgstr "" +msgstr "URL" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 msgid "Raw binary data" -msgstr "" +msgstr "Dades binàries en brut" #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 #, python-format msgid "“%(value)s” is not a valid UUID." -msgstr "" +msgstr "“%(value)s” no és un UUID vàlid." #: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 msgid "Universally unique identifier" -msgstr "" +msgstr "Identificador únic universal" #: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 #: idhub/templates/idhub/admin/import.html:16 msgid "File" -msgstr "" +msgstr "Fitxer" #: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 msgid "Image" -msgstr "" +msgstr "Imatge" #: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 msgid "A JSON object" -msgstr "" +msgstr "Un objecte JSON" #: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 msgid "Value must be valid JSON." -msgstr "" +msgstr "El valor ha de ser un JSON vàlid." #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "" +msgstr "%(model)s instància amb %(field)s %(value)r no existeix." #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 msgid "Foreign Key (type determined by related field)" -msgstr "" +msgstr "Clau externa (tipus determinat pel camp relacionat)" #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 msgid "One-to-one relationship" -msgstr "" +msgstr "Relació un a un" #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "" +msgstr "relació %(from)s-%(to)s" #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "" +msgstr "relacions %(from)s-%(to)s" #: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 msgid "Many-to-many relationship" -msgstr "" +msgstr "Relació de molts a molts" #. Translators: If found as last label character, these punctuation #. characters will prevent the default label_suffix to be appended to the label #: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 msgid ":?.!" -msgstr "" +msgstr ":?.!" #: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." -msgstr "" +msgstr "Aquest camp és obligatori." #: env/lib/python3.11/site-packages/django/forms/fields.py:298 msgid "Enter a whole number." -msgstr "" +msgstr "Introduïu un número sencer." #: env/lib/python3.11/site-packages/django/forms/fields.py:467 #: env/lib/python3.11/site-packages/django/forms/fields.py:1241 msgid "Enter a valid date." -msgstr "" +msgstr "Introduïu una data vàlida." #: env/lib/python3.11/site-packages/django/forms/fields.py:490 #: env/lib/python3.11/site-packages/django/forms/fields.py:1242 msgid "Enter a valid time." -msgstr "" +msgstr "Introduïu una hora vàlida." #: env/lib/python3.11/site-packages/django/forms/fields.py:517 msgid "Enter a valid date/time." -msgstr "" +msgstr "Introduïu una data/hora vàlida." #: env/lib/python3.11/site-packages/django/forms/fields.py:551 msgid "Enter a valid duration." -msgstr "" +msgstr "Introduïu una durada vàlida." #: env/lib/python3.11/site-packages/django/forms/fields.py:552 #, python-brace-format msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" +msgstr "El nombre de dies ha d'estar entre {min_days} i {max_days}." #: env/lib/python3.11/site-packages/django/forms/fields.py:621 msgid "No file was submitted. Check the encoding type on the form." msgstr "" +"No s'ha enviat cap fitxer. Comproveu el tipus de codificació al formulari." #: env/lib/python3.11/site-packages/django/forms/fields.py:622 msgid "No file was submitted." -msgstr "" +msgstr "No s'ha enviat cap fitxer." #: env/lib/python3.11/site-packages/django/forms/fields.py:623 msgid "The submitted file is empty." -msgstr "" +msgstr "El fitxer enviat està buit." #: env/lib/python3.11/site-packages/django/forms/fields.py:625 #, python-format @@ -807,17 +850,23 @@ msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." msgid_plural "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr[0] "" +"Assegureu-vos que aquest nom de fitxer tingui com a màxim %(max)d caràcter " +"(té %(length)d)." msgstr[1] "" +"Assegureu-vos que aquest nom de fitxer tingui com a màxim %(max)d caràcters " +"(té %(length)d)." #: env/lib/python3.11/site-packages/django/forms/fields.py:630 msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "" +msgstr "Envieu un fitxer o marqueu la casella de selecció clara, no tots dos." #: env/lib/python3.11/site-packages/django/forms/fields.py:694 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" +"Carregueu una imatge vàlida. El fitxer que heu penjat no era una imatge o " +"una imatge malmesa." #: env/lib/python3.11/site-packages/django/forms/fields.py:857 #: env/lib/python3.11/site-packages/django/forms/fields.py:949 @@ -825,35 +874,36 @@ msgstr "" #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" +"Seleccioneu una opció vàlida. %(value)s no és una de les opcions disponibles." #: env/lib/python3.11/site-packages/django/forms/fields.py:951 #: env/lib/python3.11/site-packages/django/forms/fields.py:1070 #: env/lib/python3.11/site-packages/django/forms/models.py:1564 msgid "Enter a list of values." -msgstr "" +msgstr "Introduïu una llista de valors." #: env/lib/python3.11/site-packages/django/forms/fields.py:1071 msgid "Enter a complete value." -msgstr "" +msgstr "Introduïu un valor complet." #: env/lib/python3.11/site-packages/django/forms/fields.py:1310 msgid "Enter a valid UUID." -msgstr "" +msgstr "Introduïu un UUID vàlid." #: env/lib/python3.11/site-packages/django/forms/fields.py:1340 msgid "Enter a valid JSON." -msgstr "" +msgstr "Introduïu un JSON vàlid." #. Translators: This is the default suffix added to form field labels #: env/lib/python3.11/site-packages/django/forms/forms.py:98 msgid ":" -msgstr "" +msgstr ":" #: env/lib/python3.11/site-packages/django/forms/forms.py:244 #: env/lib/python3.11/site-packages/django/forms/forms.py:328 #, python-format msgid "(Hidden field %(name)s) %(error)s" -msgstr "" +msgstr "(Camp ocult %(name)s) %(error)s" #: env/lib/python3.11/site-packages/django/forms/formsets.py:63 #, python-format @@ -861,25 +911,28 @@ msgid "" "ManagementForm data is missing or has been tampered with. Missing fields: " "%(field_names)s. You may need to file a bug report if the issue persists." msgstr "" +"Les dades del Fomulario de Gestión falten o s'han manipulat. Camps que " +"falten: %(field_names)s. És possible que hàgiu de presentar un informe " +"d'error si el problema persisteix." #: env/lib/python3.11/site-packages/django/forms/formsets.py:67 #, python-format msgid "Please submit at most %(num)d form." msgid_plural "Please submit at most %(num)d forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Envieu com a màxim %(num)d formulari." +msgstr[1] "Envieu com a màxim %(num)d formularis." #: env/lib/python3.11/site-packages/django/forms/formsets.py:72 #, python-format msgid "Please submit at least %(num)d form." msgid_plural "Please submit at least %(num)d forms." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Envieu almenys %(num)d formulari." +msgstr[1] "Envieu almenys %(num)d formularis." #: env/lib/python3.11/site-packages/django/forms/formsets.py:484 #: env/lib/python3.11/site-packages/django/forms/formsets.py:491 msgid "Order" -msgstr "" +msgstr "Ordre" #: env/lib/python3.11/site-packages/django/forms/formsets.py:499 #: idhub/templates/idhub/admin/dids.html:54 @@ -899,17 +952,17 @@ msgstr "" #: idhub/templates/templates/musician/mailbox_check_delete.html:12 #: idhub/templates/templates/musician/mailbox_form.html:25 msgid "Delete" -msgstr "" +msgstr "Esborrar" #: env/lib/python3.11/site-packages/django/forms/models.py:886 #, python-format msgid "Please correct the duplicate data for %(field)s." -msgstr "" +msgstr "Corregiu les dades duplicades de %(field)s." #: env/lib/python3.11/site-packages/django/forms/models.py:891 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" +msgstr "Corregiu les dades duplicades de %(field)s, que han de ser úniques." #: env/lib/python3.11/site-packages/django/forms/models.py:898 #, python-format @@ -917,23 +970,27 @@ msgid "" "Please correct the duplicate data for %(field_name)s which must be unique " "for the %(lookup)s in %(date_field)s." msgstr "" +"Corregiu les dades duplicades per a %(field_name)s que han de ser úniques " +"per a %(lookup)s a %(date_field)s." #: env/lib/python3.11/site-packages/django/forms/models.py:907 msgid "Please correct the duplicate values below." -msgstr "" +msgstr "Corregiu els valors duplicats a continuació." #: env/lib/python3.11/site-packages/django/forms/models.py:1338 msgid "The inline value did not match the parent instance." -msgstr "" +msgstr "El valor en línia no coincideix amb la instància principal." #: env/lib/python3.11/site-packages/django/forms/models.py:1429 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" +"Seleccioneu una opció vàlida. Aquesta elecció no és una de les opcions " +"disponibles." #: env/lib/python3.11/site-packages/django/forms/models.py:1568 #, python-format msgid "“%(pk)s” is not a valid value." -msgstr "" +msgstr "\"%(pk)s\" no és un valor vàlid." #: env/lib/python3.11/site-packages/django/forms/utils.py:226 #, python-format @@ -941,434 +998,436 @@ msgid "" "%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " "may be ambiguous or it may not exist." msgstr "" +"%(datetime)s no s'ha pogut interpretar a la zona horària " +"%(current_timezone)s; pot ser ambigu o pot no existir." #: env/lib/python3.11/site-packages/django/forms/widgets.py:463 msgid "Clear" -msgstr "" +msgstr "Neteja" #: env/lib/python3.11/site-packages/django/forms/widgets.py:464 msgid "Currently" -msgstr "" +msgstr "Actualment" #: env/lib/python3.11/site-packages/django/forms/widgets.py:465 msgid "Change" -msgstr "" +msgstr "Canviar" #: env/lib/python3.11/site-packages/django/forms/widgets.py:794 msgid "Unknown" -msgstr "" +msgstr "Desconeguda" #: env/lib/python3.11/site-packages/django/forms/widgets.py:795 msgid "Yes" -msgstr "" +msgstr "Yes" #: env/lib/python3.11/site-packages/django/forms/widgets.py:796 msgid "No" -msgstr "" +msgstr "No" #. Translators: Please do not add spaces around commas. #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:861 msgid "yes,no,maybe" -msgstr "" +msgstr "sí,no,potser" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:891 #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:908 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(size)d byte" +msgstr[1] "%(size)d bytes" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:910 #, python-format msgid "%s KB" -msgstr "" +msgstr "%s KB" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:912 #, python-format msgid "%s MB" -msgstr "" +msgstr "%s MB" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:914 #, python-format msgid "%s GB" -msgstr "" +msgstr "%s GB" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:916 #, python-format msgid "%s TB" -msgstr "" +msgstr "%s TB" #: env/lib/python3.11/site-packages/django/template/defaultfilters.py:918 #, python-format msgid "%s PB" -msgstr "" +msgstr "%s PB" #: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 msgid "p.m." -msgstr "" +msgstr "p.m." #: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 msgid "a.m." -msgstr "" +msgstr "a.m." #: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 msgid "PM" -msgstr "" +msgstr "PM" #: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 msgid "AM" -msgstr "" +msgstr "AM" #: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 msgid "midnight" -msgstr "" +msgstr "mitjanit" #: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 msgid "noon" -msgstr "" +msgstr "migdia" #: env/lib/python3.11/site-packages/django/utils/dates.py:7 msgid "Monday" -msgstr "" +msgstr "Dilluns" #: env/lib/python3.11/site-packages/django/utils/dates.py:8 msgid "Tuesday" -msgstr "" +msgstr "Dimarts" #: env/lib/python3.11/site-packages/django/utils/dates.py:9 msgid "Wednesday" -msgstr "" +msgstr "Dimecres" #: env/lib/python3.11/site-packages/django/utils/dates.py:10 msgid "Thursday" -msgstr "" +msgstr "Dijous" #: env/lib/python3.11/site-packages/django/utils/dates.py:11 msgid "Friday" -msgstr "" +msgstr "Divendres" #: env/lib/python3.11/site-packages/django/utils/dates.py:12 msgid "Saturday" -msgstr "" +msgstr "Dissabte" #: env/lib/python3.11/site-packages/django/utils/dates.py:13 msgid "Sunday" -msgstr "" +msgstr "Diumenge" #: env/lib/python3.11/site-packages/django/utils/dates.py:16 msgid "Mon" -msgstr "" +msgstr "Dl" #: env/lib/python3.11/site-packages/django/utils/dates.py:17 msgid "Tue" -msgstr "" +msgstr "Dt" #: env/lib/python3.11/site-packages/django/utils/dates.py:18 msgid "Wed" -msgstr "" +msgstr "Dx" #: env/lib/python3.11/site-packages/django/utils/dates.py:19 msgid "Thu" -msgstr "" +msgstr "Dj" #: env/lib/python3.11/site-packages/django/utils/dates.py:20 msgid "Fri" -msgstr "" +msgstr "Dv" #: env/lib/python3.11/site-packages/django/utils/dates.py:21 msgid "Sat" -msgstr "" +msgstr "Ds" #: env/lib/python3.11/site-packages/django/utils/dates.py:22 msgid "Sun" -msgstr "" +msgstr "Dg" #: env/lib/python3.11/site-packages/django/utils/dates.py:25 msgid "January" -msgstr "" +msgstr "Gener" #: env/lib/python3.11/site-packages/django/utils/dates.py:26 msgid "February" -msgstr "" +msgstr "Febrer" #: env/lib/python3.11/site-packages/django/utils/dates.py:27 msgid "March" -msgstr "" +msgstr "Març" #: env/lib/python3.11/site-packages/django/utils/dates.py:28 msgid "April" -msgstr "" +msgstr "Abril" #: env/lib/python3.11/site-packages/django/utils/dates.py:29 msgid "May" -msgstr "" +msgstr "Maig" #: env/lib/python3.11/site-packages/django/utils/dates.py:30 msgid "June" -msgstr "" +msgstr "Juny" #: env/lib/python3.11/site-packages/django/utils/dates.py:31 msgid "July" -msgstr "" +msgstr "Juliol" #: env/lib/python3.11/site-packages/django/utils/dates.py:32 msgid "August" -msgstr "" +msgstr "Agost" #: env/lib/python3.11/site-packages/django/utils/dates.py:33 msgid "September" -msgstr "" +msgstr "Setembre" #: env/lib/python3.11/site-packages/django/utils/dates.py:34 msgid "October" -msgstr "" +msgstr "Octubre" #: env/lib/python3.11/site-packages/django/utils/dates.py:35 msgid "November" -msgstr "" +msgstr "Novembre" #: env/lib/python3.11/site-packages/django/utils/dates.py:36 msgid "December" -msgstr "" +msgstr "Desembre" #: env/lib/python3.11/site-packages/django/utils/dates.py:39 msgid "jan" -msgstr "" +msgstr "gen" #: env/lib/python3.11/site-packages/django/utils/dates.py:40 msgid "feb" -msgstr "" +msgstr "feb" #: env/lib/python3.11/site-packages/django/utils/dates.py:41 msgid "mar" -msgstr "" +msgstr "mar" #: env/lib/python3.11/site-packages/django/utils/dates.py:42 msgid "apr" -msgstr "" +msgstr "apr" #: env/lib/python3.11/site-packages/django/utils/dates.py:43 msgid "may" -msgstr "" +msgstr "maig" #: env/lib/python3.11/site-packages/django/utils/dates.py:44 msgid "jun" -msgstr "" +msgstr "jun" #: env/lib/python3.11/site-packages/django/utils/dates.py:45 msgid "jul" -msgstr "" +msgstr "jul" #: env/lib/python3.11/site-packages/django/utils/dates.py:46 msgid "aug" -msgstr "" +msgstr "ago" #: env/lib/python3.11/site-packages/django/utils/dates.py:47 msgid "sep" -msgstr "" +msgstr "set" #: env/lib/python3.11/site-packages/django/utils/dates.py:48 msgid "oct" -msgstr "" +msgstr "oct" #: env/lib/python3.11/site-packages/django/utils/dates.py:49 msgid "nov" -msgstr "" +msgstr "nov" #: env/lib/python3.11/site-packages/django/utils/dates.py:50 msgid "dec" -msgstr "" +msgstr "des" #: env/lib/python3.11/site-packages/django/utils/dates.py:53 msgctxt "abbrev. month" msgid "Jan." -msgstr "" +msgstr "Gen." #: env/lib/python3.11/site-packages/django/utils/dates.py:54 msgctxt "abbrev. month" msgid "Feb." -msgstr "" +msgstr "Febr." #: env/lib/python3.11/site-packages/django/utils/dates.py:55 msgctxt "abbrev. month" msgid "March" -msgstr "" +msgstr "Març" #: env/lib/python3.11/site-packages/django/utils/dates.py:56 msgctxt "abbrev. month" msgid "April" -msgstr "" +msgstr "Abril" #: env/lib/python3.11/site-packages/django/utils/dates.py:57 msgctxt "abbrev. month" msgid "May" -msgstr "" +msgstr "Maig" #: env/lib/python3.11/site-packages/django/utils/dates.py:58 msgctxt "abbrev. month" msgid "June" -msgstr "" +msgstr "Juny" #: env/lib/python3.11/site-packages/django/utils/dates.py:59 msgctxt "abbrev. month" msgid "July" -msgstr "" +msgstr "Juliol" #: env/lib/python3.11/site-packages/django/utils/dates.py:60 msgctxt "abbrev. month" msgid "Aug." -msgstr "" +msgstr "Ago." #: env/lib/python3.11/site-packages/django/utils/dates.py:61 msgctxt "abbrev. month" msgid "Sept." -msgstr "" +msgstr "Sept." #: env/lib/python3.11/site-packages/django/utils/dates.py:62 msgctxt "abbrev. month" msgid "Oct." -msgstr "" +msgstr "Oct." #: env/lib/python3.11/site-packages/django/utils/dates.py:63 msgctxt "abbrev. month" msgid "Nov." -msgstr "" +msgstr "Nov." #: env/lib/python3.11/site-packages/django/utils/dates.py:64 msgctxt "abbrev. month" msgid "Dec." -msgstr "" +msgstr "Des." #: env/lib/python3.11/site-packages/django/utils/dates.py:67 msgctxt "alt. month" msgid "January" -msgstr "" +msgstr "Gener" #: env/lib/python3.11/site-packages/django/utils/dates.py:68 msgctxt "alt. month" msgid "February" -msgstr "" +msgstr "Febrer" #: env/lib/python3.11/site-packages/django/utils/dates.py:69 msgctxt "alt. month" msgid "March" -msgstr "" +msgstr "Març" #: env/lib/python3.11/site-packages/django/utils/dates.py:70 msgctxt "alt. month" msgid "April" -msgstr "" +msgstr "Abril" #: env/lib/python3.11/site-packages/django/utils/dates.py:71 msgctxt "alt. month" msgid "May" -msgstr "" +msgstr "Maig" #: env/lib/python3.11/site-packages/django/utils/dates.py:72 msgctxt "alt. month" msgid "June" -msgstr "" +msgstr "Juny" #: env/lib/python3.11/site-packages/django/utils/dates.py:73 msgctxt "alt. month" msgid "July" -msgstr "" +msgstr "Juliol" #: env/lib/python3.11/site-packages/django/utils/dates.py:74 msgctxt "alt. month" msgid "August" -msgstr "" +msgstr "Agost" #: env/lib/python3.11/site-packages/django/utils/dates.py:75 msgctxt "alt. month" msgid "September" -msgstr "" +msgstr "Setembre" #: env/lib/python3.11/site-packages/django/utils/dates.py:76 msgctxt "alt. month" msgid "October" -msgstr "" +msgstr "Octubre" #: env/lib/python3.11/site-packages/django/utils/dates.py:77 msgctxt "alt. month" msgid "November" -msgstr "" +msgstr "Novembre" #: env/lib/python3.11/site-packages/django/utils/dates.py:78 msgctxt "alt. month" msgid "December" -msgstr "" +msgstr "Desembre" #: env/lib/python3.11/site-packages/django/utils/ipv6.py:8 msgid "This is not a valid IPv6 address." -msgstr "" +msgstr "Aquesta adreça IPv6 no és vàlida." #: env/lib/python3.11/site-packages/django/utils/text.py:78 #, python-format msgctxt "String to return when truncating text" msgid "%(truncated_text)s…" -msgstr "" +msgstr "%(truncated_text)s…" #: env/lib/python3.11/site-packages/django/utils/text.py:254 msgid "or" -msgstr "" +msgstr "o" #. Translators: This string is used as a separator between list elements #: env/lib/python3.11/site-packages/django/utils/text.py:273 #: env/lib/python3.11/site-packages/django/utils/timesince.py:135 msgid ", " -msgstr "" +msgstr ", " #: env/lib/python3.11/site-packages/django/utils/timesince.py:8 #, python-format msgid "%(num)d year" msgid_plural "%(num)d years" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d any" +msgstr[1] "%(num)d anys" #: env/lib/python3.11/site-packages/django/utils/timesince.py:9 #, python-format msgid "%(num)d month" msgid_plural "%(num)d months" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d mes" +msgstr[1] "%(num)d mesos" #: env/lib/python3.11/site-packages/django/utils/timesince.py:10 #, python-format msgid "%(num)d week" msgid_plural "%(num)d weeks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d setmana" +msgstr[1] "%(num)d setmanes" #: env/lib/python3.11/site-packages/django/utils/timesince.py:11 #, python-format msgid "%(num)d day" msgid_plural "%(num)d days" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d dia" +msgstr[1] "%(num)d dies" #: env/lib/python3.11/site-packages/django/utils/timesince.py:12 #, python-format msgid "%(num)d hour" msgid_plural "%(num)d hours" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d hora" +msgstr[1] "%(num)d hores" #: env/lib/python3.11/site-packages/django/utils/timesince.py:13 #, python-format msgid "%(num)d minute" msgid_plural "%(num)d minutes" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(num)d minut" +msgstr[1] "%(num)d minuts" #: env/lib/python3.11/site-packages/django/views/csrf.py:111 msgid "Forbidden" -msgstr "" +msgstr "Prohibit" #: env/lib/python3.11/site-packages/django/views/csrf.py:112 msgid "CSRF verification failed. Request aborted." -msgstr "" +msgstr "La verificació CSRF ha fallat. Sol·licitud avortada." #: env/lib/python3.11/site-packages/django/views/csrf.py:116 msgid "" @@ -1377,6 +1436,10 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" +"Esteu veient aquest missatge perquè aquest lloc HTTPS requereix que el " +"vostre navegador web enviï una “Referer header”, però no se n'ha enviat cap. " +"Aquesta capçalera és necessària per motius de seguretat, per garantir que el " +"vostre navegador no sigui segrestat per tercers." #: env/lib/python3.11/site-packages/django/views/csrf.py:122 msgid "" @@ -1384,6 +1447,9 @@ msgid "" "enable them, at least for this site, or for HTTPS connections, or for “same-" "origin” requests." msgstr "" +"Si heu configurat el vostre navegador per desactivar les capçaleres " +"\"Referer\", si us plau, torneu a habilitar-les, almenys per a aquest lloc, " +"o per a connexions HTTPS, o per a sol·licituds del \"mateix origen\"." #: env/lib/python3.11/site-packages/django/views/csrf.py:127 msgid "" @@ -1393,6 +1459,11 @@ msgid "" "If you’re concerned about privacy, use alternatives like for links to third-party sites." msgstr "" +"Si utilitzeu el etiqueteu o " +"inclogueu la capçalera \"Política de referència: no-referrer\", traieu-les. " +"La protecció CSRF requereix que la capçalera \"Referer\" faci una " +"comprovació estricta de referència. Si us preocupa la privadesa, utilitzeu " +"alternatives, com ara enllaços a llocs de tercers." #: env/lib/python3.11/site-packages/django/views/csrf.py:136 msgid "" @@ -1400,44 +1471,51 @@ msgid "" "submitting forms. This cookie is required for security reasons, to ensure " "that your browser is not being hijacked by third parties." msgstr "" +"Esteu veient aquest missatge perquè aquest lloc requereix una galeta CSRF a " +"l'hora d'enviar formularis. Aquesta galeta és necessària per motius de " +"seguretat, per garantir que el vostre navegador no sigui segrestat per " +"tercers." #: env/lib/python3.11/site-packages/django/views/csrf.py:142 msgid "" "If you have configured your browser to disable cookies, please re-enable " "them, at least for this site, or for “same-origin” requests." msgstr "" +"Si heu configurat el vostre navegador per desactivar les galetes, torneu-les " +"a habilitar, almenys per a aquest lloc, o per a sol·licituds del \"mateix " +"origen\"." #: env/lib/python3.11/site-packages/django/views/csrf.py:148 msgid "More information is available with DEBUG=True." -msgstr "" +msgstr "Hi ha més informació disponible amb DEBUG=True." #: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 msgid "No year specified" -msgstr "" +msgstr "Cap any especificat" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 #: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 #: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 msgid "Date out of range" -msgstr "" +msgstr "Data fora d'interval" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 msgid "No month specified" -msgstr "" +msgstr "Sense mes especificat" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 msgid "No day specified" -msgstr "" +msgstr "Sense dia especificat" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 msgid "No week specified" -msgstr "" +msgstr "Cap setmana especificada" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 #: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 #, python-format msgid "No %(verbose_name_plural)s available" -msgstr "" +msgstr "No hi ha %(verbose_name_plural)s disponible" #: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 #, python-format @@ -1445,49 +1523,52 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" +"Futur %(verbose_name_plural)s no disponible perquè %(class_name)s." +"allow_future és fals." #: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 #, python-format msgid "Invalid date string “%(datestr)s” given format “%(format)s”" msgstr "" +"La cadena de data \"%(datestr)s\" no és vàlida amb el format \"%(format)s\"" #: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 #, python-format msgid "No %(verbose_name)s found matching the query" -msgstr "" +msgstr "No s'ha trobat cap %(verbose_name)s que coincideixi amb la consulta" #: env/lib/python3.11/site-packages/django/views/generic/list.py:70 msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" +msgstr "La pàgina no és \"última\", ni es pot convertir a int." #: env/lib/python3.11/site-packages/django/views/generic/list.py:77 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" +msgstr "Pàgina no vàlida (%(page_number)s): %(message)s" #: env/lib/python3.11/site-packages/django/views/generic/list.py:169 #, python-format msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" +msgstr "Llista buida i \"%(class_name)s.allow_empty\" és Fals." #: env/lib/python3.11/site-packages/django/views/static.py:38 msgid "Directory indexes are not allowed here." -msgstr "" +msgstr "Els índexs de directoris no es permeten aquí." #: env/lib/python3.11/site-packages/django/views/static.py:40 #, python-format msgid "“%(path)s” does not exist" -msgstr "" +msgstr "“%(path)s” no existeix" #: env/lib/python3.11/site-packages/django/views/static.py:79 #, python-format msgid "Index of %(directory)s" -msgstr "" +msgstr "Índex de %(directory)s" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 msgid "The install worked successfully! Congratulations!" -msgstr "" +msgstr "La instal·lació ha funcionat correctament! Felicitats!" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 #, python-format @@ -1495,6 +1576,9 @@ msgid "" "View release notes for Django %(version)s" msgstr "" +"Consulta les notes de la versió de Django " +"%(version)s" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 #, python-format @@ -1504,275 +1588,279 @@ msgid "" "rel=\"noopener\">DEBUG=True is in your settings file and you have not " "configured any URLs." msgstr "" +"Esteu veient aquesta pàgina perquè DEBUG=True és al vostre fitxer de configuració i no heu " +"configurat cap URL." #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 msgid "Django Documentation" -msgstr "" +msgstr "Documentació de Django" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 msgid "Topics, references, & how-to’s" -msgstr "" +msgstr "Temes, referències, & com fer-ho" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 msgid "Tutorial: A Polling App" -msgstr "" +msgstr "Tutorial: una aplicació de votació" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 msgid "Get started with Django" -msgstr "" +msgstr "Comença amb Django" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 msgid "Django Community" -msgstr "" +msgstr "Comunitat Django" #: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 msgid "Connect, get help, or contribute" -msgstr "" +msgstr "Connecteu-vos, obteniu ajuda o contribuïu" #: env/lib/python3.11/site-packages/isort/main.py:158 msgid "show this help message and exit" -msgstr "" +msgstr "mostra aquest missatge d'ajuda i surt" #: idhub/admin/forms.py:112 msgid "The user does not exist!" -msgstr "" +msgstr "L'usuari no existeix!" #: idhub/admin/forms.py:154 idhub/admin/forms.py:168 idhub/admin/forms.py:177 msgid "This membership already exists!" -msgstr "" +msgstr "Aquesta subscripció ja existeix!" #: idhub/admin/forms.py:159 msgid "The end date is less than the start date" -msgstr "" +msgstr "La data de finalització és inferior a la data d'inici" #: idhub/admin/forms.py:206 msgid "Is not possible to have a duplicate role" -msgstr "" +msgstr "No és possible tenir una funció duplicada" #: idhub/admin/views.py:48 idhub/templates/idhub/base.html:69 #: idhub/templates/idhub/base_admin.html:69 idhub/user/views.py:32 msgid "Dashboard" -msgstr "" +msgstr "Panell de control" #: idhub/admin/views.py:49 idhub/user/views.py:33 msgid "Events" -msgstr "" +msgstr "Esdeveniments" #: idhub/admin/views.py:61 msgid "User management" -msgstr "" +msgstr "Configuració de gestió d'usuaris" #: idhub/admin/views.py:66 msgid "Access control management" -msgstr "" +msgstr "Gestió del control d'accés" #: idhub/admin/views.py:71 idhub/user/views.py:74 msgid "Credential management" -msgstr "" +msgstr "Gestió de credencials" #: idhub/admin/views.py:76 msgid "Template management" -msgstr "" +msgstr "Gestió de plantilles" #: idhub/admin/views.py:81 msgid "Data file management" -msgstr "" +msgstr "Gestió de fitxers de dades" #: idhub/admin/views.py:87 idhub/templates/idhub/base_admin.html:81 msgid "View users" -msgstr "" +msgstr "Veure usuaris" #: idhub/admin/views.py:100 msgid "User personal information" -msgstr "" +msgstr "Informació personal de l'usuari" #: idhub/admin/views.py:124 msgid "It is not possible deactivate your account!" -msgstr "" +msgstr "No és possible desactivar el teu compte!" #: idhub/admin/views.py:148 msgid "It is not possible delete your account!" -msgstr "" +msgstr "No és possible esborrar el teu compte!" #: idhub/admin/views.py:155 msgid "Update user" -msgstr "" +msgstr "Actualitza l'usuari" #: idhub/admin/views.py:184 msgid "The account was updated successfully" -msgstr "" +msgstr "El compte s'ha actualitzat correctament" #: idhub/admin/views.py:193 idhub/templates/idhub/base_admin.html:86 msgid "Add user" -msgstr "" +msgstr "Afegeix usuari" #: idhub/admin/views.py:207 msgid "The account was created successfully" -msgstr "" +msgstr "El compte s'ha creat correctament" #: idhub/admin/views.py:221 idhub/admin/views.py:265 msgid "Associate a membership to the user" -msgstr "" +msgstr "Associar una pertinença a l'usuari" #: idhub/admin/views.py:252 idhub/admin/views.py:342 msgid "Membership created successfully" -msgstr "" +msgstr "Membre creat amb èxit" #: idhub/admin/views.py:297 idhub/admin/views.py:377 msgid "Membership updated successfully" -msgstr "" +msgstr "Membre s'ha actualitzat correctament" #: idhub/admin/views.py:312 msgid "Is not possible delete your account!" -msgstr "" +msgstr "No és possible eliminar el teu compte!" #: idhub/admin/views.py:319 msgid "Add a user role to access a service" -msgstr "" +msgstr "Afegiu un rol d'usuari per accedir a un servei" #: idhub/admin/views.py:355 msgid "Modify a user role to access a service" -msgstr "" +msgstr "Modificar un rol d'usuari per accedir a un servei" #: idhub/admin/views.py:403 idhub/templates/idhub/base_admin.html:99 msgid "Manage roles" -msgstr "" +msgstr "Administrar rols" #: idhub/admin/views.py:415 msgid "Add role" -msgstr "" +msgstr "Afegeix un rol" #: idhub/admin/views.py:424 msgid "Role created successfully" -msgstr "" +msgstr "Rol creat correctament" #: idhub/admin/views.py:431 msgid "Edit role" -msgstr "" +msgstr "Edita rol" #: idhub/admin/views.py:446 msgid "Role updated successfully" -msgstr "" +msgstr "El rol s'ha actualitzat correctament" #: idhub/admin/views.py:459 msgid "Role deleted successfully" -msgstr "" +msgstr "El rol s'ha suprimit correctament" #: idhub/admin/views.py:466 idhub/templates/idhub/base_admin.html:104 msgid "Manage services" -msgstr "" +msgstr "Gestionar els serveis" #: idhub/admin/views.py:478 idhub/templates/idhub/admin/services.html:35 msgid "Add service" -msgstr "" +msgstr "Afegeix un servei" #: idhub/admin/views.py:492 msgid "Service created successfully" -msgstr "" +msgstr "Servei creat correctament" #: idhub/admin/views.py:499 msgid "Modify service" -msgstr "" +msgstr "Modificar servei" #: idhub/admin/views.py:519 msgid "Service updated successfully" -msgstr "" +msgstr "Servei actualitzat correctament" #: idhub/admin/views.py:532 msgid "Service deleted successfully" -msgstr "" +msgstr "El servei s'ha suprimit correctament" #: idhub/admin/views.py:539 idhub/templates/idhub/base_admin.html:117 msgid "View credentials" -msgstr "" +msgstr "Veure credencials" #: idhub/admin/views.py:552 msgid "Change credential status" -msgstr "" +msgstr "Canvia l'estat de la credencial" #: idhub/admin/views.py:594 msgid "Credential revoked successfully" -msgstr "" +msgstr "La credencial s'ha revocat correctament" #: idhub/admin/views.py:616 msgid "Credential deleted successfully" -msgstr "" +msgstr "La credencial s'ha suprimit correctament" #: idhub/admin/views.py:625 msgid "Manage identities (DID)" -msgstr "" +msgstr "Gestiona les identitats (DID)" #: idhub/admin/views.py:638 msgid "Add a new organizational identity (DID)" -msgstr "" +msgstr "Afegeix una identitat organitzativa nova (DID)" #: idhub/admin/views.py:650 idhub/user/views.py:194 msgid "DID created successfully" -msgstr "" +msgstr "DID creat correctament" #: idhub/admin/views.py:657 idhub/admin/views.py:676 msgid "Organization Identities (DID)" -msgstr "" +msgstr "Identitats de l'organització (DID)" #: idhub/admin/views.py:671 idhub/user/views.py:217 msgid "DID updated successfully" -msgstr "" +msgstr "DID s'ha actualitzat correctament" #: idhub/admin/views.py:687 idhub/user/views.py:233 msgid "DID delete successfully" -msgstr "" +msgstr "DID s’ha suprimit correctament" #: idhub/admin/views.py:693 idhub/templates/idhub/base_admin.html:132 msgid "View org. credentials" -msgstr "" +msgstr "Veure org. credencials" #: idhub/admin/views.py:700 idhub/templates/idhub/base_admin.html:137 msgid "Configure credential issuance" -msgstr "" +msgstr "Configura l'emissió de credencials" #: idhub/admin/views.py:707 msgid "View credential templates" -msgstr "" +msgstr "Veure plantilles de credencials" #: idhub/admin/views.py:741 msgid "Upload template" -msgstr "" +msgstr "Carrega la plantilla" #: idhub/admin/views.py:757 msgid "There are some errors in the file" -msgstr "" +msgstr "Hi ha alguns errors al fitxer" #: idhub/admin/views.py:771 msgid "This template already exists!" -msgstr "" +msgstr "Aquesta plantilla ja existeix!" #: idhub/admin/views.py:777 idhub/admin/views.py:822 msgid "This is not a valid schema!" -msgstr "" +msgstr "Aquest no és un esquema vàlid!" #: idhub/admin/views.py:786 msgid "Import template" -msgstr "" +msgstr "Importa plantilla" #: idhub/admin/views.py:814 msgid "The schema was added sucessfully" -msgstr "" +msgstr "L'esquema s'ha afegit correctament" #: idhub/admin/views.py:839 idhub/templates/idhub/admin/import.html:31 msgid "Import data" -msgstr "" +msgstr "Importa les dades" #: idhub/admin/views.py:852 idhub/admin/views.py:865 msgid "Import" -msgstr "" +msgstr "Importa" #: idhub/admin/views.py:878 msgid "The file was imported successfully!" -msgstr "" +msgstr "El fitxer s'ha importat correctament!" #: idhub/admin/views.py:883 msgid "Error importing the file!" -msgstr "" +msgstr "S'ha produït un error en importar el fitxer!" #: idhub/models.py:67 #, python-brace-format @@ -1780,60 +1868,69 @@ msgid "" "The user {username} was registered: name: {first_name}, last name: " "{last_name}" msgstr "" +"L'usuari {username} estava registrat: nom: {first_name}, cognom: {last_name}" #: idhub/models.py:79 #, python-brace-format msgid "" "Welcome. You has been registered: name: {first_name}, last name: {last_name}" -msgstr "" +msgstr "Benvingut. Has estat registrat: nom: {first_name}, cognom: {last_name}" #: idhub/models.py:92 #, python-brace-format msgid "" "The user '{username}' has request the update of the following information: " msgstr "" +"L'usuari '{username}' ha sol·licitat l'actualització de la informació " +"següent: " #: idhub/models.py:104 msgid "You have requested the update of the following information: " -msgstr "" +msgstr "Heu sol·licitat l'actualització de la informació següent: " #: idhub/models.py:141 #, python-brace-format msgid "The admin has deleted the user: username: {username}" -msgstr "" +msgstr "L'administrador ha suprimit l'usuari: nom d'usuari: {username}" #: idhub/models.py:151 #, python-brace-format msgid "New DID with DID-ID: '{did}' created by user '{username}'" -msgstr "" +msgstr "DID nou amb DID-ID: '{did}' creat per l'usuari '{username}'" #: idhub/models.py:162 #, python-brace-format msgid "New DID with label: '{label}' and DID-ID: '{did}' was created'" -msgstr "" +msgstr "S'ha creat un DID nou amb l'etiqueta: '{label}' i l'ID-DID: '{did}''" #: idhub/models.py:174 #, python-brace-format msgid "" "The DID with label '{label}' and DID-ID: '{did}' was deleted from your wallet" msgstr "" +"El DID amb l'etiqueta \"{label}\" i el DID-ID: \"{did}\" s'ha suprimit de la " +"vostra cartera" #: idhub/models.py:186 #, python-brace-format msgid "The credential of type '{type}' and ID: '{id}' was deleted" -msgstr "" +msgstr "S'ha suprimit la credencial del tipus \"{type}\" i ID: \"{id}\"" #: idhub/models.py:197 #, python-brace-format msgid "" "The credential of type '{type}' and ID: '{id}' was deleted from your wallet" msgstr "" +"La credencial del tipus \"{type}\" i ID: \"{id}\" s'han suprimit de la " +"vostra cartera" #: idhub/models.py:209 #, python-brace-format msgid "" "The credential of type '{type}' and ID: '{id}' was issued for user {username}" msgstr "" +"S'ha emès la credencial del tipus \"{type}\" i ID: \"{id}\" per a l'usuari " +"{username}" #: idhub/models.py:221 #, python-brace-format @@ -1841,115 +1938,123 @@ msgid "" "The credential of type '{type}' and ID: '{id}' was issued and stored in your " "wallet" msgstr "" +"La credencial del tipus \"{type}\" i ID: \"{id}\" s'ha emès i s'ha " +"emmagatzemat a la vostra cartera" #: idhub/models.py:263 #, python-brace-format msgid "The credential of type '{type}' was enabled for user {username}" msgstr "" +"La credencial del tipus \"{type}\" s'ha habilitat per a l'usuari {username}" #: idhub/models.py:274 #, python-brace-format msgid "You can request the '{type}' credential" -msgstr "" +msgstr "Podeu sol·licitar la credencial '{type}'" #: idhub/models.py:285 #, python-brace-format msgid "The credential of type '{type}' and ID: '{id}' was revoked for " -msgstr "" +msgstr "S'ha revocat la credencial del tipus \"{type}\" i ID: \"{id}\" per " #: idhub/models.py:296 #, python-brace-format msgid "The credential of type '{type}' and ID: '{id}' was revoked by admin" msgstr "" +"L'administrador ha revocat la credencial del tipus \"{type}\" i ID: \"{id}\"" #: idhub/models.py:308 msgid "A new role was created by admin" -msgstr "" +msgstr "Un nou rol va ser creat per l'administrador" #: idhub/models.py:316 msgid "The role was modified by admin" -msgstr "" +msgstr "El rol va ser modificat per l'administrador" #: idhub/models.py:324 msgid "The role was removed by admin" -msgstr "" +msgstr "L'administrador ha eliminat el rol" #: idhub/models.py:332 msgid "A new service was created by admin" -msgstr "" +msgstr "Un nou servei ha estat creat per l'administrador" #: idhub/models.py:340 msgid "The service was modified by admin" -msgstr "" +msgstr "El servei ha estat modificat per l'administrador" #: idhub/models.py:348 msgid "The service was removed by admin" -msgstr "" +msgstr "El servei va ser eliminat per l'administrador" #: idhub/models.py:356 #, python-brace-format msgid "" "New Organisational DID with label: '{label}' and DID-ID: '{did}' was created" msgstr "" +"S'ha creat un DID organitzatiu nou amb l'etiqueta: '{label}' i l'ID-DID: " +"'{did}'" #: idhub/models.py:367 #, python-brace-format msgid "" "Organisational DID with label: '{label}' and DID-ID: '{did}' was removed" msgstr "" +"S'ha eliminat el DID de l'organització amb l'etiqueta \"{label}\" i l'ID-" +"DID: \"{did}\"" #: idhub/models.py:457 msgid "Enabled" -msgstr "" +msgstr "Habilitat" #: idhub/models.py:458 idhub/templates/idhub/admin/credentials.html:17 #: idhub/templates/idhub/user/credentials.html:17 msgid "Issued" -msgstr "" +msgstr "Emès" #: idhub/models.py:459 msgid "Revoked" -msgstr "" +msgstr "Revocat" #: idhub/models.py:460 msgid "Expired" -msgstr "" +msgstr "Caducat" #: idhub/models.py:576 msgid "Beneficiary" -msgstr "" +msgstr "Beneficiari" #: idhub/models.py:577 msgid "Employee" -msgstr "" +msgstr "Empleat" #: idhub/models.py:578 msgid "Member" -msgstr "" +msgstr "Membre" #: idhub/models.py:580 msgid "Type of membership" -msgstr "" +msgstr "Tipus de pertinença" #: idhub/models.py:582 msgid "Start date" -msgstr "" +msgstr "Data d'inici" #: idhub/models.py:583 msgid "What date did the membership start?" -msgstr "" +msgstr "Quina data va començar la subscripció?" #: idhub/models.py:588 msgid "End date" -msgstr "" +msgstr "Data final" #: idhub/models.py:589 msgid "What date will the membership end?" -msgstr "" +msgstr "Quina data finalitzarà la subscripció?" #: idhub/models.py:605 msgid "name" -msgstr "" +msgstr "nom" #: idhub/models.py:606 idhub/models.py:614 #: idhub/templates/idhub/admin/dashboard.html:14 @@ -1961,31 +2066,31 @@ msgstr "" #: idhub/templates/idhub/user/dashboard.html:14 #: idhub/templates/idhub/user/roles.html:16 msgid "Description" -msgstr "" +msgstr "Descripción" #: idhub/models.py:613 idhub/templates/templates/musician/addresses.html:16 msgid "Domain" -msgstr "" +msgstr "Domini" #: idhub/models.py:622 msgid "None" -msgstr "" +msgstr "Sense" #: idhub/models.py:647 msgid "Url where to send the presentation" -msgstr "" +msgstr "URL on enviar la presentació" #: idhub/templates/auth/login.html:47 msgid "Log in" -msgstr "" +msgstr "Entra" #: idhub/templates/auth/login.html:52 msgid "Forgot your password? Click here to recover" -msgstr "" +msgstr "Has oblidat la teva contrasenya? Feu clic aquí per recuperar-la" #: idhub/templates/auth/login_base.html:91 msgid "Forgot your password?" -msgstr "" +msgstr "Heu oblidat la contrasenya?" #: idhub/templates/auth/login_base.html:97 #, python-format @@ -1993,76 +2098,91 @@ msgid "" "Send an email to %(support_email)s " "including your username and we will provide instructions." msgstr "" +"Envieu un correu electrònic a %(support_email)s amb el vostre nom d'usuari i us " +"proporcionarem instruccions." #: idhub/templates/auth/password_reset.html:8 msgid "Password reset" -msgstr "" +msgstr "Restableix contrasenya" #: idhub/templates/auth/password_reset.html:9 msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" +"Has oblidat la teva contrassenya? Introduïu la vostra adreça de correu " +"electrònic a continuació i us enviarem les instruccions per configurar-ne " +"una de nova." #: idhub/templates/auth/password_reset.html:21 msgid "Reset my password" -msgstr "" +msgstr "Reinicia la meva contrasenya" #: idhub/templates/auth/password_reset_complete.html:9 msgid "Password reset complete" -msgstr "" +msgstr "S'ha completat la reinicialització de la contrasenya" #: idhub/templates/auth/password_reset_complete.html:10 msgid "Your password has been set. You may go ahead and log in now." -msgstr "" +msgstr "S'ha establert la vostra contrasenya. Ara podeu entrar." #: idhub/templates/auth/password_reset_complete.html:11 idhub/views.py:9 msgid "Login" -msgstr "" +msgstr "Inicia la sessió" #: idhub/templates/auth/password_reset_confirm.html:9 msgid "Enter new password" -msgstr "" +msgstr "Introduïu la contrasenya nova" #: idhub/templates/auth/password_reset_confirm.html:10 msgid "" "Please enter your new password twice so we can verify you typed it in " "correctly." msgstr "" +"Introduïu la contrasenya nova dues vegades, de manera que ens assegurem que " +"l'heu escrita correctament." #: idhub/templates/auth/password_reset_confirm.html:21 msgid "Change my password" -msgstr "" +msgstr "Canvia la contrasenya" #: idhub/templates/auth/password_reset_confirm.html:29 msgid "Password reset unsuccessful" -msgstr "" +msgstr "No s'ha pogut recuperar la contrasenya" #: idhub/templates/auth/password_reset_confirm.html:30 msgid "" "The password reset link was invalid, possibly because it has already been " "used." msgstr "" +"L'enllaç de restabliment de la contrasenya no era vàlid, possiblement perquè " +"ja s'ha utilitzat." #: idhub/templates/auth/password_reset_confirm.html:31 msgid "Please request a new password reset." -msgstr "" +msgstr "Sol·liciteu un restabliment de la contrasenya nova." #: idhub/templates/auth/password_reset_done.html:7 msgid "Password reset sent" -msgstr "" +msgstr "S'ha enviat el restabliment de la contrasenya" #: idhub/templates/auth/password_reset_done.html:9 msgid "" "We've emailed you instructions for setting your password, if an account " "exists with the email you entered. You should receive them shortly." msgstr "" +"Us hem enviat per correu electrònic instruccions per configurar la vostra " +"contrasenya, si hi ha un compte amb el correu electrònic que heu introduït. " +"Les hauríeu de rebre en breu." #: idhub/templates/auth/password_reset_done.html:11 msgid "" "If you don't receive an email, please make sure you've entered the address " "you registered with, and check your spam folder." msgstr "" +"Si no rebeu cap correu electrònic, assegureu-vos que heu introduït l'adreça " +"amb la qual us heu registrat i comproveu la vostra carpeta de correu brossa." #: idhub/templates/auth/password_reset_email.html:3 #: idhub/templates/auth/password_reset_email.txt:2 @@ -2071,34 +2191,36 @@ msgid "" "You're receiving this email because you requested a password reset for your " "user account at %(site_name)s." msgstr "" +"Heu rebut aquest correu electrònic perquè heu sol·licitat un restabliment de " +"la contrasenya per al vostre compte d'usuari a %(site_name)s." #: idhub/templates/auth/password_reset_email.html:7 #: idhub/templates/auth/password_reset_email.txt:4 msgid "Please go to the following page and choose a new password:" -msgstr "" +msgstr "Aneu a la pàgina següent i trieu una nova contrasenya:" #: idhub/templates/auth/password_reset_email.html:19 #: idhub/templates/auth/password_reset_email.txt:8 msgid "Your username, in case you've forgotten:" -msgstr "" +msgstr "El vostre nom d'usuari, en cas que l'heu oblidat:" #: idhub/templates/auth/password_reset_email.html:23 #: idhub/templates/auth/password_reset_email.txt:10 #: idhub/templates/idhub/admin/registration/activate_user_email.html:24 #: idhub/templates/idhub/admin/registration/activate_user_email.txt:15 msgid "Thanks for using our site!" -msgstr "" +msgstr "Gràcies per utilitzar el nostre lloc!" #: idhub/templates/auth/password_reset_email.html:27 #: idhub/templates/auth/password_reset_email.txt:12 #, python-format msgid "The %(site_name)s team" -msgstr "" +msgstr "L'equip de %(site_name)s" #: idhub/templates/auth/password_reset_subject.txt:2 #, python-format msgid "Password reset on %(site_name)s" -msgstr "" +msgstr "Restabliment de la contrasenya a %(site_name)s" #: idhub/templates/idhub/admin/credentials.html:15 #: idhub/templates/idhub/user/credentials.html:15 @@ -2107,38 +2229,38 @@ msgstr "" #: idhub/templates/templates/musician/databases.html:17 #: idhub/templates/templates/musician/domain_detail.html:17 msgid "Type" -msgstr "" +msgstr "Tipus" #: idhub/templates/idhub/admin/credentials.html:16 #: idhub/templates/idhub/user/credentials.html:16 msgid "Details" -msgstr "" +msgstr "Detalls" #: idhub/templates/idhub/admin/credentials.html:18 #: idhub/templates/idhub/user/credentials.html:18 msgid "Status" -msgstr "" +msgstr "Status" #: idhub/templates/idhub/admin/credentials.html:19 msgid "User" -msgstr "" +msgstr "Usuaris" #: idhub/templates/idhub/admin/credentials.html:31 #: idhub/templates/idhub/admin/people.html:33 #: idhub/templates/idhub/admin/schemas.html:30 msgid "View" -msgstr "" +msgstr "Vista" #: idhub/templates/idhub/admin/dashboard.html:13 msgid "Event" -msgstr "" +msgstr "Esdeveniment" #: idhub/templates/idhub/admin/dashboard.html:15 #: idhub/templates/idhub/admin/dids.html:15 #: idhub/templates/idhub/user/dashboard.html:15 #: idhub/templates/idhub/user/dids.html:15 msgid "Date" -msgstr "" +msgstr "Data" #: idhub/templates/idhub/admin/did_register.html:29 #: idhub/templates/idhub/admin/import_add.html:27 @@ -2158,7 +2280,7 @@ msgstr "" #: idhub/templates/templates/musician/mailbox_check_delete.html:13 #: idhub/templates/templates/musician/mailbox_form.html:20 msgid "Cancel" -msgstr "" +msgstr "Cancel·lar" #: idhub/templates/idhub/admin/did_register.html:30 #: idhub/templates/idhub/admin/import_add.html:28 @@ -2174,12 +2296,12 @@ msgstr "" #: idhub/templates/templates/musician/mailbox_change_password.html:12 #: idhub/templates/templates/musician/mailbox_form.html:21 msgid "Save" -msgstr "" +msgstr "Desar" #: idhub/templates/idhub/admin/dids.html:16 #: idhub/templates/idhub/user/dids.html:16 msgid "Label" -msgstr "" +msgstr "Etiqueta" #: idhub/templates/idhub/admin/dids.html:28 #: idhub/templates/idhub/admin/roles.html:26 @@ -2188,77 +2310,77 @@ msgstr "" #: idhub/templates/idhub/admin/user_edit.html:86 #: idhub/templates/idhub/user/dids.html:28 msgid "Edit" -msgstr "" +msgstr "Editar" #: idhub/templates/idhub/admin/dids.html:29 #: idhub/templates/idhub/admin/schemas.html:31 #: idhub/templates/idhub/user/dids.html:29 msgid "Remove" -msgstr "" +msgstr "Esborra" #: idhub/templates/idhub/admin/dids.html:35 msgid "Add identity" -msgstr "" +msgstr "Afegeix identitat" #: idhub/templates/idhub/admin/dids.html:46 #: idhub/templates/idhub/user/dids.html:46 msgid "Delete DID" -msgstr "" +msgstr "Suprimeix DID" #: idhub/templates/idhub/admin/dids.html:50 #: idhub/templates/idhub/user/dids.html:50 msgid "Are you sure that you want delete this DID?" -msgstr "" +msgstr "Esteu segur que voleu suprimir aquest DID?" #: idhub/templates/idhub/admin/import.html:15 #: idhub/templates/idhub/admin/schemas.html:15 msgid "Created" -msgstr "" +msgstr "Creat" #: idhub/templates/idhub/admin/import.html:17 msgid "Success" -msgstr "" +msgstr "Ha tingut èxit" #: idhub/templates/idhub/admin/issue_credentials.html:14 #: idhub/templates/idhub/admin/issue_credentials.html:72 msgid "Revoke" -msgstr "" +msgstr "Revoca" #: idhub/templates/idhub/admin/issue_credentials.html:54 #: idhub/templates/idhub/user/credential.html:41 msgid "View in JSON format" -msgstr "" +msgstr "Visualitza en format JSON" #: idhub/templates/idhub/admin/issue_credentials.html:64 msgid "Revoke credential" -msgstr "" +msgstr "Revoca la credencial" #: idhub/templates/idhub/admin/issue_credentials.html:68 msgid "Are you sure that you want revoke this credential?" -msgstr "" +msgstr "Esteu segur que voleu revocar aquesta credencial?" #: idhub/templates/idhub/admin/issue_credentials.html:82 msgid "Delete credential" -msgstr "" +msgstr "Suprimeix la credencial" #: idhub/templates/idhub/admin/issue_credentials.html:86 msgid "Are you sure that you want delete this Credential?" -msgstr "" +msgstr "Esteu segur que voleu suprimir aquesta credencial?" #: idhub/templates/idhub/admin/people.html:13 msgid "Last name" -msgstr "" +msgstr "Cognoms" #: idhub/templates/idhub/admin/people.html:14 msgid "First name" -msgstr "" +msgstr "Nom" #: idhub/templates/idhub/admin/people.html:16 #: idhub/templates/idhub/admin/user.html:62 #: idhub/templates/idhub/admin/user_edit.html:41 #: idhub/templates/idhub/user/profile.html:43 msgid "Membership" -msgstr "" +msgstr "Afiliació" #: idhub/templates/idhub/admin/people.html:17 #: idhub/templates/idhub/admin/roles.html:15 @@ -2267,12 +2389,12 @@ msgstr "" #: idhub/templates/idhub/admin/user_edit.html:73 #: idhub/templates/idhub/user/roles.html:15 msgid "Role" -msgstr "" +msgstr "Càrrec" #: idhub/templates/idhub/admin/registration/activate_user_email.html:2 #: idhub/templates/idhub/admin/registration/activate_user_subject.txt:2 msgid "IdHub" -msgstr "" +msgstr "IdHub" #: idhub/templates/idhub/admin/registration/activate_user_email.html:4 #: idhub/templates/idhub/admin/registration/activate_user_email.txt:5 @@ -2281,529 +2403,536 @@ msgid "" "You're receiving this email because your user account at %(site)s has been " "activated." msgstr "" +"Heu rebut aquest correu electrònic perquè el vostre compte d'usuari a " +"%(site)s s'ha activat." #: idhub/templates/idhub/admin/registration/activate_user_email.html:8 #: idhub/templates/idhub/admin/registration/activate_user_email.txt:7 msgid "Your username is:" -msgstr "" +msgstr "El teu nom d'usuari és:" #: idhub/templates/idhub/admin/registration/activate_user_email.html:12 #: idhub/templates/idhub/admin/registration/activate_user_email.txt:9 msgid "Please go to the following page and choose a password:" -msgstr "" +msgstr "Aneu a la pàgina següent i trieu una contrasenya:" #: idhub/templates/idhub/admin/registration/activate_user_email.html:28 #: idhub/templates/idhub/admin/registration/activate_user_email.txt:17 #, python-format msgid "The %(site)s team" -msgstr "" +msgstr "L'equip de %(site)s" #: idhub/templates/idhub/admin/registration/activate_user_email.txt:3 msgid "Idhub" -msgstr "" +msgstr "Idhub" #: idhub/templates/idhub/admin/registration/activate_user_subject.txt:3 #, python-format msgid "User activation on %(site)s" -msgstr "" +msgstr "Activació d'usuari a %(site)s" #: idhub/templates/idhub/admin/roles.html:33 #: idhub/templates/idhub/admin/user_edit.html:93 msgid "Add Role" -msgstr "" +msgstr "Afegeix un rol" #: idhub/templates/idhub/admin/schemas.html:16 msgid "Template file" -msgstr "" +msgstr "Fitxer de plantilla" #: idhub/templates/idhub/admin/schemas.html:17 #: idhub/templates/templates/musician/mailboxes.html:14 msgid "Name" -msgstr "" +msgstr "Name" #: idhub/templates/idhub/admin/schemas.html:37 #: idhub/templates/idhub/admin/schemas_import.html:29 msgid "Add template" -msgstr "" +msgstr "Afegeix plantilla" #: idhub/templates/idhub/admin/schemas.html:48 msgid "Delete template" -msgstr "" +msgstr "Suprimeix la plantilla" #: idhub/templates/idhub/admin/schemas.html:52 msgid "Are you sure that you want delete this template?" -msgstr "" +msgstr "Segur que voleu suprimir aquesta plantilla?" #: idhub/templates/idhub/admin/schemas_import.html:15 msgid "Available templates" -msgstr "" +msgstr "Plantilles disponibles" #: idhub/templates/idhub/admin/schemas_import.html:23 msgid "Add" -msgstr "" +msgstr "Afegeix" #: idhub/templates/idhub/admin/services.html:15 #: idhub/templates/idhub/admin/user.html:89 #: idhub/templates/idhub/admin/user_edit.html:75 #: idhub/templates/idhub/user/roles.html:17 msgid "Service" -msgstr "" +msgstr "Servei" #: idhub/templates/idhub/admin/user.html:13 msgid "Modify" -msgstr "" +msgstr "Modifica" #: idhub/templates/idhub/admin/user.html:14 msgid "Deactivate" -msgstr "" +msgstr "Desactiva" #: idhub/templates/idhub/admin/user.html:14 msgid "Activate" -msgstr "" +msgstr "Activa" #: idhub/templates/idhub/admin/user.html:63 #: idhub/templates/idhub/admin/user_edit.html:42 #: idhub/templates/idhub/user/profile.html:44 msgid "From" -msgstr "" +msgstr "Remitent" #: idhub/templates/idhub/admin/user.html:64 #: idhub/templates/idhub/admin/user_edit.html:43 #: idhub/templates/idhub/user/profile.html:45 msgid "To" -msgstr "" +msgstr "Destinatari" #: idhub/templates/idhub/admin/user.html:112 msgid "Delete user" -msgstr "" +msgstr "Suprimeix l'usuari" #: idhub/templates/idhub/admin/user.html:116 msgid "Are you sure that you want delete this user?" -msgstr "" +msgstr "Esteu segur que voleu suprimir aquest usuari?" #: idhub/templates/idhub/admin/user_edit.html:61 msgid "Add membership" -msgstr "" +msgstr "Afegeix membres" #: idhub/templates/idhub/base.html:76 msgid "My information" -msgstr "" +msgstr "La meva informació" #: idhub/templates/idhub/base.html:81 msgid "My personal information" -msgstr "" +msgstr "La meva informació personal" #: idhub/templates/idhub/base.html:86 idhub/user/views.py:62 msgid "My roles" -msgstr "" +msgstr "Els meus rols" #: idhub/templates/idhub/base.html:91 idhub/user/views.py:68 msgid "GDPR info" -msgstr "" +msgstr "Informació GDPR" #: idhub/templates/idhub/base.html:99 idhub/user/views.py:26 msgid "My wallet" -msgstr "" +msgstr "La meva cartera" #: idhub/templates/idhub/base.html:104 idhub/user/views.py:169 #: idhub/user/views.py:203 idhub/user/views.py:222 msgid "Identities (DIDs)" -msgstr "" +msgstr "Identitats (DID)" #: idhub/templates/idhub/base.html:109 msgid "My credentials" -msgstr "" +msgstr "Les meves credencials" #: idhub/templates/idhub/base.html:114 msgid "Request a credential" -msgstr "" +msgstr "Sol·licitar una credencial" #: idhub/templates/idhub/base.html:119 msgid "Present a credential" -msgstr "" +msgstr "Presentar una credencial" #: idhub/templates/idhub/base_admin.html:76 msgid "Users" -msgstr "" +msgstr "Usuaris" #: idhub/templates/idhub/base_admin.html:94 msgid "Roles" -msgstr "" +msgstr "Rols" #: idhub/templates/idhub/base_admin.html:112 #: idhub/templates/idhub/user/profile.html:46 msgid "Credentials" -msgstr "" +msgstr "Credencials" #: idhub/templates/idhub/base_admin.html:122 msgid "Organization's wallet" -msgstr "" +msgstr "Cartera de l'organització" #: idhub/templates/idhub/base_admin.html:127 msgid "Manage Identities (DIDs)" -msgstr "" +msgstr "Gestionar identitats (DID)" #: idhub/templates/idhub/base_admin.html:147 msgid "Templates" -msgstr "" +msgstr "Plantilles" #: idhub/templates/idhub/base_admin.html:153 msgid "Data" -msgstr "" +msgstr "Dades" #: idhub/templates/idhub/user/credentials_presentation.html:30 msgid "Send" -msgstr "" +msgstr "Enviar" #: idhub/templates/idhub/user/credentials_request.html:30 msgid "Request" -msgstr "" +msgstr "Sol·licitud" #: idhub/templates/idhub/user/dids.html:35 msgid "Add Identity" -msgstr "" +msgstr "Afegeix identitat" #: idhub/templates/idhub/user/profile.html:13 msgid "ARCO Forms" -msgstr "" +msgstr "Formularis ARCO" #: idhub/templates/idhub/user/profile.html:14 msgid "Notice of Privacy" -msgstr "" +msgstr "Avís de Privacitat" #: idhub/templates/templates/musician/address_check_delete.html:7 #, python-format msgid "Are you sure that you want remove the address: \"%(address_name)s\"?" -msgstr "" +msgstr "Esteu segur que voleu eliminar l'adreça: \"%(address_name)s\"?" #: idhub/templates/templates/musician/address_check_delete.html:8 #: idhub/templates/templates/musician/mailbox_check_delete.html:11 msgid "WARNING: This action cannot be undone." -msgstr "" +msgstr "ADVERTIMENT: Aquesta acció no es pot desfer." #: idhub/templates/templates/musician/addresses.html:15 msgid "Email" -msgstr "" +msgstr "Adreça electrònica" #: idhub/templates/templates/musician/addresses.html:17 #: idhub/templates/templates/musician/mail_base.html:22 msgid "Mailboxes" -msgstr "" +msgstr "Bústies" #: idhub/templates/templates/musician/addresses.html:18 msgid "Forward" -msgstr "" +msgstr "Reenvia" #: idhub/templates/templates/musician/addresses.html:38 msgid "New mail address" -msgstr "" +msgstr "Nova adreça de correu" #: idhub/templates/templates/musician/billing.html:6 msgid "Billing" -msgstr "" +msgstr "Facturació" #: idhub/templates/templates/musician/billing.html:7 msgid "Billing page description." -msgstr "" +msgstr "Descripció de la pàgina de facturació." #: idhub/templates/templates/musician/billing.html:19 msgid "Number" -msgstr "" +msgstr "Número" #: idhub/templates/templates/musician/billing.html:20 msgid "Bill date" -msgstr "" +msgstr "Data de facturació" #: idhub/templates/templates/musician/billing.html:22 msgid "Total" -msgstr "" +msgstr "Total" #: idhub/templates/templates/musician/billing.html:23 msgid "Download PDF" -msgstr "" +msgstr "Descarregar PDF" #: idhub/templates/templates/musician/components/table_paginator.html:15 msgid "Previous" -msgstr "" +msgstr "Anterior" #: idhub/templates/templates/musician/components/table_paginator.html:29 msgid "Next" -msgstr "" +msgstr "Següent" #: idhub/templates/templates/musician/dashboard.html:6 msgid "Welcome back" -msgstr "" +msgstr "Benvingut de nou" #: idhub/templates/templates/musician/dashboard.html:8 #, python-format msgid "Last time you logged in was: %(last_login)s" -msgstr "" +msgstr "La darrera vegada que vas iniciar sessió va ser: %(last_login)s" #: idhub/templates/templates/musician/dashboard.html:10 msgid "It's the first time you log into the system, welcome on board!" -msgstr "" +msgstr "És la primera vegada que inicieu sessió al sistema, benvingut a bord!" #: idhub/templates/templates/musician/dashboard.html:29 msgid "Notifications" -msgstr "" +msgstr "Notificacions" #: idhub/templates/templates/musician/dashboard.html:33 msgid "There is no notifications at this time." -msgstr "" +msgstr "No hi ha notificacions en aquest moment." #: idhub/templates/templates/musician/dashboard.html:40 msgid "Your domains and websites" -msgstr "" +msgstr "Els vostres dominis i llocs web" #: idhub/templates/templates/musician/dashboard.html:41 msgid "Dashboard page description." -msgstr "" +msgstr "Descripció de la pàgina del tauler." #: idhub/templates/templates/musician/dashboard.html:56 msgid "view configuration" -msgstr "" +msgstr "veure la configuració" #: idhub/templates/templates/musician/dashboard.html:63 msgid "Expiration date" -msgstr "" +msgstr "Data de caducitat" #: idhub/templates/templates/musician/dashboard.html:70 msgid "Mail" -msgstr "" +msgstr "Correu" #: idhub/templates/templates/musician/dashboard.html:73 msgid "mail addresses created" -msgstr "" +msgstr "adreces de correu creades" #: idhub/templates/templates/musician/dashboard.html:78 msgid "Mail list" -msgstr "" +msgstr "Llista de correu" #: idhub/templates/templates/musician/dashboard.html:83 msgid "Software as a Service" -msgstr "" +msgstr "Programari com a servei" #: idhub/templates/templates/musician/dashboard.html:85 msgid "Nothing installed" -msgstr "" +msgstr "No hi ha res instal·lat" #: idhub/templates/templates/musician/dashboard.html:90 msgid "Disk usage" -msgstr "" +msgstr "Ús del disc" #: idhub/templates/templates/musician/dashboard.html:107 msgid "Configuration details" -msgstr "" +msgstr "Detalls de configuració" #: idhub/templates/templates/musician/dashboard.html:114 msgid "FTP access:" -msgstr "" +msgstr "Accés FTP:" #. Translators: domain configuration detail modal #: idhub/templates/templates/musician/dashboard.html:116 msgid "Contact with the support team to get details concerning FTP access." msgstr "" +"Contacta amb l'equip d'assistència per obtenir detalls sobre l'accés FTP." #: idhub/templates/templates/musician/dashboard.html:125 msgid "No website configured." -msgstr "" +msgstr "No s'ha configurat cap lloc web." #: idhub/templates/templates/musician/dashboard.html:127 msgid "Root directory:" -msgstr "" +msgstr "Directori arrel:" #: idhub/templates/templates/musician/dashboard.html:128 msgid "Type:" -msgstr "" +msgstr "Tipus:" #: idhub/templates/templates/musician/dashboard.html:133 msgid "View DNS records" -msgstr "" +msgstr "Veure registres DNS" #: idhub/templates/templates/musician/databases.html:21 msgid "associated to" -msgstr "" +msgstr "associada a" #: idhub/templates/templates/musician/databases.html:34 msgid "No users for this database." -msgstr "" +msgstr "No hi ha usuaris per a aquesta base de dades." #: idhub/templates/templates/musician/databases.html:45 msgid "Open database manager" -msgstr "" +msgstr "Obriu el gestor de bases de dades" #. Translators: database page when there isn't any database. #. Translators: saas page when there isn't any saas. #: idhub/templates/templates/musician/databases.html:58 #: idhub/templates/templates/musician/saas.html:49 msgid "Ooops! Looks like there is nothing here!" -msgstr "" +msgstr "Vaja! Sembla que aquí no hi ha res!" #: idhub/templates/templates/musician/domain_detail.html:5 msgid "Go back" -msgstr "" +msgstr "Torna enrere" #: idhub/templates/templates/musician/domain_detail.html:7 msgid "DNS settings for" -msgstr "" +msgstr "Configuració de DNS per a" #: idhub/templates/templates/musician/domain_detail.html:8 msgid "DNS settings page description." -msgstr "" +msgstr "Descripció de la pàgina de configuració de DNS." #: idhub/templates/templates/musician/domain_detail.html:18 msgid "Value" -msgstr "" +msgstr "Valor" #: idhub/templates/templates/musician/mail_base.html:6 #: idhub/templates/templates/musician/mailinglists.html:6 msgid "Go to global" -msgstr "" +msgstr "Anar a global" #: idhub/templates/templates/musician/mail_base.html:10 #: idhub/templates/templates/musician/mailinglists.html:9 msgid "for" -msgstr "" +msgstr "per" #: idhub/templates/templates/musician/mail_base.html:18 #: idhub/templates/templates/musician/mailboxes.html:16 msgid "Addresses" -msgstr "" +msgstr "Adreces" #: idhub/templates/templates/musician/mailbox_change_password.html:5 #: idhub/templates/templates/musician/mailbox_form.html:24 msgid "Change password" -msgstr "" +msgstr "Canviar la contrasenya" #: idhub/templates/templates/musician/mailbox_check_delete.html:7 #, python-format msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?" -msgstr "" +msgstr "Esteu segur que voleu eliminar la bústia de correu: \"%(name)s\"?" #: idhub/templates/templates/musician/mailbox_check_delete.html:9 msgid "" "All mailbox's messages will be deleted and cannot be recovered." msgstr "" +"Tots els missatges de la bústia se suprimiran i no es podran " +"recuperar ." #: idhub/templates/templates/musician/mailbox_form.html:9 msgid "Warning!" -msgstr "" +msgstr "Atenció!" #: idhub/templates/templates/musician/mailbox_form.html:9 msgid "" "You have reached the limit of mailboxes of your subscription so " "extra fees may apply." msgstr "" +"Heu arribat al límit de bústies de la vostra subscripció, de manera que es " +"poden aplicar càrrecs addicionals." #: idhub/templates/templates/musician/mailbox_form.html:10 msgid "Close" -msgstr "" +msgstr "Tanca" #: idhub/templates/templates/musician/mailboxes.html:15 msgid "Filtering" -msgstr "" +msgstr "Filtratge" #: idhub/templates/templates/musician/mailboxes.html:27 msgid "Update password" -msgstr "" +msgstr "Actualitzar la contrasenya" #: idhub/templates/templates/musician/mailboxes.html:43 msgid "New mailbox" -msgstr "" +msgstr "Nova bústia" #: idhub/templates/templates/musician/mailinglists.html:34 msgid "Active" -msgstr "" +msgstr "Actiu" #: idhub/templates/templates/musician/mailinglists.html:36 msgid "Inactive" -msgstr "" +msgstr "Inactiu" #: idhub/templates/templates/musician/profile.html:6 msgid "Profile" -msgstr "" +msgstr "Perfil" #: idhub/templates/templates/musician/profile.html:7 msgid "Little description on profile page." -msgstr "" +msgstr "Petita descripció a la pàgina de perfil." #: idhub/templates/templates/musician/profile.html:11 msgid "User information" -msgstr "" +msgstr "Informació d'usuari" #: idhub/templates/templates/musician/profile.html:21 msgid "Preferred language:" -msgstr "" +msgstr "Idioma preferit:" #: idhub/templates/templates/musician/profile.html:35 msgid "Billing information" -msgstr "" +msgstr "Dades de facturació" #: idhub/templates/templates/musician/profile.html:49 msgid "payment method:" -msgstr "" +msgstr "mètode de pagament:" #: idhub/templates/templates/musician/profile.html:60 msgid "Check your last bills" -msgstr "" +msgstr "Comproveu les vostres últimes factures" #: idhub/templates/templates/musician/saas.html:18 msgid "Installed on" -msgstr "" +msgstr "Instal·lat a" #: idhub/templates/templates/musician/saas.html:29 msgid "Service info" -msgstr "" +msgstr "Informació del servei" #: idhub/templates/templates/musician/saas.html:30 msgid "active" -msgstr "" +msgstr "actiu" #: idhub/templates/templates/musician/saas.html:37 msgid "Open service admin panel" -msgstr "" +msgstr "Obriu el tauler d'administració del servei" #: idhub/user/views.py:21 msgid "My profile" -msgstr "" +msgstr "El meu perfil" #: idhub/user/views.py:40 msgid "My personal data" -msgstr "" +msgstr "Les meves dades personals" #: idhub/user/views.py:87 msgid "Credential" -msgstr "" +msgstr "Credencial" #: idhub/user/views.py:123 msgid "Credential request" -msgstr "" +msgstr "Sol·licitar una credencial" #: idhub/user/views.py:136 msgid "The credential was issued successfully!" -msgstr "" +msgstr "La credencial s'ha emès correctament!" #: idhub/user/views.py:140 msgid "The credential does not exist!" -msgstr "" +msgstr "La credencial no existeix!" #: idhub/user/views.py:146 msgid "Credential presentation" -msgstr "" +msgstr "Presentació de credencials" #: idhub/user/views.py:161 msgid "The credential was presented successfully!" -msgstr "" +msgstr "La credencial s'ha presentat correctament!" #: idhub/user/views.py:163 msgid "Error sending credential!" -msgstr "" +msgstr "S'ha produït un error en enviar la credencial!" #: idhub/user/views.py:182 msgid "Add a new Identity (DID)" -msgstr "" +msgstr "Afegeix una nova identitat (DID)" #: idhub_auth/forms.py:19 idhub_auth/forms.py:27 msgid "The string must contain only characters and spaces" -msgstr "" +msgstr "La cadena ha de contenir només caràcters i espais" diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000..71cbdf3 Binary files /dev/null and b/locale/es/LC_MESSAGES/django.mo differ diff --git a/oidc4vp/__init__.py b/oidc4vp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oidc4vp/admin.py b/oidc4vp/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/oidc4vp/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/oidc4vp/apps.py b/oidc4vp/apps.py new file mode 100644 index 0000000..1e00f2b --- /dev/null +++ b/oidc4vp/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class Oidc4VpConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'oidc4vp' diff --git a/oidc4vp/forms.py b/oidc4vp/forms.py new file mode 100644 index 0000000..b3c4c9e --- /dev/null +++ b/oidc4vp/forms.py @@ -0,0 +1,81 @@ +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 + + +class AuthorizeForm(forms.Form): + + def __init__(self, *args, **kwargs): + self.data = kwargs.get('data', {}).copy() + self.user = kwargs.pop('user', None) + self.org = kwargs.pop('org', None) + self.code = kwargs.pop('code', None) + self.presentation_definition = kwargs.pop('presentation_definition', []) + + reg = r'({})'.format('|'.join(self.presentation_definition)) + + self.all_credentials = self.user.vcredentials.filter( + schema__type__iregex=reg, + ) + self.credentials = self.all_credentials.filter( + status=VerificableCredential.Status.ISSUED.value + ) + super().__init__(*args, **kwargs) + for vp in self.presentation_definition: + vp = vp.lower() + choices = [ + (str(x.id), x.schema.type.lower()) for x in self.credentials.filter( + schema__type__iexact=vp) + ] + self.fields[vp.lower()] = forms.ChoiceField( + widget=forms.RadioSelect, + choices=choices + ) + def clean(self): + data = super().clean() + self.list_credentials = [] + for c in self.credentials: + if str(c.id) == data.get(c.schema.type.lower()): + if c.status is not c.Status.ISSUED.value or not c.data: + txt = _('There are some problems with this credentials') + raise ValidationError(txt) + + self.list_credentials.append(c) + + if not self.code: + txt = _("There isn't code in request") + raise ValidationError(txt) + + return data + + def save(self, commit=True): + if not self.list_credentials: + return + + self.get_verificable_presentation() + + if commit: + return self.org.send(self.vp, self.code) + + return + + def get_verificable_presentation(self): + did = self.list_credentials[0].subject_did + vp_template = get_template('credentials/verifiable_presentation.json') + vc_list = json.dumps([json.loads(x.data) for x in self.list_credentials]) + + context = { + "holder_did": did.did, + "verifiable_credential_list": vc_list + } + unsigned_vp = vp_template.render(context) + self.vp = create_verifiable_presentation(did.key_material, unsigned_vp) diff --git a/oidc4vp/migrations/0001_initial.py b/oidc4vp/migrations/0001_initial.py new file mode 100644 index 0000000..700c4e8 --- /dev/null +++ b/oidc4vp/migrations/0001_initial.py @@ -0,0 +1,137 @@ +# Generated by Django 4.2.5 on 2023-12-11 08:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import oidc4vp.models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Authorization', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'code', + models.CharField(default=oidc4vp.models.set_code, max_length=24), + ), + ('code_used', models.BooleanField()), + ('created', models.DateTimeField(auto_now=True)), + ('presentation_definition', models.CharField(max_length=250)), + ], + ), + migrations.CreateModel( + name='Organization', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('name', models.CharField(max_length=250)), + ( + 'client_id', + models.CharField( + default=oidc4vp.models.set_client_id, max_length=24, unique=True + ), + ), + ( + 'client_secret', + models.CharField( + default=oidc4vp.models.set_client_secret, max_length=48 + ), + ), + ('my_client_id', models.CharField(max_length=24)), + ('my_client_secret', models.CharField(max_length=48)), + ( + 'response_uri', + models.URLField( + help_text='Url where to send the verificable presentation', + max_length=250, + ), + ), + ], + ), + migrations.CreateModel( + name='OAuth2VPToken', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('created', models.DateTimeField(auto_now=True)), + ('result_verify', models.CharField(max_length=255)), + ('vp_token', models.TextField()), + ( + 'authorization', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='oauth2vptoken', + to='oidc4vp.authorization', + ), + ), + ( + 'organization', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='vp_tokens', + to='oidc4vp.organization', + ), + ), + ( + 'user', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='vp_tokens', + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddField( + model_name='authorization', + name='organization', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='authorizations', + to='oidc4vp.organization', + ), + ), + migrations.AddField( + model_name='authorization', + name='user', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/oidc4vp/migrations/__init__.py b/oidc4vp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oidc4vp/models.py b/oidc4vp/models.py new file mode 100644 index 0000000..66ceff1 --- /dev/null +++ b/oidc4vp/models.py @@ -0,0 +1,218 @@ +import json +import requests +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 + + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + +def gen_salt(length: int) -> str: + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be positive") + + return "".join(secrets.choice(SALT_CHARS) for _ in range(length)) + + +def set_client_id(): + return gen_salt(24) + + +def set_client_secret(): + return gen_salt(48) + + +def set_code(): + return gen_salt(24) + + +class Organization(models.Model): + """ + This class represent a member of one net trust or federated host. + Client_id and client_secret are the credentials of this organization + get a connection to my. (receive a request) + My_client_id and my_client_secret are my credentials than to use if I + want to connect to this organization. (send a request) + For use the packages requests we need use my_client_id + For use in the get or post method of a View, then we need use client_id + and secret_id + """ + name = models.CharField(max_length=250) + client_id = models.CharField( + max_length=24, + default=set_client_id, + unique=True + ) + client_secret = models.CharField( + max_length=48, + default=set_client_secret + ) + my_client_id = models.CharField( + max_length=24, + ) + my_client_secret = models.CharField( + max_length=48, + ) + response_uri = models.URLField( + help_text=_("Url where to send the verificable presentation"), + max_length=250 + ) + + def send(self, vp, code): + """ + Send the verificable presentation to Verifier + """ + url = "{url}/verify".format( + url=self.response_uri.strip("/"), + ) + auth = (self.my_client_id, self.my_client_secret) + data = {"vp_token": vp} + if code: + data["code"] = code + + return requests.post(url, data=data, auth=auth) + + def demand_authorization(self): + """ + Send the a request for start a process of Verifier + """ + url = "{url}/verify?demand_uri={redirect_uri}".format( + url=self.response_uri.strip("/"), + redirect_uri=settings.RESPONSE_URI + ) + auth = (self.my_client_id, self.my_client_secret) + return requests.get(url, auth=auth) + + def __str__(self): + return self.name + + +################### +# Verifier clases # +################### + + +class Authorization(models.Model): + """ + This class represent a query through browser the client to the wallet. + The Verifier need to do a redirection to the user to Wallet. + The code we use as a soft foreing key between Authorization and OAuth2VPToken. + """ + code = models.CharField(max_length=24, default=set_code) + code_used = models.BooleanField(default=False) + created = models.DateTimeField(auto_now=True) + presentation_definition = models.CharField(max_length=250) + organization = models.ForeignKey( + Organization, + on_delete=models.CASCADE, + related_name='authorizations', + null=True, + ) + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + null=True, + ) + + def authorize(self, path=None): + data = { + "response_type": "vp_token", + "response_mode": "direct_post", + "client_id": self.organization.my_client_id, + "presentation_definition": self.presentation_definition, + "code": self.code, + "nonce": gen_salt(5), + } + query_dict = QueryDict('', mutable=True) + query_dict.update(data) + + response_uri = self.organization.response_uri.strip("/") + if path: + response_uri = "{}/{}".format(response_uri, path.strip("/")) + + url = '{response_uri}/authorize?{params}'.format( + response_uri=response_uri, + params=query_dict.urlencode() + ) + return url + + +class OAuth2VPToken(models.Model): + """ + This class represent the response of Wallet to Verifier + and the result of verify. + """ + created = models.DateTimeField(auto_now=True) + result_verify = models.CharField(max_length=255) + vp_token = models.TextField() + organization = models.ForeignKey( + Organization, + on_delete=models.CASCADE, + related_name='vp_tokens', + null=True, + ) + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name='vp_tokens', + null=True, + ) + authorization = models.ForeignKey( + Authorization, + on_delete=models.SET_NULL, + related_name='vp_tokens', + null=True, + ) + + def __init__(self, *args, **kwargs): + code = kwargs.pop("code", None) + super().__init__(*args, **kwargs) + + self.authorization = Authorization.objects.filter(code=code).first() + + def verifing(self): + self.result_verify = verify_presentation(self.vp_token) + + def get_response_verify(self): + response = { + "verify": ',', + "redirect_uri": "", + "response": "", + } + verification = json.loads(self.result_verify) + if verification.get('errors') or verification.get('warnings'): + response["verify"] = "Error, Verification Failed" + return response + + response["verify"] = "Ok, Verification correct" + response["redirect_uri"] = self.get_redirect_url() + return response + + def get_redirect_url(self): + data = { + "code": self.authorization.code, + } + query_dict = QueryDict('', mutable=True) + query_dict.update(data) + + response_uri = settings.ALLOW_CODE_URI + + url = '{response_uri}?{params}'.format( + response_uri=response_uri, + params=query_dict.urlencode() + ) + return url + + def get_user_info(self): + tk = json.loads(self.vp_token) + self.user_info = tk.get( + "verifiableCredential", [{}] + )[-1].get("credentialSubject") diff --git a/oidc4vp/templates/credentials/verifiable_presentation.json b/oidc4vp/templates/credentials/verifiable_presentation.json new file mode 100644 index 0000000..a55b769 --- /dev/null +++ b/oidc4vp/templates/credentials/verifiable_presentation.json @@ -0,0 +1,11 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "http://example.org/presentations/3731", + "type": [ + "VerifiablePresentation" + ], + "holder": "{{ holder_did }}", + "verifiableCredential": {{ verifiable_credential_list|safe }} +} diff --git a/oidc4vp/templates/credentials_presentation.html b/oidc4vp/templates/credentials_presentation.html new file mode 100644 index 0000000..63f54ba --- /dev/null +++ b/oidc4vp/templates/credentials_presentation.html @@ -0,0 +1,96 @@ +{% extends "idhub/base.html" %} +{% load i18n %} + +{% block content %} +

+ + {{ subtitle }} +

+{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% if form.credentials.all %} +{% for presentation in form.presentation_definition %} +
+
+

{{ presentation|capfirst }} +

+
+ +
+
+
+ + + + + + + + + + + + {% for f in form.credentials.all %} + {% if f.schema.type.lower == presentation.lower %} + + + + + + + + {% endif %} + {% endfor %} + +
{{ f.type }}{{ f.description }}{{ f.get_issued_on }}
+
+
+
+{% endfor %} + +
+ {% trans 'I read and understood the' %} + {% trans 'data sharing notice' %} +
+ + +{% else %} +
+
+

{% trans 'There are not credentials for present' %}

+
+
+{% endif %} +
+ + + +{% endblock %} diff --git a/oidc4vp/tests.py b/oidc4vp/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/oidc4vp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/oidc4vp/urls.py b/oidc4vp/urls.py new file mode 100644 index 0000000..d7b79be --- /dev/null +++ b/oidc4vp/urls.py @@ -0,0 +1,16 @@ +from django.urls import path, reverse_lazy + +from oidc4vp import views + + +app_name = 'oidc4vp' + + +urlpatterns = [ + path('verify', views.VerifyView.as_view(), + name="verify"), + path('authorize', views.AuthorizeView.as_view(), + name="authorize"), + path('allow_code', views.AllowCodeView.as_view(), + name="allow_code"), +] diff --git a/oidc4vp/views.py b/oidc4vp/views.py new file mode 100644 index 0000000..a62e462 --- /dev/null +++ b/oidc4vp/views.py @@ -0,0 +1,165 @@ +import json +import base64 + +from django.conf import settings +from django.views.generic.edit import View, FormView +from django.http import HttpResponse, Http404, JsonResponse +from django.shortcuts import get_object_or_404, redirect +from django.views.decorators.csrf import csrf_exempt +from django.utils.decorators import method_decorator +from django.utils.translation import gettext_lazy as _ +from django.urls import reverse_lazy +from django.contrib import messages + +from oidc4vp.models import Authorization, Organization, OAuth2VPToken +from idhub.mixins import UserView + +from oidc4vp.forms import AuthorizeForm +from utils.idhub_ssikit import verify_presentation + + +class AuthorizeView(UserView, FormView): + title = _("My wallet") + section = "MyWallet" + template_name = "credentials_presentation.html" + subtitle = _('Credential presentation') + icon = 'bi bi-patch-check-fill' + form_class = AuthorizeForm + success_url = reverse_lazy('idhub:user_demand_authorization') + + def get(self, request, *args, **kwargs): + response = super().get(request, *args, **kwargs) + if self.request.session.get('next_url'): + return redirect(reverse_lazy('idhub:user_credentials_request')) + return response + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['user'] = self.request.user + try: + vps = json.loads(self.request.GET.get('presentation_definition')) + except: + vps = [] + kwargs['presentation_definition'] = vps + kwargs["org"] = self.get_org() + kwargs["code"] = self.request.GET.get('code') + return kwargs + + def get_form(self, form_class=None): + form = super().get_form(form_class=form_class) + if form.all_credentials.exists() and not form.credentials.exists(): + self.request.session['next_url'] = self.request.get_full_path() + return form + + def form_valid(self, form): + authorization = form.save() + if not authorization or authorization.status_code != 200: + messages.error(self.request, _("Error sending credential!")) + return super().form_valid(form) + try: + authorization = authorization.json() + except: + messages.error(self.request, _("Error sending credential!")) + return super().form_valid(form) + + verify = authorization.get('verify') + result, msg = verify.split(",") + if 'error' in result.lower(): + messages.error(self.request, msg) + if 'ok' in result.lower(): + messages.success(self.request, msg) + + if authorization.get('redirect_uri'): + return redirect(authorization.get('redirect_uri')) + elif authorization.get('response'): + txt = authorization.get('response') + messages.success(self.request, txt) + + return super().form_valid(form) + + def get_org(self): + client_id = self.request.GET.get("client_id") + if not client_id: + raise Http404("Organization not found!") + + org = get_object_or_404( + Organization, + client_id=client_id, + ) + return org + + +@method_decorator(csrf_exempt, name='dispatch') +class VerifyView(View): + def get(self, request, *args, **kwargs): + org = self.validate(request) + presentation_definition = json.dumps(settings.SUPPORTED_CREDENTIALS) + authorization = Authorization( + organization=org, + presentation_definition=presentation_definition + ) + authorization.save() + res = json.dumps({"redirect_uri": authorization.authorize()}) + return HttpResponse(res) + + def post(self, request, *args, **kwargs): + code = self.request.POST.get("code") + vp_tk = self.request.POST.get("vp_token") + + if not vp_tk or not code: + raise Http404("Page not Found!") + + org = self.validate(request) + + vp_token = OAuth2VPToken( + vp_token = vp_tk, + organization=org, + code=code + ) + if not vp_token.authorization: + raise Http404("Page not Found!") + + vp_token.verifing() + response = vp_token.get_response_verify() + vp_token.save() + if not vp_token.authorization.promotions.exists(): + response["redirect_uri"] = "" + response["response"] = "Validation Code {}".format(code) + + return JsonResponse(response) + + def validate(self, request): + auth_header = request.headers.get('Authorization', b'') + auth_data = auth_header.split() + + 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, + client_secret=client_secret + ) + return org + + raise Http404("Page not Found!") + + +class AllowCodeView(View): + def get(self, request, *args, **kwargs): + code = self.request.GET.get("code") + + if not code: + raise Http404("Page not Found!") + self.authorization = get_object_or_404( + Authorization, + code=code, + code_used=False + ) + if not self.authorization.promotions.exists(): + raise Http404("Page not Found!") + + promotion = self.authorization.promotions.all()[0] + return redirect(promotion.get_url(code)) + diff --git a/promotion/__init__.py b/promotion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/promotion/admin.py b/promotion/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/promotion/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/promotion/apps.py b/promotion/apps.py new file mode 100644 index 0000000..baccc02 --- /dev/null +++ b/promotion/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PromotionConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'promotion' diff --git a/promotion/forms.py b/promotion/forms.py new file mode 100644 index 0000000..5b96423 --- /dev/null +++ b/promotion/forms.py @@ -0,0 +1,65 @@ + +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, Authorization +from promotion.models import Promotion + + +class WalletForm(forms.Form): + organization = forms.ChoiceField(choices=[]) + + def __init__(self, *args, **kwargs): + self.presentation_definition = kwargs.pop('presentation_definition', []) + super().__init__(*args, **kwargs) + self.fields['organization'].choices = [ + (x.id, x.name) for x in Organization.objects.filter() + if x.response_uri != settings.RESPONSE_URI + ] + + def save(self, commit=True): + self.org = Organization.objects.filter( + id=self.data['organization'] + ) + if not self.org.exists(): + return + + self.org = self.org[0] + + self.authorization = Authorization( + organization=self.org, + presentation_definition=self.presentation_definition, + ) + self.promotion = Promotion( + discount = Promotion.Types.VULNERABLE.value, + authorize = self.authorization + ) + + if commit: + self.authorization.save() + self.promotion.save() + + return self.authorization.authorize() + + return + + +class ContractForm(forms.Form): + nif = forms.CharField() + name = forms.CharField() + first_last_name = forms.CharField() + second_last_name = forms.CharField() + email = forms.CharField() + email_repeat = forms.CharField() + telephone = forms.CharField() + birthday = forms.CharField() + gen = forms.CharField() + lang = forms.CharField() + diff --git a/promotion/migrations/0001_initial.py b/promotion/migrations/0001_initial.py new file mode 100644 index 0000000..cbc1f17 --- /dev/null +++ b/promotion/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2.5 on 2023-12-11 08:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ('oidc4vp', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Promotion', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('name', models.CharField(max_length=250)), + ( + 'discount', + models.PositiveSmallIntegerField( + choices=[(1, 'Financial vulnerability')] + ), + ), + ( + 'authorize', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='promotions', + to='oidc4vp.authorization', + ), + ), + ], + ), + ] diff --git a/promotion/migrations/__init__.py b/promotion/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/promotion/models.py b/promotion/models.py new file mode 100644 index 0000000..4401faf --- /dev/null +++ b/promotion/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from oidc4vp.models import Authorization + + +class Promotion(models.Model): + class Types(models.IntegerChoices): + VULNERABLE = 1, _("Financial vulnerability") + + name = models.CharField(max_length=250) + discount = models.PositiveSmallIntegerField( + choices=Types.choices, + ) + authorize = models.ForeignKey( + Authorization, + on_delete=models.CASCADE, + related_name='promotions', + null=True, + ) + + def get_url(self, code): + url = "{}?code={}".format( + reverse_lazy("promotion:contract"), + code + ) + return url + + def get_discount(self, price): + return price - price*0.25 + diff --git a/promotion/templates/select_wallet.html b/promotion/templates/select_wallet.html new file mode 100644 index 0000000..ba813ee --- /dev/null +++ b/promotion/templates/select_wallet.html @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + Escull la teva tarifa mòbil - Som Connexió + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + Tarifes
+ +
+
+
+ Persones sòcies 9.257 + Contractes 22.303 +
+ +
+ Blog  |  + Contacte +
+ + + Vols que et truquem? +
+
+skip to Main Content
+
+ + + + + + +
+ +
+ + +
+ + +
+ + + +
+ + +{% load i18n %} +{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +
+
+ {% bootstrap_form form %} +
+
+ + +
+
+ +
+ + +
+ + +
+ + + +
+ + + + + + + + + + +
+ + +
+ + + + +Back To Top + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/promotion/templates/somconnexio_contract.html b/promotion/templates/somconnexio_contract.html new file mode 100644 index 0000000..7b7779e --- /dev/null +++ b/promotion/templates/somconnexio_contract.html @@ -0,0 +1,1170 @@ + + + + + + + + + + + + + + + Escull la teva tarifa mòbil - Som Connexió + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + Tarifes
+ +
+
+
+ Persones sòcies 9.257 + Contractes 22.303 +
+ +
+ Blog  |  + Contacte +
+ + + Vols que et truquem? +
+
+skip to Main Content
+
+ + + + + + +
+ +
+ + +
+ + +
+ + + +
+ +

Tarifes Mòbil - Contractar

+
+
+{% load i18n %} +{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} + + +
+
+
+ +
+
    + + Resum + +
  • + Imports inicials +
  • +
  • + Tarjeta SIM {{ sim }}€ +
  • +
  • + Factura mensual +
  • +
  • + {{ mensual }}€ +
  • + Total {{ total }}€ (IVA inclòs) +
+
+
+
+
+ + +
+
+ Vols que et truquem? +
+
+
+
+ +
+
+
+
+ + +
+ +
+ + +
+ + +
+ + + +
+ + + + + + + + + + +
+ + +
+ + + + +Back To Top + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/promotion/templates/somconnexio_tarifes_mobil.html b/promotion/templates/somconnexio_tarifes_mobil.html new file mode 100644 index 0000000..3016e59 --- /dev/null +++ b/promotion/templates/somconnexio_tarifes_mobil.html @@ -0,0 +1,1219 @@ + + + + + + + + + + + + + + + Escull la teva tarifa mòbil - Som Connexió + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + Tarifes
+ +
+
+
+ Persones sòcies 9.257 + Contractes 22.303 +
+ +
+ Blog  |  + Contacte +
+ + + Vols que et truquem? +
+
+skip to Main Content
+
+ + + + + + +
+ +
+ + +
+ + +
+ + + +
+ + +
+ + +
+
El més preguntat
+
    +
  • + Mòbil: tarifes i detalls +
    +

    Pots consultar els preus i detalls de les tarifes de mòbil en aquest enllaç.

    +

     

    +
    +
  • +
  • + Quina cobertura de mòbil té Som Connexió? +
    +

    La cobertura del servei és Yoigo/MásMóvil, Orange (4G) i Movistar (3G) i el nostre proveïdor de serveis és MásMóvil.

    +
    +
  • +
  • + Trucades gratuïtes entre mòbils de Som Connexió +
    +

    Les tarifes de mòbil de 0 minuts i 150 minuts inclouen 1.000 minuts de trucades gratuïtes a d’altres telèfons mòbils de Som Connexió (a la tarifa de minuts il·limitats ja estan incloses totes les trucades a mòbils de qualsevol operadora dins l’estat espanyol).

    +

    Això vol dir que si truques a un telèfon mòbil de Som Connexió, els minuts que utilitzis no es descomptaran del teu abonament sinó d’aquests 1.000 minuts.

    +

    Aquesta prestació només s’aplica a la telefonia mòbil de Som Connexió. La telefonia fixa de Som Connexió no inclou aquests minuts, ni en el servei de Fibra ni en el d’ADSL.

    +

    Aquest servei és automàtic. No s’ha de fer res per activar-lo i no es pot desactivar.

    +
    +
  • +
  • + Costos d’alta (mòbil) +
    +

    Quan es fa una alta de servei de telefonia mòbil, afegim a la factura els costos de l’alta del servei: 2,05€ (IVA inclòs)

    +
    +
  • +
  • + Puc mantenir el meu número de mòbil? +
    +

    Sí, sense cap dubte! Se’n diu portabilitat. Però si vols, també es pot canviar.

    +
    +
  • +
  • + Contractar dades addicionals +
    +

    Si esgotes dades abans d’acabar el mes, tens l’opció de contractar dades addicionals que podràs utilitzar fins a acabar el mes:

    +
      +
    • 500 MB (2 €)*
    • +
    • 1 GB (3 €)*
    • +
    • 3 GB (5 €)*
    • +
    • 5 GB (6 €)*
    • +
    • 10 GB (9€)*
    • +
    +

    * IVA inclòs

    +

    Aquí t’expliquem com afegir dades extra.

    +

    Contractar dades addicionals en les tarifes compartides

    +

    Si els mòbils que estan compartint dades esgoten els 50 GB abans que acabi el més, es pot comprar una ampliació de 10 GB per 9€ o de 20 GB per 12€ que podran consumir els diferents mòbils. Aquí t’expliquem com afegir dades extra a les tarifes compartides.

    +
    +
  • +
  • + Preus SMS +
    +

    Els SMS tenen un preu de 0,10€/sms

    +

    IMPORTANT: els SMS no estan inclosos a cap de les tarifes de Som Connexió. Tampoc a la tarifa de minuts il·limitats.

    +
    +
  • +
  • + Preus de trucades fora de tarifa des del mòbil +
    +

    Aquests són els preus de les trucades un cop has superat els minuts que tens contractats a la teva tarifa:

    +

    0,18€ per l’establiment de la trucada, que inclou els 5 primers minuts de la conversa.

    +

    A partir del minut 6, es paga 0,036€/min.

    +

    Consulta les tarifes de roaming AQUÍ.

    +
    +
  • +
  • + Tarifa bàsica mòbil: 0 min i 0 Gb +
    +

    Amb un cost base de 2€ mensuals, es tracta d’un consum “a granel”, és a dir, que es paga per minut i per unitat de dades consumides.

    +

    Aquesta tarifa és adequada per a persones que utilitzen el mòbil per estar localitzables i fer una trucada molt puntualment (només surt a compte si es fan menys de 6 trucades al mes de curta durada) i no utilitza el mòbil per connectar-se a Internet o ho fa de una manera molt esporàdica.

    +

    El preu per trucada en aquest abonament és de 0,18€ per l’establiment de la trucada, que inclou els 5 primers minuts de la conversa. A partir del minut 6, es paga 0,036€/min

    +

    El preu del servei de dades quan no s’ha contractat cap abonament és de 0,036€/Mb

    +

    Si vols desactivar el servei de dades, ens ho has de sol·licitar a serveis@somconnexio.coop.

    +
    +
  • +
  • + NÚMEROS DE TARIFACIÓ ESPECIAL +
    +

    Els preus estàndard de veu o missatges exclouen:

    +
      +
    • Els serveis de tarifació especial (per exemple: 80x, 90x o SMS Premium). Mira la taula de tarifes AQUÍ
    • +
    • Trànsit internacional
    • +
    • Trànsit en itinerància
    • +
    +

    Alguns dels serveis de tarifació especial de cost elevat estan bloquejats per defecte. Per tenir més informació sobre l’opció de bloqueig dels números de cost especial, consulta aquesta TAULA.

    +

    En aquest enllaç de la OCU ens donen més informació sobre els números de tarificació especial.

    +
    +
  • +
+
+
+
+
+ + +
+
+ Vols que et truquem? +
+
+
+
+ +
+
+
+
+ + +
+ +
+ + +
+ + +
+ + + +
+ + + + + + + + + + +
+ + +
+ + + + +Back To Top + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/promotion/templates/somconnexio_thanks.html b/promotion/templates/somconnexio_thanks.html new file mode 100644 index 0000000..b3c694f --- /dev/null +++ b/promotion/templates/somconnexio_thanks.html @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + Escull la teva tarifa mòbil - Som Connexió + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + Tarifes
+ +
+
+
+ Persones sòcies 9.257 + Contractes 22.303 +
+ +
+ Blog  |  + Contacte +
+ + + Vols que et truquem? +
+
+skip to Main Content
+
+ + + + + + +
+ +
+ + +
+ + +
+ + + +
+

Contrato

+ + +
+
+Contrato realizado correctamente. Te hemos enviado la información por email. +
+
+ +
+
El més preguntat
+
    +
  • + Mòbil: tarifes i detalls +
    +

    Pots consultar els preus i detalls de les tarifes de mòbil en aquest enllaç.

    +

     

    +
    +
  • +
  • + Quina cobertura de mòbil té Som Connexió? +
    +

    La cobertura del servei és Yoigo/MásMóvil, Orange (4G) i Movistar (3G) i el nostre proveïdor de serveis és MásMóvil.

    +
    +
  • +
  • + Trucades gratuïtes entre mòbils de Som Connexió +
    +

    Les tarifes de mòbil de 0 minuts i 150 minuts inclouen 1.000 minuts de trucades gratuïtes a d’altres telèfons mòbils de Som Connexió (a la tarifa de minuts il·limitats ja estan incloses totes les trucades a mòbils de qualsevol operadora dins l’estat espanyol).

    +

    Això vol dir que si truques a un telèfon mòbil de Som Connexió, els minuts que utilitzis no es descomptaran del teu abonament sinó d’aquests 1.000 minuts.

    +

    Aquesta prestació només s’aplica a la telefonia mòbil de Som Connexió. La telefonia fixa de Som Connexió no inclou aquests minuts, ni en el servei de Fibra ni en el d’ADSL.

    +

    Aquest servei és automàtic. No s’ha de fer res per activar-lo i no es pot desactivar.

    +
    +
  • +
  • + Costos d’alta (mòbil) +
    +

    Quan es fa una alta de servei de telefonia mòbil, afegim a la factura els costos de l’alta del servei: 2,05€ (IVA inclòs)

    +
    +
  • +
  • + Puc mantenir el meu número de mòbil? +
    +

    Sí, sense cap dubte! Se’n diu portabilitat. Però si vols, també es pot canviar.

    +
    +
  • +
  • + Contractar dades addicionals +
    +

    Si esgotes dades abans d’acabar el mes, tens l’opció de contractar dades addicionals que podràs utilitzar fins a acabar el mes:

    +
      +
    • 500 MB (2 €)*
    • +
    • 1 GB (3 €)*
    • +
    • 3 GB (5 €)*
    • +
    • 5 GB (6 €)*
    • +
    • 10 GB (9€)*
    • +
    +

    * IVA inclòs

    +

    Aquí t’expliquem com afegir dades extra.

    +

    Contractar dades addicionals en les tarifes compartides

    +

    Si els mòbils que estan compartint dades esgoten els 50 GB abans que acabi el més, es pot comprar una ampliació de 10 GB per 9€ o de 20 GB per 12€ que podran consumir els diferents mòbils. Aquí t’expliquem com afegir dades extra a les tarifes compartides.

    +
    +
  • +
  • + Preus SMS +
    +

    Els SMS tenen un preu de 0,10€/sms

    +

    IMPORTANT: els SMS no estan inclosos a cap de les tarifes de Som Connexió. Tampoc a la tarifa de minuts il·limitats.

    +
    +
  • +
  • + Preus de trucades fora de tarifa des del mòbil +
    +

    Aquests són els preus de les trucades un cop has superat els minuts que tens contractats a la teva tarifa:

    +

    0,18€ per l’establiment de la trucada, que inclou els 5 primers minuts de la conversa.

    +

    A partir del minut 6, es paga 0,036€/min.

    +

    Consulta les tarifes de roaming AQUÍ.

    +
    +
  • +
  • + Tarifa bàsica mòbil: 0 min i 0 Gb +
    +

    Amb un cost base de 2€ mensuals, es tracta d’un consum “a granel”, és a dir, que es paga per minut i per unitat de dades consumides.

    +

    Aquesta tarifa és adequada per a persones que utilitzen el mòbil per estar localitzables i fer una trucada molt puntualment (només surt a compte si es fan menys de 6 trucades al mes de curta durada) i no utilitza el mòbil per connectar-se a Internet o ho fa de una manera molt esporàdica.

    +

    El preu per trucada en aquest abonament és de 0,18€ per l’establiment de la trucada, que inclou els 5 primers minuts de la conversa. A partir del minut 6, es paga 0,036€/min

    +

    El preu del servei de dades quan no s’ha contractat cap abonament és de 0,036€/Mb

    +

    Si vols desactivar el servei de dades, ens ho has de sol·licitar a serveis@somconnexio.coop.

    +
    +
  • +
  • + NÚMEROS DE TARIFACIÓ ESPECIAL +
    +

    Els preus estàndard de veu o missatges exclouen:

    +
      +
    • Els serveis de tarifació especial (per exemple: 80x, 90x o SMS Premium). Mira la taula de tarifes AQUÍ
    • +
    • Trànsit internacional
    • +
    • Trànsit en itinerància
    • +
    +

    Alguns dels serveis de tarifació especial de cost elevat estan bloquejats per defecte. Per tenir més informació sobre l’opció de bloqueig dels números de cost especial, consulta aquesta TAULA.

    +

    En aquest enllaç de la OCU ens donen més informació sobre els números de tarificació especial.

    +
    +
  • +
+
+
+
+
+ + +
+
+ Vols que et truquem? +
+
+
+
+ +
+
+
+
+ + +
+ +
+ + +
+ + +
+ + + +
+ + + + + + + + + + +
+ + +
+ + + + +Back To Top + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/promotion/tests.py b/promotion/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/promotion/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/promotion/urls.py b/promotion/urls.py new file mode 100644 index 0000000..2925480 --- /dev/null +++ b/promotion/urls.py @@ -0,0 +1,18 @@ +from django.urls import path, reverse_lazy + +from promotion import views + + +app_name = 'promotion' + + +urlpatterns = [ + path('', views.PromotionView.as_view(), + name="show_promotion"), + path('select_wallet', views.SelectWalletView.as_view(), + name="select_wallet"), + path('contract', views.ContractView.as_view(), + name="contract"), + path('contract/1', views.ThanksView.as_view(), + name="thanks"), +] diff --git a/promotion/views.py b/promotion/views.py new file mode 100644 index 0000000..79b47ea --- /dev/null +++ b/promotion/views.py @@ -0,0 +1,125 @@ +import json + +from django.views.generic.edit import View, FormView +from django.shortcuts import redirect +from django.template.loader import get_template +from django.urls import reverse_lazy +from django.http import HttpResponse + +from oidc4vp.models import Authorization +from promotion.forms import WalletForm, ContractForm + + +class PromotionView(View): + template_name = "somconnexio_tarifes_mobil.html" + def get(self, request, *args, **kwargs): + self.context = {} + template = get_template( + self.template_name, + ).render() + return HttpResponse(template) + +class ThanksView(View): + template_name = "somconnexio_thanks.html" + def get(self, request, *args, **kwargs): + self.context = {} + template = get_template( + self.template_name, + ).render() + return HttpResponse(template) + + +class ContractView(FormView): + template_name = "somconnexio_contract.html" + promotion = None + vp_token = None + authorization = None + form_class = ContractForm + success_url = reverse_lazy('promotion:thanks') + + def get_context_data(self, **kwargs): + self.context = super().get_context_data(**kwargs) + code = self.request.GET.get("code") + self.get_discount(code) + self.context.update({ + "promotion": self.promotion, + "verificable_presentation": self.vp_token, + "sim": 10.0, + "mensual": 15.0, + "total": 25.0 + }) + if self.promotion: + self.context['sim'] = self.promotion.get_discount(self.context["sim"]) + self.context['mensual'] = self.promotion.get_discount(self.context["mensual"]) + self.context['total'] = self.promotion.get_discount(self.context["total"]) + + if self.vp_token: + self.context['verificable_presentation'] = self.vp_token + + return self.context + + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + code = self.request.GET.get("code") + self.get_discount(code) + if not self.vp_token: + return kwargs + + self.vp_token.get_user_info() + kwargs['initial']["nif"] = self.vp_token.user_info.get("nif", '') + kwargs['initial']["name"] = self.vp_token.user_info.get("name", '') + kwargs['initial']["first_last_name"] = self.vp_token.user_info.get("first_last_name", '') + kwargs['initial']["second_last_name"] = self.vp_token.user_info.get("second_last_name", '') + kwargs['initial']["email"] = self.vp_token.user_info.get("email", '') + kwargs['initial']["email_repeat"] = self.vp_token.user_info.get("email", '') + kwargs['initial']["telephone"] = self.vp_token.user_info.get("telephone", '') + kwargs['initial']["birthday"] = self.vp_token.user_info.get("birthday", '') + kwargs['initial']["gen"] = self.vp_token.user_info.get("gen", '') + kwargs['initial']["lang"] = self.vp_token.user_info.get("lang", '') + return kwargs + + def form_valid(self, form): + return super().form_valid(form) + + def get_discount(self, code): + if not code: + return + if self.authorization: + return + + self.authorization = Authorization.objects.filter( + code=code, + code_used=False + ).first() + if self.authorization: + if self.authorization.promotions.exists(): + self.promotion = self.authorization.promotions.all()[0] + if self.authorization.vp_tokens.exists(): + self.vp_token = self.authorization.vp_tokens.all()[0] + + +class SelectWalletView(FormView): + template_name = "select_wallet.html" + form_class = WalletForm + success_url = reverse_lazy('promotion:select_wallet') + # def get(self, request, *args, **kwargs): + # self.context = {'form': fo} + # template = get_template( + # self.template_name, + # # context + # ).render() + # return HttpResponse(template) + + # def post(self, request, *args, **kwargs): + # super().post(request, *args, **kwargs) + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['presentation_definition'] = json.dumps(["MemberShipCard"]) + return kwargs + + def form_valid(self, form): + url = form.save() + return redirect(url) + diff --git a/requirements.txt b/requirements.txt index c09c9d6..71d5e9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,5 @@ jinja2==3.1.2 jsonref==1.1.0 pyld==2.0.3 pynacl==1.5.0 +more-itertools==10.1.0 +dj-database-url==2.1.0 diff --git a/schemas/member.json b/schemas/member.json deleted file mode 100644 index 38199be..0000000 --- a/schemas/member.json +++ /dev/null @@ -1,21 +0,0 @@ - { - "$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 61d9637..b9eecc5 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ import os from ast import literal_eval +from dj_database_url import parse as db_url from pathlib import Path from django.contrib.messages import constants as messages @@ -72,13 +73,16 @@ INSTALLED_APPS = [ 'django_bootstrap5', 'django_tables2', 'idhub_auth', - 'idhub' + 'oidc4vp', + 'idhub', + 'promotion' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', @@ -110,10 +114,15 @@ WSGI_APPLICATION = 'trustchain_idhub.wsgi.application' # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } + # 'default': { + # 'ENGINE': 'django.db.backends.sqlite3', + # 'NAME': BASE_DIR / 'db.sqlite3', + # } + 'default': config( + 'DATABASE_URL', + default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'), + cast=db_url + ) # 'default': config( # 'DATABASE_URL', # default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'), @@ -178,9 +187,38 @@ MESSAGE_TAGS = { LOCALE_PATHS = [ os.path.join(BASE_DIR, 'locale'), ] -LANGUAGE_CODE="en" +# LANGUAGE_CODE="en" # LANGUAGE_CODE="es" +LANGUAGE_CODE="ca" +gettext = lambda s: s +LANGUAGES = ( + ('de', gettext('German')), + ('en', gettext('English')), + ('ca', gettext('Catalan')), +) USE_I18N = True USE_L10N = True AUTH_USER_MODEL = 'idhub_auth.User' +RESPONSE_URI = config('RESPONSE_URI', default="") +ALLOW_CODE_URI= config('ALLOW_CODE_URI', default="") +SUPPORTED_CREDENTIALS = config( + 'SUPPORTED_CREDENTIALS', + default='[]', + cast=literal_eval +) + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": {"class": "logging.StreamHandler"}, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + }, + } +} + diff --git a/trustchain_idhub/urls.py b/trustchain_idhub/urls.py index f2fdc05..e8468a3 100644 --- a/trustchain_idhub/urls.py +++ b/trustchain_idhub/urls.py @@ -24,4 +24,6 @@ from django.contrib.auth import views as auth_views urlpatterns = [ # path('django-admin/', admin.site.urls), path('', include('idhub.urls')), + path('oidc4vp/', include('oidc4vp.urls')), + path('promotion/', include('promotion.urls')), ] diff --git a/utils/idhub_ssikit/README.md b/utils/idhub_ssikit/README.md index e69de29..8443717 100644 --- a/utils/idhub_ssikit/README.md +++ b/utils/idhub_ssikit/README.md @@ -0,0 +1,73 @@ +# Helper routines to manage DIDs/VC/VPs + +This module is a wrapper around the functions exported by SpruceID's `DIDKit` framework. + +## DID generation and storage + +For now DIDs are of the kind `did:key`, with planned support for `did:web` in the near future. + +Creation of a DID involves two steps: +* Generate a unique DID controller key +* Derive a `did:key` type from the key + +Both must be stored in the IdHub database and linked to a `User` for later retrieval. + +```python +# Use case: generate and link a new DID for an existing user +user = request.user # ... + +controller_key = idhub_ssikit.generate_did_controller_key() +did_string = idhub_ssikit.keydid_from_controller_key(controller_key) + + +did = idhub.models.DID( + did = did_string, + user = user +) +did_controller_key = idhub.models.DIDControllerKey( + key_material = controller_key, + owner_did = did +) + +did.save() +did_controller_key.save() +``` + +## Verifiable Credential issuance + +Verifiable Credential templates are stored as Jinja2 (TBD) templates in `/schemas` folder. Please examine each template to see what data must be passed to it in order to render. + +The data passed to the template must at a minimum include: +* issuer_did +* subject_did +* vc_id + +For example, in order to render `/schemas/member-credential.json`: + +```python +from jinja2 import Environment, FileSystemLoader, select_autoescape +import idhub_ssikit + +env = Environment( + loader=FileSystemLoader("vc_templates"), + autoescape=select_autoescape() +) +unsigned_vc_template = env.get_template("member-credential.json") + +issuer_user = request.user +issuer_did = user.dids[0] # TODO: Django ORM pseudocode +issuer_did_controller_key = did.keys[0] # TODO: Django ORM pseudocode + +data = { + "vc_id": "http://pangea.org/credentials/3731", + "issuer_did": issuer_did, + "subject_did": "did:web:[...]", + "issuance_date": "2020-08-19T21:41:50Z", + "subject_is_member_of": "Pangea" +} +signed_credential = idhub_ssikit.render_and_sign_credential( + unsigned_vc_template, + issuer_did_controller_key, + data +) +``` \ No newline at end of file diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py index 18a5ff2..cc3e9b4 100644 --- a/utils/idhub_ssikit/__init__.py +++ b/utils/idhub_ssikit/__init__.py @@ -4,6 +4,7 @@ import didkit import json import jinja2 from django.template.backends.django import Template +from django.template.loader import get_template def generate_did_controller_key(): @@ -49,26 +50,71 @@ def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data def sign_credential(unsigned_vc: str, jwk_issuer): """ - Signs the and unsigned credential with the provided key. + Signs the unsigned credential with the provided key. + The credential template must be rendered with all user data. """ async def inner(): - signed_vc = await didkit.issue_credential( - unsigned_vc, - '{"proofFormat": "ldp"}', - jwk_issuer - ) - return signed_vc + signed_vc = await didkit.issue_credential( + unsigned_vc, + '{"proofFormat": "ldp"}', + jwk_issuer + ) + return signed_vc return asyncio.run(inner()) -def verify_credential(vc, proof_options): +def verify_credential(vc): """ Returns a (bool, str) tuple indicating whether the credential is valid. If the boolean is true, the credential is valid and the second argument can be ignored. If it is false, the VC is invalid and the second argument contains a JSON object with further information. """ async def inner(): - return didkit.verify_credential(vc, proof_options) + return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}') return asyncio.run(inner()) + + +def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str: + async def inner(): + unsigned_vp = vp_template.render(data) + signed_vp = await didkit.issue_presentation( + unsigned_vp, + '{"proofFormat": "ldp"}', + jwk_holder + ) + return signed_vp + + data = { + "holder_did": holder_did, + "verifiable_credential_list": "[" + ",".join(vc_list) + "]" + } + + return asyncio.run(inner()) + + +def create_verifiable_presentation(jwk_holder: str, unsigned_vp: str) -> str: + async def inner(): + signed_vp = await didkit.issue_presentation( + unsigned_vp, + '{"proofFormat": "ldp"}', + jwk_holder + ) + return signed_vp + + return asyncio.run(inner()) + + +def verify_presentation(vp): + """ + Returns a (bool, str) tuple indicating whether the credential is valid. + If the boolean is true, the credential is valid and the second argument can be ignored. + If it is false, the VC is invalid and the second argument contains a JSON object with further information. + """ + async def inner(): + proof_options = '{"proofFormat": "ldp"}' + return await didkit.verify_presentation(vp, proof_options) + + return asyncio.run(inner()) +