Merge branch 'main' into release

This commit is contained in:
Cayo Puigdefabregas 2024-02-13 20:45:32 +01:00
commit b4058aba3d
44 changed files with 814 additions and 3938 deletions

View file

@ -50,7 +50,7 @@ jobs:
- name: Get DIDKit wheel - name: Get DIDKit wheel
id: didkit id: didkit
run: | run: |
wget https://gitea.pangea.org/trustchain-oc1-orchestral/ssikit_trustchain/raw/branch/master/didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl wget -O didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl https://gitea.pangea.org/api/v1/repos/trustchain-oc1-orchestral/ssikit_trustchain/raw/didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl?token=${{ secrets.FILE_GETTER_TOKEN }}
echo "Successfully downloaded DIDkit" echo "Successfully downloaded DIDkit"
- name: Install dependencies - name: Install dependencies
@ -69,3 +69,21 @@ jobs:
source venv/bin/activate source venv/bin/activate
python manage.py test python manage.py test
deploy:
needs: test
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Trigger Remote Script
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" -X POST http://45.150.187.54:5000/trigger-script -H "Authorization: SecretToken")
if [ "$response" -ne 200 ]; then
echo "Script execution failed with HTTP status $response"
exit 1
else
echo "Script execution successful"
exit 0
fi
if: success() && github.ref == 'refs/heads/main'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -23,7 +23,7 @@ from idhub.models import (
from idhub_auth.models import User from idhub_auth.models import User
class TermsConditionsForm(forms.Form): class TermsConditionsForm2(forms.Form):
accept = forms.BooleanField( accept = forms.BooleanField(
label=_("Accept terms and conditions of the service"), label=_("Accept terms and conditions of the service"),
required=False required=False
@ -50,6 +50,65 @@ class TermsConditionsForm(forms.Form):
return return
class TermsConditionsForm(forms.Form):
accept_privacy = forms.BooleanField(
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False
)
accept_legal = forms.BooleanField(
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False
)
accept_cookies = forms.BooleanField(
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
def get_label(self, url, read):
label = _('I read and accepted the')
label += f' <a class="btn btn-green-admin" target="_blank" href="{url}" '
label += f'title="{read}">{read}</a>'
return label
def privacy_label(self):
url = "https://laweb.pangea.org/politica-de-privacitat/"
read = _("Privacy policy")
return self.get_label(url, read)
def legal_label(self):
url = "https://laweb.pangea.org/avis-legal/"
read = _("Legal policy")
return self.get_label(url, read)
def cookies_label(self):
url = "https://laweb.pangea.org/politica-de-cookies-2/"
read = _("Cookies policy")
return self.get_label(url, read)
def clean(self):
data = self.cleaned_data
privacy = data.get("accept_privacy")
legal = data.get("accept_legal")
cookies = data.get("accept_cookies")
if privacy and legal and cookies:
self.user.accept_gdpr = True
else:
self.user.accept_gdpr = False
return data
def save(self, commit=True):
if commit:
self.user.save()
return self.user
return
class ImportForm(forms.Form): class ImportForm(forms.Form):
did = forms.ChoiceField(label=_("Did"), choices=[]) did = forms.ChoiceField(label=_("Did"), choices=[])
eidas1 = forms.ChoiceField( eidas1 = forms.ChoiceField(
@ -189,7 +248,7 @@ class ImportForm(forms.Form):
cred = VerificableCredential( cred = VerificableCredential(
verified=False, verified=False,
user=user, user=user,
csv_data=json.dumps(row), csv_data=json.dumps(row, default=str),
issuer_did=self._did, issuer_did=self._did,
schema=self._schema, schema=self._schema,
eidas1_did=self._eidas1 eidas1_did=self._eidas1

View file

@ -25,7 +25,7 @@ from django.contrib import messages
from utils import credtools from utils import credtools
from idhub_auth.models import User from idhub_auth.models import User
from idhub_auth.forms import ProfileForm from idhub_auth.forms import ProfileForm
from idhub.mixins import AdminView from idhub.mixins import AdminView, Http403
from idhub.email.views import NotifyActivateUserByEmail from idhub.email.views import NotifyActivateUserByEmail
from idhub.admin.forms import ( from idhub.admin.forms import (
ImportForm, ImportForm,
@ -60,9 +60,9 @@ from idhub.models import (
class TermsAndConditionsView(AdminView, FormView): class TermsAndConditionsView(AdminView, FormView):
template_name = "idhub/admin/terms_conditions.html" template_name = "idhub/admin/terms_conditions.html"
title = _("GDPR") title = _('Data protection')
section = "" section = ""
subtitle = _('Accept Terms and Conditions') subtitle = _('Terms and Conditions')
icon = 'bi bi-file-earmark-medical' icon = 'bi bi-file-earmark-medical'
form_class = TermsConditionsForm form_class = TermsConditionsForm
success_url = reverse_lazy('idhub:admin_dashboard') success_url = reverse_lazy('idhub:admin_dashboard')
@ -70,7 +70,12 @@ class TermsAndConditionsView(AdminView, FormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
kwargs['initial'] = {"accept": self.request.user.accept_gdpr} if self.request.user.accept_gdpr:
kwargs['initial'] = {
"accept_privacy": True,
"accept_legal": True,
"accept_cookies": True
}
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
@ -82,7 +87,9 @@ class DobleFactorAuthView(AdminView, View):
url = reverse_lazy('idhub:admin_dashboard') url = reverse_lazy('idhub:admin_dashboard')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.check_valid_user() if not self.request.user.is_admin:
raise Http403()
if not self.request.session.get("2fauth"): if not self.request.session.get("2fauth"):
return redirect(self.url) return redirect(self.url)
@ -700,12 +707,13 @@ class DidsView(Credentials, SingleTableView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
queryset = kwargs.pop('object_list', None) queryset = kwargs.pop('object_list', None)
dids = DID.objects.filter(user=self.request.user)
if queryset is None: if queryset is None:
self.object_list = self.model.objects.all() self.object_list = dids.all()
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context.update({ context.update({
'dids': DID.objects.filter(user=self.request.user), 'dids': dids
}) })
return context return context
@ -905,19 +913,20 @@ class SchemasImportAddView(SchemasMix):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.check_valid_user() self.check_valid_user()
file_name = kwargs['file_schema'] self.file_name = kwargs['file_schema']
schemas_files = os.listdir(settings.SCHEMAS_DIR) schemas_files = os.listdir(settings.SCHEMAS_DIR)
if file_name not in schemas_files: if self.file_name not in schemas_files:
file_name = self.file_name
messages.error(self.request, f"The schema {file_name} not exist!") messages.error(self.request, f"The schema {file_name} not exist!")
return redirect('idhub:admin_schemas_import') return redirect('idhub:admin_schemas_import')
schema = self.create_schema(file_name) schema = self.create_schema()
if schema: if schema:
messages.success(self.request, _("The schema was added sucessfully")) messages.success(self.request, _("The schema was added sucessfully"))
return redirect('idhub:admin_schemas') return redirect('idhub:admin_schemas')
def create_schema(self, file_name): def create_schema(self):
data = self.open_file(file_name) data = self.open_file()
try: try:
ldata = json.loads(data) ldata = json.loads(data)
assert credtools.validate_schema(ldata) assert credtools.validate_schema(ldata)
@ -933,7 +942,7 @@ class SchemasImportAddView(SchemasMix):
_description = json.dumps(ldata.get('description', '')) _description = json.dumps(ldata.get('description', ''))
schema = Schemas.objects.create( schema = Schemas.objects.create(
file_schema=file_name, file_schema=self.file_name,
data=data, data=data,
type=title, type=title,
_name=_name, _name=_name,
@ -944,9 +953,9 @@ class SchemasImportAddView(SchemasMix):
schema.save() schema.save()
return schema return schema
def open_file(self, file_name): def open_file(self):
data = '' data = ''
filename = Path(settings.SCHEMAS_DIR).joinpath(file_name) filename = Path(settings.SCHEMAS_DIR).joinpath(self.file_name)
with filename.open() as schema_file: with filename.open() as schema_file:
data = schema_file.read() data = schema_file.read()
@ -955,7 +964,7 @@ class SchemasImportAddView(SchemasMix):
def get_template_description(self): def get_template_description(self):
context = {} context = {}
template_name = 'credentials/{}'.format( template_name = 'credentials/{}'.format(
self.schema.file_schema self.file_name
) )
tmpl = get_template(template_name) tmpl = get_template(template_name)
return tmpl.render(context) return tmpl.render(context)
@ -970,7 +979,7 @@ class SchemasImportAddView(SchemasMix):
class ImportView(ImportExport, SingleTableView): class ImportView(ImportExport, SingleTableView):
template_name = "idhub/admin/import.html" template_name = "idhub/admin/import.html"
table_class = DataTable table_class = DataTable
subtitle = _('Import data') subtitle = _('Imported data')
icon = '' icon = ''
model = File_datas model = File_datas

View file

@ -57,12 +57,13 @@ class NotifyActivateUserByEmail:
html_email = loader.render_to_string(self.html_email_template_name, context) html_email = loader.render_to_string(self.html_email_template_name, context)
email_message.attach_alternative(html_email, 'text/html') email_message.attach_alternative(html_email, 'text/html')
try: try:
if settings.DEVELOPMENT: if settings.ENABLE_EMAIL:
logger.warning(to_email) email_message.send()
logger.warning(body)
return return
email_message.send() logger.warning(to_email)
logger.warning(body)
except Exception as err: except Exception as err:
logger.error(err) logger.error(err)
return return

View file

@ -23,11 +23,12 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
ADMIN_EMAIL = config('ADMIN_EMAIL', 'admin@example.org') ADMIN_EMAIL = config('ADMIN_EMAIL', 'admin@example.org')
ADMIN_PASSWORD = config('ADMIN_PASSWORD', '1234') ADMIN_PASSWORD = config('ADMIN_PASSWORD', '1234')
USER_EMAIL = config('USER_EMAIL', 'user1@example.org')
USER_PASSWORD = config('USER_PASSWORD', '1234')
self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD) self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD)
self.create_users(USER_EMAIL, USER_PASSWORD) if settings.CREATE_TEST_USERS:
for u in range(1, 6):
user = 'user{}@example.org'.format(u)
self.create_users(user, '1234')
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
ORGANIZATION = os.path.join(BASE_DIR, settings.ORG_FILE) ORGANIZATION = os.path.join(BASE_DIR, settings.ORG_FILE)

View file

@ -674,7 +674,6 @@ class VerificableCredential(models.Model):
'organisation': settings.ORGANIZATION or '', 'organisation': settings.ORGANIZATION or '',
} }
context.update(d) context.update(d)
context['firstName'] = ""
return context return context
def render(self, domain): def render(self, domain):

View file

@ -5,14 +5,14 @@
<div class="well"> <div class="well">
<div class="row-fluid"> <div class="row-fluid">
<h2>{% trans 'Doble Factor of Authentication' %}</h2> <h2>{% trans 'Two-factor Authentication' %}</h2>
</div> </div>
</div> </div>
<div class="well"> <div class="well">
<div class="row-fluid"> <div class="row-fluid">
<div> <div>
<span>{% trans "We have sent an email with a link that you have to select in order to login." %}</span> <span>{% trans "We have sent you an email with a link that you have to click to log in." %}</span>
</div> </div>
</div><!-- /.row-fluid --> </div><!-- /.row-fluid -->
</div><!--/.well--> </div><!--/.well-->

View file

@ -1,6 +1,6 @@
{% load i18n %}{% autoescape off %} {% load i18n %}{% autoescape off %}
<p> <p>
{% blocktrans %}You're receiving this email because you try to access in {{ site_name }}.{% endblocktrans %} {% blocktrans %}You're receiving this email because you tried to access {{ site_name }}.{% endblocktrans %}
</p> </p>
<p> <p>

View file

@ -1,5 +1,5 @@
{% load i18n %}{% autoescape off %} {% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you try to access in {{ site_name }}.{% endblocktrans %} {% blocktrans %}You're receiving this email because you tried to access {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page" %} {% trans "Please go to the following page" %}
{% block reset_link %} {% block reset_link %}

View file

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

View file

@ -2,25 +2,7 @@
"@context": [ "@context": [
"https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/v1",
"https://idhub.pangea.org/credentials/base/v1", "https://idhub.pangea.org/credentials/base/v1",
{ "https://idhub.pangea.org/credentials/course-credential/v1"
"firstName": "https://idhub.pangea.org/context/#firstName",
"lastName": "https://idhub.pangea.org/context/#lastName",
"personalIdentifier": "https://idhub.pangea.org/context/#personalIdentifier",
"issuedDate": "https://idhub.pangea.org/context/#issuedDate",
"modeOfInstruction": "https://idhub.pangea.org/context/#modeOfInstruction",
"courseDuration": "https://idhub.pangea.org/context/#courseDuration",
"courseDays": "https://idhub.pangea.org/context/#courseDays",
"courseName": "https://idhub.pangea.org/context/#courseName",
"courseDescription": "https://idhub.pangea.org/context/#courseDescription",
"gradingScheme": "https://idhub.pangea.org/context/#gradingScheme",
"scoreAwarded": "https://idhub.pangea.org/context/#scoreAwarded",
"qualificationAwarded": "https://idhub.pangea.org/context/#qualificationAwarded",
"courseLevel": "https://idhub.pangea.org/context/#courseLevel",
"courseFramework": "https://idhub.pangea.org/context/#courseFramework",
"courseCredits": "https://idhub.pangea.org/context/#courseCredits",
"dateOfAssessment": "https://idhub.pangea.org/context/#dateOfAssessment",
"evidenceAssessment": "https://idhub.pangea.org/context/#evidenceAssessment"
}
], ],
"id": "{{ vc_id }}", "id": "{{ vc_id }}",
"type": [ "type": [
@ -59,6 +41,7 @@
"id": "{{ subject_did }}", "id": "{{ subject_did }}",
"firstName": "{{ firstName }}", "firstName": "{{ firstName }}",
"lastName": "{{ lastName }}", "lastName": "{{ lastName }}",
"email": "{{ email }}",
"personalIdentifier": "{{ personalIdentifier }}", "personalIdentifier": "{{ personalIdentifier }}",
"issuedDate": "{{ issuedDate }}", "issuedDate": "{{ issuedDate }}",
"modeOfInstruction": "{{ modeOfInstruction }}", "modeOfInstruction": "{{ modeOfInstruction }}",

View file

@ -2,14 +2,7 @@
"@context": [ "@context": [
"https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/v1",
"https://idhub.pangea.org/credentials/base/v1", "https://idhub.pangea.org/credentials/base/v1",
{ "https://idhub.pangea.org/credentials/e-operator-claim/v1"
"legalName": "https://idhub.pangea.org/context/#legalName",
"accreditedBy": "https://idhub.pangea.org/context/#accreditedBy",
"operatorNumber": "https://idhub.pangea.org/context/#operatorNumber",
"limitJurisdiction": "https://idhub.pangea.org/context/#limitJurisdiction",
"accreditedFor": "https://idhub.pangea.org/context/#accreditedFor",
"role": "https://idhub.pangea.org/context/#role"
}
], ],
"id": "{{ vc_id }}", "id": "{{ vc_id }}",
"type": [ "type": [
@ -59,7 +52,8 @@
"operatorNumber": "{{ operatorNumber }}", "operatorNumber": "{{ operatorNumber }}",
"limitJurisdiction": "{{ limitJurisdiction }}", "limitJurisdiction": "{{ limitJurisdiction }}",
"accreditedFor": "{{ accreditedFor }}", "accreditedFor": "{{ accreditedFor }}",
"role": "{{ role }}" "role": "{{ role }}",
"email": "{{ email }}"
}, },
"credentialSchema": { "credentialSchema": {
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json", "id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",

View file

@ -2,26 +2,7 @@
"@context": [ "@context": [
"https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/v1",
"https://idhub.pangea.org/credentials/base/v1", "https://idhub.pangea.org/credentials/base/v1",
{ "https://idhub.pangea.org/credentials/federation-membership/v1"
"federation": "https://idhub.pangea.org/context/#federation",
"legalName": "https://idhub.pangea.org/context/#legalName",
"shortName": "https://idhub.pangea.org/context/#shortName",
"registrationIdentifier": "https://idhub.pangea.org/context/#registrationIdentifier",
"publicRegistry": "https://idhub.pangea.org/context/#publicRegistry",
"streetAddress": "https://idhub.pangea.org/context/#streetAddress",
"postCode": "https://idhub.pangea.org/context/#postCode",
"city": "https://idhub.pangea.org/context/#city",
"taxReference": "https://idhub.pangea.org/context/#taxReference",
"membershipType": "https://idhub.pangea.org/context/#membershipType",
"membershipStatus": "https://idhub.pangea.org/context/#membershipStatus",
"membershipId": "https://idhub.pangea.org/context/#membershipId",
"membershipSince": "https://idhub.pangea.org/context/#membershipSince",
"email": "https://idhub.pangea.org/context/#email",
"phone": "https://idhub.pangea.org/context/#phone",
"website": "https://idhub.pangea.org/context/#website",
"evidence": "https://idhub.pangea.org/context/#evidence",
"certificationDate": "https://idhub.pangea.org/context/#certificationDate"
}
], ],
"id": "{{ vc_id }}", "id": "{{ vc_id }}",
"type": [ "type": [

View file

@ -20,38 +20,68 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="row">
<div class="row mt-4">
<div class="col"> <div class="col">
You must read the terms and conditions of this service and accept the {{ form.accept_privacy }}
<a class="btn btn-green-admin" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#gdpr" title="{% trans 'GDPR' %}">Read GDPR</a> {{ form.privacy_label|safe }}
</div> </div>
</div> </div>
<div class="row"> <div class="row mt-2">
<div class="col-sm-4"> <div class="col">
{% bootstrap_form form %} {{ form.accept_legal }}
{{ form.legal_label|safe }}
</div> </div>
</div> </div>
<div class="form-actions-no-box"> <div class="row mt-2">
<a class="btn btn-grey" href="{% url 'idhub:admin_dashboard' %}">{% translate "Cancel" %}</a> <div class="col">
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> {{ form.accept_cookies }}
{{ form.cookies_label|safe }}
</div>
</div>
<div class="form-actions-no-box mt-4">
<a href="javascript:accepts();" type="button" class="btn btn-green-admin">{% trans 'Confirm' %}</a>
</div> </div>
</form>
<!-- Modal --> <!-- Modal -->
<div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{% trans 'GDPR info' %}</h5> <h5 class="modal-title" id="exampleModalLabel">{% trans 'Data protection' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Here we write the info about GDPR</p> {% blocktrans %}
<p>If you do not consent to all terms and conditons this service may not be available to
anyone in your organization.<br /><br />
If you are sure to opt out of using this service please contact <a href="mailto:suport@pangea.org">suport@pangea.org</a> to unsubscribe. If you wish to continue, you must consent to all terms and conditions.</p>
{% endblocktrans %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Close' %}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button>
<input id="submit" class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Yes' %}" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form>
{% endblock %}
{% block extrascript %}
<script>
$(document).ready(function() {
});
function accepts() {
var privacy = $("#id_accept_privacy").prop("checked");
var policy = $("#id_accept_legal").prop("checked");
var cookies = $("#id_accept_cookies").prop("checked");
if (privacy && policy && cookies) {
$("#submit").trigger("click");
} else {
$("#gdpr").modal("show");
}
}
</script>
{% endblock %} {% endblock %}

View file

@ -87,8 +87,8 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if path == 'user_gdpr' %}active2{% endif %}" href="{% url 'idhub:user_gdpr' %}"> <a class="nav-link {% if path == 'user_terms_and_conditions' %}active2{% endif %}" href="{% url 'idhub:user_terms_and_conditions' %}">
{% trans 'GDPR info' %} {% trans 'Data protection' %}
</a> </a>
</li> </li>
</ul> </ul>
@ -150,9 +150,12 @@
</div> </div>
</div> </div>
{% block script %}
<script src="{% static "js/jquery-3.3.1.slim.min.js" %}"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script> <script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
<script src="/static/js/dashboard.js"></script> {% block extrascript %}{% endblock %}
{% endblock %}
</body> </body>
</html> </html>

View file

@ -11,7 +11,12 @@
</h3> </h3>
</div> </div>
<div class="col text-center"> <div class="col text-center">
<a href="javascript:void()" type="button" class="btn btn-green-user me-3">{% trans 'ARCO Forms' %}</a> {% if lang == 'es' %}
<a href="https://laweb.pangea.org/es/politica-de-proteccion-de-datos/acceso-a-los-formularios-arco/" target="_blank" type="button" class="btn btn-green-user me-3">{% trans 'ARCO Forms' %}</a>
{% else %}
<a href="https://laweb.pangea.org/politica-de-proteccio-de-dades/acces-als-formularis-arco/" target="_blank" type="button" class="btn btn-green-user me-3">{% trans 'ARCO Forms' %}</a>
{% endif %}
<a href="javascript:void()" type="button" class="btn btn-green-user">{% trans 'Notice of Privacy' %}</a> <a href="javascript:void()" type="button" class="btn btn-green-user">{% trans 'Notice of Privacy' %}</a>
</div> </div>
</div> </div>

View file

@ -20,38 +20,68 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="row"> <div class="row mt-4">
<div class="col"> <div class="col">
You must read the terms and conditions of this service and accept the {{ form.accept_privacy }}
<a class="btn btn-green-user" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#gdpr" title="{% trans 'GDPR' %}">Read GDPR</a> {{ form.privacy_label|safe }}
</div> </div>
</div> </div>
<div class="row"> <div class="row mt-2">
<div class="col-sm-4"> <div class="col">
{% bootstrap_form form %} {{ form.accept_legal }}
{{ form.legal_label|safe }}
</div> </div>
</div> </div>
<div class="form-actions-no-box"> <div class="row mt-2">
<a class="btn btn-grey" href="{% url 'idhub:user_dashboard' %}">{% translate "Cancel" %}</a> <div class="col">
<input class="btn btn-green-user" type="submit" name="submit" value="{% translate 'Save' %}" /> {{ form.accept_cookies }}
{{ form.cookies_label|safe }}
</div>
</div>
<div class="form-actions-no-box mt-4">
<a href="javascript:accepts();" type="button" class="btn btn-green-user">{% trans 'Confirm' %}</a>
</div> </div>
</form>
<!-- Modal --> <!-- Modal -->
<div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{% trans 'GDPR info' %}</h5> <h5 class="modal-title" id="exampleModalLabel">{% trans 'Data protection' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Here we write the info about GDPR</p> {% blocktrans %}
<p>If you do not accept all the terms and conditions of this service you will not be able
to continue.
<br /><br />
Are you sure to opt out of using this service?</p>
{% endblocktrans %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Close' %}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button>
<input id="submit" class="btn btn-green-user" type="submit" name="submit" value="{% translate 'Yes' %}" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form>
{% endblock %}
{% block extrascript %}
<script>
$(document).ready(function() {
});
function accepts() {
var privacy = $("#id_accept_privacy").prop("checked");
var policy = $("#id_accept_legal").prop("checked");
var cookies = $("#id_accept_cookies").prop("checked");
if (privacy && policy && cookies) {
$("#submit").trigger("click");
} else {
$("#gdpr").modal("show");
}
}
</script>
{% endblock %} {% endblock %}

View file

@ -19,6 +19,7 @@ from django.views.generic import RedirectView
from django.urls import path, reverse_lazy from django.urls import path, reverse_lazy
from .views import ( from .views import (
LoginView, LoginView,
PasswordResetView,
PasswordResetConfirmView, PasswordResetConfirmView,
serve_did, serve_did,
DobleFactorSendView, DobleFactorSendView,
@ -34,16 +35,7 @@ urlpatterns = [
permanent=False)), permanent=False)),
path('login/', LoginView.as_view(), name='login'), path('login/', LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('auth/password_reset/', path('auth/password_reset/', PasswordResetView.as_view(), name='password_reset'),
auth_views.PasswordResetView.as_view(
template_name='auth/password_reset.html',
email_template_name='auth/password_reset_email.txt',
html_email_template_name='auth/password_reset_email.html',
subject_template_name='auth/password_reset_subject.txt',
success_url=reverse_lazy('idhub:password_reset_done')
),
name='password_reset'
),
path('auth/password_reset/done/', path('auth/password_reset/done/',
auth_views.PasswordResetDoneView.as_view( auth_views.PasswordResetDoneView.as_view(
template_name='auth/password_reset_done.html' template_name='auth/password_reset_done.html'
@ -53,13 +45,6 @@ urlpatterns = [
path('auth/reset/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), path('auth/reset/<uidb64>/<token>/', PasswordResetConfirmView.as_view(),
name='password_reset_confirm' name='password_reset_confirm'
), ),
# path('auth/reset/<uidb64>/<token>/',
# auth_views.PasswordResetConfirmView.as_view(
# template_name='auth/password_reset_confirm.html',
# success_url=reverse_lazy('idhub:password_reset_complete')
# ),
# name='password_reset_confirm'
# ),
path('auth/reset/done/', path('auth/reset/done/',
auth_views.PasswordResetCompleteView.as_view( auth_views.PasswordResetCompleteView.as_view(
template_name='auth/password_reset_complete.html' template_name='auth/password_reset_complete.html'

View file

@ -15,8 +15,16 @@ class ProfileForm(forms.ModelForm):
class TermsConditionsForm(forms.Form): class TermsConditionsForm(forms.Form):
accept = forms.BooleanField( accept_privacy = forms.BooleanField(
label=_("Accept terms and conditions of the service"), widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False
)
accept_legal = forms.BooleanField(
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False
)
accept_cookies = forms.BooleanField(
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
required=False required=False
) )
@ -24,9 +32,33 @@ class TermsConditionsForm(forms.Form):
self.user = kwargs.pop('user', None) self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def get_label(self, url, read):
label = _('I read and accepted the')
label += f' <a class="btn btn-green-user" target="_blank" href="{url}" '
label += f'title="{read}">{read}</a>'
return label
def privacy_label(self):
url = "https://laweb.pangea.org/politica-de-privacitat/"
read = _("Privacy policy")
return self.get_label(url, read)
def legal_label(self):
url = "https://laweb.pangea.org/avis-legal/"
read = _("Legal policy")
return self.get_label(url, read)
def cookies_label(self):
url = "https://laweb.pangea.org/politica-de-cookies-2/"
read = _("Cookies policy")
return self.get_label(url, read)
def clean(self): def clean(self):
data = self.cleaned_data data = self.cleaned_data
if data.get("accept"): privacy = data.get("accept_privacy")
legal = data.get("accept_legal")
cookies = data.get("accept_cookies")
if privacy and legal and cookies:
self.user.accept_gdpr = True self.user.accept_gdpr = True
else: else:
self.user.accept_gdpr = False self.user.accept_gdpr = False

View file

@ -117,6 +117,13 @@ class DIDTable(tables.Table):
class CredentialsTable(tables.Table): class CredentialsTable(tables.Table):
description = tables.Column(verbose_name="Details", empty_values=()) description = tables.Column(verbose_name="Details", empty_values=())
view_credential = ButtonColumn(
linkify={
"viewname": "idhub:user_credential",
"args": [tables.A("pk")]
},
orderable=False
)
def render_description(self, record): def render_description(self, record):
return record.get_description() return record.get_description()
@ -131,6 +138,9 @@ class CredentialsTable(tables.Table):
return (queryset, True) return (queryset, True)
def render_view_credential(self):
return format_html('<i class="bi bi-eye"></i>')
class Meta: class Meta:
model = VerificableCredential model = VerificableCredential
template_name = "idhub/custom_table.html" template_name = "idhub/custom_table.html"

View file

@ -101,6 +101,13 @@ class ProfileView(MyProfile, UpdateView, SingleTableView):
def form_valid(self, form): def form_valid(self, form):
return super().form_valid(form) return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'lang': self.request.LANGUAGE_CODE,
})
return context
class RolesView(MyProfile, SingleTableView): class RolesView(MyProfile, SingleTableView):
template_name = "idhub/user/roles.html" template_name = "idhub/user/roles.html"
@ -116,7 +123,7 @@ class RolesView(MyProfile, SingleTableView):
class GDPRView(MyProfile, TemplateView): class GDPRView(MyProfile, TemplateView):
template_name = "idhub/user/gdpr.html" template_name = "idhub/user/gdpr.html"
subtitle = _('GDPR info') subtitle = _('Data protection')
icon = 'bi bi-file-earmark-medical' icon = 'bi bi-file-earmark-medical'
@ -135,9 +142,9 @@ class CredentialsView(MyWallet, SingleTableView):
class TermsAndConditionsView(UserView, FormView): class TermsAndConditionsView(UserView, FormView):
template_name = "idhub/user/terms_conditions.html" template_name = "idhub/user/terms_conditions.html"
title = _("GDPR") title = _("Data Protection")
section = "" section = ""
subtitle = _('Accept Terms and Conditions') subtitle = _('Terms and Conditions')
icon = 'bi bi-file-earmark-medical' icon = 'bi bi-file-earmark-medical'
form_class = TermsConditionsForm form_class = TermsConditionsForm
success_url = reverse_lazy('idhub:user_dashboard') success_url = reverse_lazy('idhub:user_dashboard')
@ -145,7 +152,12 @@ class TermsAndConditionsView(UserView, FormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
kwargs['initial'] = {"accept": self.request.user.accept_gdpr} if self.request.user.accept_gdpr:
kwargs['initial'] = {
"accept_privacy": True,
"accept_legal": True,
"accept_cookies": True
}
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):

View file

@ -1,4 +1,5 @@
import uuid import uuid
import logging
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
@ -16,6 +17,9 @@ from idhub.email.views import NotifyActivateUserByEmail
from trustchain_idhub import settings from trustchain_idhub import settings
logger = logging.getLogger(__name__)
class LoginView(auth_views.LoginView): class LoginView(auth_views.LoginView):
template_name = 'auth/login.html' template_name = 'auth/login.html'
extra_context = { extra_context = {
@ -52,7 +56,7 @@ class LoginView(auth_views.LoginView):
# ) # )
# cache.set("KEY_DIDS", encryption_key, None) # cache.set("KEY_DIDS", encryption_key, None)
cache.set("KEY_DIDS", sensitive_data_encryption_key, None) cache.set("KEY_DIDS", sensitive_data_encryption_key, None)
if not settings.DEVELOPMENT: if settings.ENABLE_2FACTOR_AUTH:
self.request.session["2fauth"] = str(uuid.uuid4()) self.request.session["2fauth"] = str(uuid.uuid4())
return redirect(reverse_lazy('idhub:confirm_send_2f')) return redirect(reverse_lazy('idhub:confirm_send_2f'))
@ -69,13 +73,31 @@ class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
success_url = reverse_lazy('idhub:password_reset_complete') success_url = reverse_lazy('idhub:password_reset_complete')
def form_valid(self, form): def form_valid(self, form):
password = form.cleaned_data.get("password") password = form.cleaned_data.get("new_password1")
user = form.get_user() user = form.user
user.set_password(password)
user.set_encrypted_sensitive_data(password) user.set_encrypted_sensitive_data(password)
user.save() user.save()
return HttpResponseRedirect(self.success_url) return HttpResponseRedirect(self.success_url)
class PasswordResetView(auth_views.PasswordResetView):
template_name = 'auth/password_reset.html'
email_template_name = 'auth/password_reset_email.txt'
html_email_template_name = 'auth/password_reset_email.html'
subject_template_name = 'auth/password_reset_subject.txt'
success_url = reverse_lazy('idhub:password_reset_done')
def form_valid(self, form):
try:
return super().form_valid(form)
except Exception as err:
logger.error(err)
# url_error = reverse_lazy('idhub:password_reset_error')
# return HttpResponseRedirect(url_error)
return HttpResponseRedirect(self.success_url)
def serve_did(request, did_id): def serve_did(request, did_id):
id_did = f'did:web:{settings.DOMAIN}:did-registry:{did_id}' id_did = f'did:web:{settings.DOMAIN}:did-registry:{did_id}'
did = get_object_or_404(DID, did=id_did) did = get_object_or_404(DID, did=id_did)

View file

@ -155,3 +155,11 @@ class User(AbstractBaseUser):
pw = base64.b64decode(password.encode('utf-8')*4) pw = base64.b64decode(password.encode('utf-8')*4)
sb_key = self.derive_key_from_password(pw) sb_key = self.derive_key_from_password(pw)
return nacl.secret.SecretBox(sb_key) return nacl.secret.SecretBox(sb_key)
def change_password(self, old_password, new_password):
sensitive_data = self.decrypt_sensitive_data(old_password)
self.encrypted_sensitive_data = self.encrypt_sensitive_data(
new_password,
sensitive_data
)
self.set_password(new_password)

View file

@ -137,7 +137,6 @@ class VerifyView(View):
vp_token.verifing() vp_token.verifing()
response = vp_token.get_response_verify() response = vp_token.get_response_verify()
vp_token.save() vp_token.save()
if not vp_token.authorization.promotions.exists():
response["response"] = "Validation Code {}".format(code) response["response"] = "Validation Code {}".format(code)
return JsonResponse(response) return JsonResponse(response)

View file

@ -14,7 +14,10 @@ from promotion.models import Promotion
class WalletForm(forms.Form): class WalletForm(forms.Form):
organization = forms.ChoiceField(choices=[]) organization = forms.ChoiceField(
choices=[],
widget=forms.Select(attrs={'class': 'form-select'})
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.presentation_definition = kwargs.pop('presentation_definition', []) self.presentation_definition = kwargs.pop('presentation_definition', [])

File diff suppressed because one or more lines are too long

View file

@ -36,7 +36,7 @@
<link href="{% static "/css/dashboard.css" %}" rel="stylesheet"> <link href="{% static "/css/dashboard.css" %}" rel="stylesheet">
</head> </head>
<body id="body-login"> <body id="body-login">
<header class="navbar navbar-dark sticky-top bg-grey flex-md-nowrap p-0 shadow"> <header class="navbar navbar-dark sticky-top bg-grey flex-md-nowrap p-0 shadow" style="background-color: #c4a812;">
<div class="navbar-nav navbar-sub-brand"> <div class="navbar-nav navbar-sub-brand">
</div> </div>
<div class="navbar-nav"> <div class="navbar-nav">
@ -66,7 +66,7 @@
<form role="form" method="post"> <form role="form" method="post">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col">
{% bootstrap_form form %} {% bootstrap_form form %}
</div> </div>
</div> </div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -68,9 +68,9 @@ class ContractView(FormView):
return kwargs return kwargs
self.vp_token.get_user_info() self.vp_token.get_user_info()
kwargs['initial']["nif"] = self.vp_token.user_info.get("nif", '') kwargs['initial']["nif"] = self.vp_token.user_info.get("identityNumber", '')
kwargs['initial']["name"] = self.vp_token.user_info.get("name", '') kwargs['initial']["name"] = self.vp_token.user_info.get("firstName", '')
kwargs['initial']["first_last_name"] = self.vp_token.user_info.get("first_last_name", '') kwargs['initial']["first_last_name"] = self.vp_token.user_info.get("lastName", '')
kwargs['initial']["second_last_name"] = self.vp_token.user_info.get("second_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"] = self.vp_token.user_info.get("email", '')
kwargs['initial']["email_repeat"] = self.vp_token.user_info.get("email", '') kwargs['initial']["email_repeat"] = self.vp_token.user_info.get("email", '')

View file

@ -27,4 +27,6 @@ uharfbuzz==0.38.0
fontTools==4.47.0 fontTools==4.47.0
weasyprint==60.2 weasyprint==60.2
ujson==5.9.0 ujson==5.9.0
openpyxl==3.1.2
jsonpath_ng==1.6.1
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl ./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl

View file

@ -1,5 +1,5 @@
{ {
"$id": "https://idhub.pangea.org/vc_schemas/courseCredential", "$id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NGO Course Credential Schema", "title": "NGO Course Credential Schema",
"description": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat", "description": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat",
@ -40,6 +40,10 @@
"type": "string", "type": "string",
"description": "The family name of the student" "description": "The family name of the student"
}, },
"email": {
"type": "string",
"format": "email"
},
"personalIdentifier": { "personalIdentifier": {
"type": "string", "type": "string",
"description": "The personal identifier of the student, such as ID number" "description": "The personal identifier of the student, such as ID number"
@ -116,6 +120,7 @@
"id", "id",
"firstName", "firstName",
"lastName", "lastName",
"email",
"personalIdentifier", "personalIdentifier",
"issuedDate", "issuedDate",
"modeOfInstruction", "modeOfInstruction",

View file

@ -1,176 +0,0 @@
{
"$id": "https://idhub.pangea.org/vc_schemas/devicePurchase.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Purchase of an eReuse device",
"description": "A device purchase credential is a proof of purchase of a device from a seller by a buyer",
"name": [
{
"value": "Device purchase credential",
"lang": "en"
},
{
"value": "Credencial d'adquisició d'un dispositiu",
"lang": "ca_ES"
},
{
"value": "Credencial de adquisición de un dispositivo",
"lang": "es"
}
],
"type": "object",
"allOf": [
{
"$ref": "https://idhub.pangea.org/vc_schemas/ebsi/attestation.json"
},
{
"properties": {
"credentialSubject": {
"description": "Defines additional properties on credentialSubject: the purchase act, to qualify as simplified invoice (ES)",
"type": "object",
"properties": {
"id": {
"description": "Defines a unique identifier (DID) of the credential subject: the purchase act/transaction",
"type": "string"
},
"invoiceNumber": {
"description": "The invoice number of the purchase act/transaction",
"type": "string"
},
"totalAmount": {
"description": "The total amount of the transaction in local currency units: Euro by default",
"type": "string"
},
"sellerId": {
"description": "Defines a unique identifier (DID) of the seller actor",
"type": "string"
},
"sellerBusinessName": {
"description": "Business name of the credential subject in the seller role",
"type": "string"
},
"sellerName": {
"description": "Name of the credential subject in the seller role",
"type": "string"
},
"sellerSurname": {
"description": "Surname of the credential subject in the seller role, if natural person",
"type": "string"
},
"sellerEmail": {
"type": "string",
"format": "email"
},
"sellerPhoneNumber": {
"type": "string"
},
"sellerIdentityDocType": {
"description": "Type of the Identity Document of the credential subject in the seller role",
"type": "string"
},
"sellerIdentityNumber": {
"description": "Number of the Identity Document of the credential subject in the seller role",
"type": "string"
},
"buyerId": {
"description": "Defines a unique identifier (DID) of the credential subject: the buyer actor",
"type": "string"
},
"buyerBusinessName": {
"description": "Business name of the credential subject in the buyer role",
"type": "string"
},
"buyerName": {
"description": "Name of the credential subject in the buyer role",
"type": "string"
},
"buyerSurname": {
"description": "Surname of the credential subject in the buyer role, if natural person",
"type": "string"
},
"buyerEmail": {
"type": "string",
"format": "email"
},
"buyerPhoneNumber": {
"type": "string"
},
"buyerIdentityDocType": {
"description": "Type of the Identity Document of the credential subject in the buyer role",
"type": "string"
},
"buyerIdentityNumber": {
"description": "Number of the Identity Document of the credential subject in the buyer role",
"type": "string"
},
"deliveryStreetAddress": {
"description": "Postal address of the credential Subject in the buyer role",
"type": "string"
},
"deliveryPostCode": {
"description": "Postal code of the credential Subject in the buyer role",
"type": "string"
},
"deliveryCity": {
"description": "City of the credential Subject in the buyer role",
"type": "string"
},
"supplyDescription": {
"description": "Description of the product/device supplied, needed in a simplified invoice",
"type": "string"
},
"taxRate": {
"description": "Description of Tax rate (VAT) and optionally also the expression VAT included, or special circumstances such as REBU, needed in a simplified invoice",
"type": "string"
},
"deviceChassisId": {
"description": "Chassis identifier of the device",
"type": "string"
},
"devicePreciseHardwareId": {
"description": "Chassis precise hardware configuration identifier of the device",
"type": "string"
},
"depositId": {
"description": "Identifier of an economic deposit left on loan to be returned under conditions",
"type": "string"
},
"sponsorId": {
"description": "Identifier of the sponsor of this purchase that paid the economic cost of the purchase",
"type": "string"
},
"sponsorName": {
"description": "Name of the sponsor of this purchase that paid the economic cost of the purchase",
"type": "string"
},
"purchaseDate": {
"type": "string",
"format": "date-time"
},
"invoiceDate": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"invoiceNumber",
"totalAmount",
"sellerId",
"sellerName",
"sellerBusinessName",
"sellerSurname",
"sellerEmail",
"sellerIdentityDocType",
"sellerIdentityNumber",
"buyerId",
"buyerEmail",
"supplyDescription",
"taxRate",
"deviceChassisId",
"purchaseDate"
]
}
}
}
]
}

View file

@ -1,5 +1,5 @@
{ {
"$id": "https://idhub.pangea.org/vc_schemas/eOperatorClaim.json", "$id": "https://idhub.pangea.org/vc_schemas/e-operator-claim.json",
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "EOperatorClaim", "title": "EOperatorClaim",
"description": "Product and waste electronics operator claim, as proposed by eReuse.org", "description": "Product and waste electronics operator claim, as proposed by eReuse.org",

View file

@ -1,5 +1,5 @@
{ {
"$id": "https://idhub.pangea.org/vc_schemas/federationMembership.json", "$id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Federation membership", "title": "Federation membership",
"description": "The federation membership specifies participation of a NGO into a NGO federation, as proposed by Lafede.cat", "description": "The federation membership specifies participation of a NGO into a NGO federation, as proposed by Lafede.cat",
@ -113,6 +113,7 @@
"membershipStatus", "membershipStatus",
"federation", "federation",
"membershipSince", "membershipSince",
"email",
"certificationDate" "certificationDate"
] ]
} }

View file

@ -31,7 +31,6 @@ SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool) DEBUG = config('DEBUG', default=False, cast=bool)
DEVELOPMENT = config('DEVELOPMENT', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv()) ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default='', cast=Csv()) CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default='', cast=Csv())
@ -226,3 +225,7 @@ LOGGING = {
ORGANIZATION = config('ORGANIZATION', 'Pangea') ORGANIZATION = config('ORGANIZATION', 'Pangea')
SYNC_ORG_DEV = config('SYNC_ORG_DEV', 'y') SYNC_ORG_DEV = config('SYNC_ORG_DEV', 'y')
ORG_FILE = config('ORG_FILE', 'examples/organizations.csv') ORG_FILE = config('ORG_FILE', 'examples/organizations.csv')
ENABLE_EMAIL = config('ENABLE_EMAIL', default=True, cast=bool)
CREATE_TEST_USERS = config('CREATE_TEST_USERS', default=False, cast=bool)
ENABLE_2FACTOR_AUTH = config('ENABLE_2FACTOR_AUTH', default=True, cast=bool)

View file

@ -1,15 +1,17 @@
import pandas as pd
import json import json
#import jsonld # import jsonld
import csv import csv
import sys import sys
import jsonschema import jsonschema
from pyld import jsonld # from jsonschema import validate, ValidationError
#from jsonschema import validate, ValidationError
import requests import requests
from pyld import jsonld from pyld import jsonld
import jsonref import jsonref
from jsonpath_ng import jsonpath, parse
#def remove_null_values(dictionary):
# def remove_null_values(dictionary):
# return {k: v for k, v in dictionary.items() if v is not None} # return {k: v for k, v in dictionary.items() if v is not None}
def _remove_null_values(dictionary): def _remove_null_values(dictionary):
@ -17,6 +19,7 @@ def _remove_null_values(dictionary):
dictionary.clear() dictionary.clear()
dictionary.update(filtered) dictionary.update(filtered)
def validate_context(jsld): def validate_context(jsld):
"""Validate a @context string through expanding""" """Validate a @context string through expanding"""
context = jsld["@context"] context = jsld["@context"]
@ -30,6 +33,7 @@ def validate_context(jsld):
return False return False
return True return True
def compact_js(doc, context): def compact_js(doc, context):
"""Validate a @context string through compacting, returns compacted context""" """Validate a @context string through compacting, returns compacted context"""
try: try:
@ -40,6 +44,7 @@ def compact_js(doc, context):
return None return None
return compacted return compacted
def dereference_context_file(json_file): def dereference_context_file(json_file):
"""Dereference and return json-ld context from file""" """Dereference and return json-ld context from file"""
json_text = open(json_file).read() json_text = open(json_file).read()
@ -76,22 +81,36 @@ def dereference_context(jsonld_dict):
def validate_schema_file(json_schema_file): def validate_schema_file(json_schema_file):
"""Validate standalone schema from file""" """Validate standalone schema from file"""
try: try:
json_schema = open(json_schema_file).read() json_schema = json.loads(open(json_schema_file).read())
validate_schema(json_schema) validate_schema(json_schema)
except Exception as e: except Exception as e:
print(f"Error loading file {json_schema_file} or validating schema {json_schema}: {e}") print(f"Error loading file {json_schema_file} or validating schema {json_schema}: {e}")
return False return False
return True return True
def validate_schema(json_schema): def validate_schema(json_schema):
"""Validate standalone schema, returns bool (uses Draft202012Validator, alt: Draft7Validator, alt: Draft4Validator, Draft6Validator )""" """Validate standalone schema, returns bool (uses Draft202012Validator, alt: Draft7Validator, alt: Draft4Validator, Draft6Validator )"""
try: try:
jsonschema.validators.Draft202012Validator.check_schema(json_schema) jsonschema.validators.Draft202012Validator.check_schema(json_schema)
# jsonschema.validators.Draft7Validator.check_schema(json_schema) # jsonschema.validators.Draft7Validator.check_schema(json_schema)
return True
except jsonschema.exceptions.SchemaError as e: except jsonschema.exceptions.SchemaError as e:
print(e) print(e)
return False return False
return True
def validate_json_file(json_data_file, json_schema_file):
"""Validate standalone schema from file"""
try:
json_data = json.loads(open(json_data_file).read())
json_schema = json.loads(open(json_schema_file).read())
validate_json(json_data, json_schema)
except Exception as e:
print(f"Error loading file {json_schema_file} or {json_data_file}: {e}")
return False
return True
def validate_json(json_data, json_schema): def validate_json(json_data, json_schema):
"""Validate json string basic (no format) with schema, returns bool""" """Validate json string basic (no format) with schema, returns bool"""
@ -100,8 +119,10 @@ def validate_json(json_data, json_schema):
except jsonschema.exceptions.ValidationError as err: except jsonschema.exceptions.ValidationError as err:
print('Validation error: ', json_data, '\n') print('Validation error: ', json_data, '\n')
return False return False
print("Successful validation")
return True return True
def validate_json_format(json_data, json_schema): def validate_json_format(json_data, json_schema):
"""Validate a json string basic (including format) with schema, returns bool""" """Validate a json string basic (including format) with schema, returns bool"""
try: try:
@ -111,9 +132,29 @@ def validate_json_format(json_data, json_schema):
return False return False
return True return True
def schema_to_csv_file(sch_f, csv_f):
try:
json_schema = json.loads(open(sch_f).read())
except Exception as e:
print(f"Error loading file {sch_f}: {e}\nSchema:\n{json_schema}.")
return False
schema_to_csv(json_schema, csv_f)
return True
def schema_to_csv(schema, csv_file_path): def schema_to_csv(schema, csv_file_path):
"""Extract headers from an schema and write to file, returns bool""" """Extract headers from an schema and write to file, returns bool"""
headers = list(schema['properties'].keys()) jsonpath_expr = parse('$..credentialSubject.properties')
# Use the JSONPath expression to select all properties under 'credentialSubject.properties'
matches = [match.value for match in jsonpath_expr.find(schema)]
# Get the keys of the matched objects
# headers = [match.keys() for match in matches]
# Use the JSONPath expression to select all properties under 'credentialSubject.properties'
# Get the keys of the matched objects
headers = [key for match in matches for key in match.keys()]
# print('\nHeaders: ', headers)
# Create a CSV file with the headers # Create a CSV file with the headers
with open(csv_file_path, 'w', newline='') as csv_file: with open(csv_file_path, 'w', newline='') as csv_file:
@ -121,6 +162,70 @@ def schema_to_csv(schema, csv_file_path):
writer.writerow(headers) writer.writerow(headers)
return True return True
def schema_to_xls_basic(schema, xls_file_path):
"""Extract headers from an schema and write to file, returns bool"""
jsonpath_expr = parse('$..credentialSubject.properties')
# Use the JSONPath expression to select all properties under 'credentialSubject.properties'
matches = [match.value for match in jsonpath_expr.find(schema)]
# Get the keys of the matched objects
# headers = [match.keys() for match in matches]
# Get the keys of the matched objects
headers = [key for match in matches for key in match.keys() if key != 'id']
# Create a DataFrame with the fields as columns
df = pd.DataFrame(columns=headers)
# Save the DataFrame as an Excel file
# df.to_excel(xls_file_path, index=False)
df.to_excel(xls_file_path, index=False, engine='openpyxl') # For .xlsx files, and pip install openpyxl
return True
def schema_to_xls_comment(schema, xls_file_path):
"""Extract headers from an schema and write to file, returns bool"""
jsonpath_expr = parse('$..credentialSubject.properties')
# Use the JSONPath expression to select all properties under 'credentialSubject.properties'
matches = [match.value for match in jsonpath_expr.find(schema)]
# Get the keys of the matched objects
# headers = [match.keys() for match in matches]
# Get the keys of the matched objects
headers = [key for match in matches for key in match.keys() if key != 'id']
jsonpath_expr_req = parse('$..credentialSubject.required')
req = [match.value for match in jsonpath_expr_req.find(schema)][0]
# Create a DataFrame with the fields as columns
df = pd.DataFrame(columns=headers)
writer = pd.ExcelWriter(xls_file_path, engine='xlsxwriter')
# Convert the dataframe to an xlsxwriter Excel object
df.to_excel(writer, sheet_name='Full1', index=False)
# Get the xlsxwriter workbook and worksheet objects
workbook = writer.book
worksheet = writer.sheets['Full1']
# Define a format for the required header cells
req_format = workbook.add_format({'border': 1})
# cell_format = workbook.add_format({'bold': True, 'font_color': 'red'})
# Write comments to the cells
for i, header in enumerate(headers):
if header in req:
worksheet.set_column(i,i, None, req_format)
# Get the description for the current field
if 'description' in matches[0][header]:
description = matches[0][header]['description']
if description is not None:
# Write the description as a comment to the corresponding cell
worksheet.write_comment(0, i, description)
# Close the Pandas Excel writer and output the Excel file
worksheet.autofit()
writer.close()
return True
def csv_to_json(csvFilePath, schema, jsonFilePath): def csv_to_json(csvFilePath, schema, jsonFilePath):
"""Read from a csv file, check schema, write to json file, returns bool""" """Read from a csv file, check schema, write to json file, returns bool"""
@ -144,6 +249,7 @@ def csv_to_json(csvFilePath, schema, jsonFilePath):
jsonf.write(jsonString) jsonf.write(jsonString)
return True return True
def csv_to_json2(csv_file_path, json_file_path): def csv_to_json2(csv_file_path, json_file_path):
"""Read from a csv file, write to json file (assumes a row 'No' is primary key), returns bool EXPERIMENT""" """Read from a csv file, write to json file (assumes a row 'No' is primary key), returns bool EXPERIMENT"""
# Create a dictionary # Create a dictionary
@ -164,11 +270,18 @@ def csv_to_json2(csv_file_path, json_file_path):
jsonf.write(json.dumps(data, indent=4)) jsonf.write(json.dumps(data, indent=4))
return True return True
if __name__ == "__main__": if __name__ == "__main__":
sch_name = sys.argv[1] # sch_name = sys.argv[1]
sch_file = sch_name + '-schema.json' schemas = sys.argv[1:]
sch = json.loads(open(sch_file).read())
if validate_json(d, sch): # credtools.py course-credential device-purchase e-operator-claim federation-membership financial-vulnerability membership-card
generate_csv_from_schema(sch, sch_name + '-template.csv') #sch_name = 'e-operator-claim'
for i, schema in enumerate(schemas):
print(schema)
sch = json.loads(open('vc_schemas/' + schema + '.json').read())
if schema_to_xls_comment(sch,'vc_excel/' + schema + '.xlsx'):
print('Success')
else: else:
print("Validation error: ", sch_name) print("Validation error: ", schema)