resolve conflict
This commit is contained in:
commit
a93563a5d0
|
@ -23,6 +23,33 @@ from idhub.models import (
|
|||
from idhub_auth.models import User
|
||||
|
||||
|
||||
class TermsConditionsForm(forms.Form):
|
||||
accept = forms.BooleanField(
|
||||
label=_("Accept terms and conditions of the service"),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
if data.get("accept"):
|
||||
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):
|
||||
did = forms.ChoiceField(label=_("Did"), choices=[])
|
||||
eidas1 = forms.ChoiceField(
|
||||
|
|
|
@ -9,7 +9,7 @@ from django_tables2 import SingleTableView
|
|||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.base import TemplateView, View
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
|
@ -29,6 +29,7 @@ from idhub.email.views import NotifyActivateUserByEmail
|
|||
from idhub.admin.forms import (
|
||||
ImportForm,
|
||||
MembershipForm,
|
||||
TermsConditionsForm,
|
||||
SchemaForm,
|
||||
UserRolForm,
|
||||
ImportCertificateForm,
|
||||
|
@ -49,6 +50,41 @@ from idhub.models import (
|
|||
)
|
||||
|
||||
|
||||
class TermsAndConditionsView(AdminView, FormView):
|
||||
template_name = "idhub/admin/terms_conditions.html"
|
||||
title = _("GDPR")
|
||||
section = ""
|
||||
subtitle = _('Accept Terms and Conditions')
|
||||
icon = 'bi bi-file-earmark-medical'
|
||||
form_class = TermsConditionsForm
|
||||
success_url = reverse_lazy('idhub:admin_dashboard')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['user'] = self.request.user
|
||||
kwargs['initial'] = {"accept": self.request.user.accept_gdpr}
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
user = form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DobleFactorAuthView(AdminView, View):
|
||||
url = reverse_lazy('idhub:admin_dashboard')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
if not self.request.session.get("2fauth"):
|
||||
return redirect(self.url)
|
||||
|
||||
if self.request.session.get("2fauth") == str(kwargs.get("admin2fauth")):
|
||||
self.request.session.pop("2fauth", None)
|
||||
return redirect(self.url)
|
||||
|
||||
return redirect(reverse_lazy("idhub:login"))
|
||||
|
||||
|
||||
class DashboardView(AdminView, SingleTableView):
|
||||
template_name = "idhub/admin/dashboard.html"
|
||||
table_class = DashboardTable
|
||||
|
@ -119,6 +155,7 @@ class PeopleView(People, TemplateView):
|
|||
class PeopleActivateView(PeopleView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
|
||||
|
@ -140,6 +177,7 @@ class PeopleActivateView(PeopleView):
|
|||
class PeopleDeleteView(PeopleView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
|
||||
|
@ -304,6 +342,7 @@ class PeopleMembershipDeleteView(PeopleView):
|
|||
model = Membership
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
|
||||
|
@ -391,6 +430,7 @@ class PeopleRolDeleteView(PeopleView):
|
|||
model = UserRol
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
user = self.object.user
|
||||
|
@ -454,6 +494,7 @@ class RolDeleteView(AccessControl):
|
|||
model = Rol
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
|
||||
|
@ -527,6 +568,7 @@ class ServiceDeleteView(AccessControl):
|
|||
model = Service
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
|
||||
|
@ -571,6 +613,7 @@ class CredentialView(Credentials):
|
|||
class CredentialJsonView(Credentials):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
pk = kwargs['pk']
|
||||
self.object = get_object_or_404(
|
||||
VerificableCredential,
|
||||
|
@ -585,6 +628,7 @@ class RevokeCredentialsView(Credentials):
|
|||
success_url = reverse_lazy('idhub:admin_credentials')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
pk = kwargs['pk']
|
||||
self.object = get_object_or_404(
|
||||
VerificableCredential,
|
||||
|
@ -604,6 +648,7 @@ class DeleteCredentialsView(Credentials):
|
|||
success_url = reverse_lazy('idhub:admin_credentials')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
pk = kwargs['pk']
|
||||
self.object = get_object_or_404(
|
||||
VerificableCredential,
|
||||
|
@ -683,6 +728,7 @@ class DidDeleteView(Credentials, DeleteView):
|
|||
success_url = reverse_lazy('idhub:admin_dids')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(self.model, pk=self.pk)
|
||||
Event.set_EV_ORG_DID_DELETED_BY_ADMIN(self.object)
|
||||
|
@ -737,6 +783,7 @@ class SchemasView(SchemasMix):
|
|||
class SchemasDeleteView(SchemasMix):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(Schemas, pk=self.pk)
|
||||
self.object.delete()
|
||||
|
@ -747,6 +794,7 @@ class SchemasDeleteView(SchemasMix):
|
|||
class SchemasDownloadView(SchemasMix):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
self.pk = kwargs['pk']
|
||||
self.object = get_object_or_404(Schemas, pk=self.pk)
|
||||
|
||||
|
@ -825,6 +873,7 @@ class SchemasImportView(SchemasMix):
|
|||
class SchemasImportAddView(SchemasMix):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
file_name = kwargs['file_schema']
|
||||
schemas_files = os.listdir(settings.SCHEMAS_DIR)
|
||||
if not file_name in schemas_files:
|
||||
|
|
|
@ -13,7 +13,11 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class NotifyActivateUserByEmail:
|
||||
def get_email_context(self, user):
|
||||
subject_template_name = 'idhub/admin/registration/activate_user_subject.txt'
|
||||
email_template_name = 'idhub/admin/registration/activate_user_email.txt'
|
||||
html_email_template_name = 'idhub/admin/registration/activate_user_email.html'
|
||||
|
||||
def get_email_context(self, user, token):
|
||||
"""
|
||||
Define a new context with a token for put in a email
|
||||
when send a email for add a new password
|
||||
|
@ -22,35 +26,35 @@ class NotifyActivateUserByEmail:
|
|||
current_site = get_current_site(self.request)
|
||||
site_name = current_site.name
|
||||
domain = current_site.domain
|
||||
if not token:
|
||||
token = default_token_generator.make_token(user)
|
||||
|
||||
context = {
|
||||
'email': user.email,
|
||||
'domain': domain,
|
||||
'site_name': site_name,
|
||||
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
||||
'user': user,
|
||||
'token': default_token_generator.make_token(user),
|
||||
'token': token,
|
||||
'protocol': protocol,
|
||||
}
|
||||
return context
|
||||
|
||||
def send_email(self, user):
|
||||
def send_email(self, user, token=None):
|
||||
"""
|
||||
Send a email when a user is activated.
|
||||
"""
|
||||
context = self.get_email_context(user)
|
||||
subject_template_name = 'idhub/admin/registration/activate_user_subject.txt'
|
||||
email_template_name = 'idhub/admin/registration/activate_user_email.txt'
|
||||
html_email_template_name = 'idhub/admin/registration/activate_user_email.html'
|
||||
subject = loader.render_to_string(subject_template_name, context)
|
||||
context = self.get_email_context(user, token)
|
||||
subject = loader.render_to_string(self.subject_template_name, context)
|
||||
# Email subject *must not* contain newlines
|
||||
subject = ''.join(subject.splitlines())
|
||||
body = loader.render_to_string(email_template_name, context)
|
||||
body = loader.render_to_string(self.email_template_name, context)
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
to_email = user.email
|
||||
|
||||
email_message = EmailMultiAlternatives(
|
||||
subject, body, from_email, [to_email])
|
||||
html_email = loader.render_to_string(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')
|
||||
try:
|
||||
if settings.DEVELOPMENT:
|
||||
|
|
|
@ -1,367 +0,0 @@
|
|||
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DID',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'type',
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[(1, 'Key'), (2, 'Web')], verbose_name='Type'
|
||||
),
|
||||
),
|
||||
('created_at', models.DateTimeField(auto_now=True)),
|
||||
('label', models.CharField(max_length=50, verbose_name='Label')),
|
||||
('did', models.CharField(max_length=250)),
|
||||
('key_material', models.TextField()),
|
||||
('eidas1', models.BooleanField(default=False)),
|
||||
('didweb_document', models.TextField()),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='dids',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='File_datas',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('file_name', models.CharField(max_length=250)),
|
||||
('success', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Rol',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=250, verbose_name='name')),
|
||||
(
|
||||
'description',
|
||||
models.CharField(
|
||||
max_length=250, null=True, verbose_name='Description'
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Schemas',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
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)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Service',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('domain', models.CharField(max_length=250, verbose_name='Domain')),
|
||||
(
|
||||
'description',
|
||||
models.CharField(max_length=250, verbose_name='Description'),
|
||||
),
|
||||
('rol', models.ManyToManyField(to='idhub.rol')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VCTemplate',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('wkit_template_id', models.CharField(max_length=250)),
|
||||
('data', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VerificableCredential',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('id_string', models.CharField(max_length=250)),
|
||||
('verified', models.BooleanField()),
|
||||
('created_on', models.DateTimeField(auto_now=True)),
|
||||
('issued_on', models.DateTimeField(null=True)),
|
||||
('data', models.TextField()),
|
||||
('csv_data', models.TextField()),
|
||||
('hash', models.CharField(max_length=260)),
|
||||
(
|
||||
'status',
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, 'Enabled'),
|
||||
(2, 'Issued'),
|
||||
(3, 'Revoked'),
|
||||
(4, 'Expired'),
|
||||
],
|
||||
default=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
'eidas1_did',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='idhub.did',
|
||||
),
|
||||
),
|
||||
(
|
||||
'issuer_did',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='vcredentials',
|
||||
to='idhub.did',
|
||||
),
|
||||
),
|
||||
(
|
||||
'schema',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='vcredentials',
|
||||
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(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='vcredentials',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Membership',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'type',
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[(1, 'Beneficiary'), (2, 'Employee'), (3, 'Member')],
|
||||
verbose_name='Type of membership',
|
||||
),
|
||||
),
|
||||
(
|
||||
'start_date',
|
||||
models.DateField(
|
||||
blank=True,
|
||||
help_text='What date did the membership start?',
|
||||
null=True,
|
||||
verbose_name='Start date',
|
||||
),
|
||||
),
|
||||
(
|
||||
'end_date',
|
||||
models.DateField(
|
||||
blank=True,
|
||||
help_text='What date will the membership end?',
|
||||
null=True,
|
||||
verbose_name='End date',
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='memberships',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('created', models.DateTimeField(auto_now=True, verbose_name='Date')),
|
||||
(
|
||||
'message',
|
||||
models.CharField(max_length=350, verbose_name='Description'),
|
||||
),
|
||||
(
|
||||
'type',
|
||||
models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, 'User registered'),
|
||||
(2, 'User welcomed'),
|
||||
(3, 'Data update requested by user'),
|
||||
(
|
||||
4,
|
||||
'Data update requested. Pending approval by administrator',
|
||||
),
|
||||
(5, "User's data updated by admin"),
|
||||
(6, 'Your data updated by admin'),
|
||||
(7, 'User deactivated by admin'),
|
||||
(8, 'DID created by user'),
|
||||
(9, 'DID created'),
|
||||
(10, 'DID deleted'),
|
||||
(11, 'Credential deleted by user'),
|
||||
(12, 'Credential deleted'),
|
||||
(13, 'Credential issued for user'),
|
||||
(14, 'Credential issued'),
|
||||
(15, 'Credential presented by user'),
|
||||
(16, 'Credential presented'),
|
||||
(17, 'Credential enabled'),
|
||||
(18, 'Credential available'),
|
||||
(19, 'Credential revoked by admin'),
|
||||
(20, 'Credential revoked'),
|
||||
(21, 'Role created by admin'),
|
||||
(22, 'Role modified by admin'),
|
||||
(23, 'Role deleted by admin'),
|
||||
(24, 'Service created by admin'),
|
||||
(25, 'Service modified by admin'),
|
||||
(26, 'Service deleted by admin'),
|
||||
(27, 'Organisational DID created by admin'),
|
||||
(28, 'Organisational DID deleted by admin'),
|
||||
(29, 'User deactivated'),
|
||||
(30, 'User activated'),
|
||||
],
|
||||
verbose_name='Event',
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='events',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserRol',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'service',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='users',
|
||||
to='idhub.service',
|
||||
verbose_name='Service',
|
||||
),
|
||||
),
|
||||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='roles',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'service')},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,14 +1,48 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import reverse_lazy, resolve
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.urls import reverse_lazy, resolve
|
||||
from django.shortcuts import redirect
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class Http403(PermissionDenied):
|
||||
status_code = 403
|
||||
default_detail = _('Permission denied. User is not authenticated')
|
||||
default_code = 'forbidden'
|
||||
|
||||
def __init__(self, detail=None, code=None):
|
||||
if detail is not None:
|
||||
self.detail = details or self.default_details
|
||||
if code is not None:
|
||||
self.code = code or self.default_code
|
||||
|
||||
|
||||
class UserView(LoginRequiredMixin):
|
||||
login_url = "/login/"
|
||||
wallet = False
|
||||
path_terms = [
|
||||
'admin_terms_and_conditions',
|
||||
'user_terms_and_conditions',
|
||||
'user_gdpr',
|
||||
]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response = super().get(request, *args, **kwargs)
|
||||
url = self.check_gdpr()
|
||||
if url:
|
||||
return url
|
||||
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
response = super().post(request, *args, **kwargs)
|
||||
url = self.check_gdpr()
|
||||
if url:
|
||||
return url
|
||||
|
||||
return response
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.admin_validated = cache.get("KEY_DIDS")
|
||||
|
@ -32,12 +66,29 @@ class UserView(LoginRequiredMixin):
|
|||
})
|
||||
return context
|
||||
|
||||
def check_gdpr(self):
|
||||
if not self.request.user.accept_gdpr:
|
||||
url = reverse_lazy("idhub:user_terms_and_conditions")
|
||||
if self.request.user.is_admin:
|
||||
url = reverse_lazy("idhub:admin_terms_and_conditions")
|
||||
if resolve(self.request.path).url_name not in self.path_terms:
|
||||
return redirect(url)
|
||||
|
||||
|
||||
class AdminView(UserView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.user.is_admin:
|
||||
url = reverse_lazy('idhub:user_dashboard')
|
||||
return redirect(url)
|
||||
|
||||
self.check_valid_user()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.check_valid_user()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def check_valid_user(self):
|
||||
if not self.request.user.is_admin:
|
||||
raise Http403()
|
||||
|
||||
if self.request.session.get("2fauth"):
|
||||
raise Http403()
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "auth/login_base.html" %}
|
||||
{% load i18n django_bootstrap5 %}
|
||||
|
||||
{% block login_content %}
|
||||
|
||||
<div class="well">
|
||||
<div class="row-fluid">
|
||||
<h2>{% trans 'Doble Factor of Authentication' %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="well">
|
||||
<div class="row-fluid">
|
||||
<div>
|
||||
<span>{% trans "We have sent an email with a link that you have to select in order to login." %}</span>
|
||||
</div>
|
||||
</div><!-- /.row-fluid -->
|
||||
</div><!--/.well-->
|
||||
{% endblock %}
|
|
@ -0,0 +1,26 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
<p>
|
||||
{% blocktrans %}You're receiving this email because you try to access in {{ site_name }}.{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% trans "Please go to the following page" %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% block reset_link %}
|
||||
<a href="{{ protocol }}://{{ domain }}{% url 'idhub:admin_2fauth' admin2fauth=token %}">
|
||||
{{ protocol }}://{{ domain }}{% url 'idhub:admin_2fauth' admin2fauth=token %}
|
||||
</a>
|
||||
{% endblock %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% trans "Thanks for using our site!" %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% endautoescape %}
|
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this email because you try to access in {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'idhub:admin_2fauth' admin2fauth=token %}
|
||||
{% endblock %}
|
||||
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||
|
||||
{% endautoescape %}
|
|
@ -0,0 +1,3 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}Authentication in {{ site_name }}{% endblocktrans %}
|
||||
{% endautoescape %}
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "idhub/base_admin.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h3>
|
||||
<i class="{{ icon }}"></i>
|
||||
{{ subtitle }}
|
||||
</h3>
|
||||
{% load django_bootstrap5 %}
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||
<div class="message">
|
||||
{% for field, error in form.errors.items %}
|
||||
{{ error }}<br />
|
||||
{% endfor %}
|
||||
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
You must read the terms and conditions of this service and accept the
|
||||
<a class="btn btn-green-admin" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#gdpr" title="{% trans 'GDPR' %}">Read GDPR</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_form form %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions-no-box">
|
||||
<a class="btn btn-grey" href="{% url 'idhub:admin_dashboard' %}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{% trans 'GDPR info' %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Here we write the info about GDPR</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -140,9 +140,6 @@
|
|||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">{{ title }}</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<input class="form-control form-control-grey " type="text" placeholder="{% trans 'Search' %}" aria-label="Search">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -170,9 +170,6 @@
|
|||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">{{ title }}</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<input class="form-control form-control-grey " type="text" placeholder="{% trans 'Search' %}" aria-label="Search">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,4 +6,7 @@
|
|||
<i class="{{ icon }}"></i>
|
||||
{{ subtitle }}
|
||||
</h3>
|
||||
Gdpr info<br/>
|
||||
If you want accept or revoke the Gdpr go to:
|
||||
<a class="btn btn-green-user" href="{% url 'idhub:user_terms_and_conditions' %}">Terms and conditions</a>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "idhub/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h3>
|
||||
<i class="{{ icon }}"></i>
|
||||
{{ subtitle }}
|
||||
</h3>
|
||||
{% load django_bootstrap5 %}
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||
<div class="message">
|
||||
{% for field, error in form.errors.items %}
|
||||
{{ error }}<br />
|
||||
{% endfor %}
|
||||
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
You must read the terms and conditions of this service and accept the
|
||||
<a class="btn btn-green-user" href="jacascript:void()" data-bs-toggle="modal" data-bs-target="#gdpr" title="{% trans 'GDPR' %}">Read GDPR</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_form form %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions-no-box">
|
||||
<a class="btn btn-grey" href="{% url 'idhub:user_dashboard' %}">{% translate "Cancel" %}</a>
|
||||
<input class="btn btn-green-user" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="gdpr" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{% trans 'GDPR info' %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Here we write the info about GDPR</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans with address_name=object.full_address_name %}Are you sure that you want remove the address: "{{ address_name }}"?{% endblocktrans %}</p>
|
||||
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
|
||||
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
|
||||
<a class="btn btn-secondary" href="{% url 'musician:address-update' view.kwargs.pk %}">{% trans 'Cancel' %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,20 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:address-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% if form.instance %}
|
||||
<div class="float-right">
|
||||
<a class="btn btn-danger" href="{% url 'musician:address-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,41 +0,0 @@
|
|||
{% extends "musician/mail_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
<div class="tab-pane fade show active" id="addresses" role="tabpanel" aria-labelledby="addresses-tab">
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Email" %}</th>
|
||||
<th scope="col">{% trans "Domain" %}</th>
|
||||
<th scope="col">{% trans "Mailboxes" %}</th>
|
||||
<th scope="col">{% trans "Forward" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in object_list %}
|
||||
<tr>
|
||||
<td><a href="{% url 'musician:address-update' obj.id %}">{{ obj.full_address_name }}</a></td>
|
||||
<td>{{ obj.domain.name }}</td>
|
||||
<td>
|
||||
{% for mailbox in obj.mailboxes %}
|
||||
<a href="{% url 'musician:mailbox-update' mailbox.id %}">{{ mailbox.name }}</a>
|
||||
{% if not forloop.last %}<br/> {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ obj.forward }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:address-create' %}">{% trans "New mail address" %}</a>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,41 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n l10n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="service-name">{% trans "Billing" %}</h1>
|
||||
<p class="service-description">{% trans "Billing page description." %}</p>
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 40%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Number" %}</th>
|
||||
<th scope="col">{% trans "Bill date" %}</th>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Total" %}</th>
|
||||
<th scope="col">{% trans "Download PDF" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bill in object_list %}
|
||||
<tr>
|
||||
<th scope="row">{{ bill.number }}</th>
|
||||
<td>{{ bill.created_on|date:"SHORT_DATE_FORMAT" }}</td>
|
||||
<td>{{ bill.type }}</td>
|
||||
<td>{{ bill.total|floatformat:2|localize }}€</td>
|
||||
<td><a class="text-dark" href="{% url 'musician:bill-download' bill.id %}" target="_blank" rel="noopener noreferrer"><i class="fas fa-file-pdf"></i></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{# TODO: define proper colspan #}
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -1,29 +0,0 @@
|
|||
{# <!-- paginator component --> #}
|
||||
<div class="row object-list-paginator">
|
||||
<div class="col-md-4">{{ page_obj.paginator.count }} items in total</div>
|
||||
<div class="col-md-4 text-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page=1&per_page={{ page_obj.paginator.per_page }}">«</a>
|
||||
<a href="?page={{ page_obj.previous_page_number }}&per_page={{ page_obj.paginator.per_page }}">‹</a>
|
||||
{% endif %}
|
||||
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}&per_page={{ page_obj.paginator.per_page }}">›</a>
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}&per_page={{ page_obj.paginator.per_page }}">»</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<form method="get">
|
||||
Showing
|
||||
<select name="{{ per_page_param }}">
|
||||
{% for value in per_page_values %}
|
||||
{% with page_obj.paginator.per_page as per_page %}
|
||||
<option value="{{ value }}" {% if value == per_page %}selected{% endif %}>{{ value }}</option>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
per page
|
||||
<input type="submit" value="apply" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -1,50 +0,0 @@
|
|||
{# <!-- table footer based paginator for ListView --> #}
|
||||
{% load i18n %}
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2">{{ page_obj.paginator.count }} items in total</td>
|
||||
<td class="text-center">
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {{ page_obj.has_previous|yesno:',disabled' }}">
|
||||
<a class="page-link" {% if page_obj.has_previous %}
|
||||
href="?page={{ page_obj.previous_page_number }}&per_page={{ page_obj.paginator.per_page }}"
|
||||
{% else %} href="#" {% endif %} tabindex="-1">
|
||||
<span aria-hidden="true">‹</span>
|
||||
<span class="sr-only">{% trans "Previous" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% for page_number in page_obj.paginator.page_range %}
|
||||
<li class="page-item {% if page_number == page_obj.number %}active {% endif %}">
|
||||
<a class="page-link"
|
||||
href="?page={{ page_number }}&per_page={{ page_obj.paginator.per_page }}">{{ page_number }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="page-item {{ page_obj.has_next|yesno:',disabled' }}">
|
||||
<a class="page-link" {% if page_obj.has_next %}
|
||||
href="?page={{ page_obj.next_page_number }}&per_page={{ page_obj.paginator.per_page }}"
|
||||
{% else %} href="#" {% endif %}>
|
||||
<span aria-hidden="true">›</span>
|
||||
<span class="sr-only">{% trans "Next" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</td>
|
||||
<td colspan="2" class="text-right">
|
||||
<form method="get">
|
||||
Showing
|
||||
<select name="{{ per_page_param }}">
|
||||
{% for value in per_page_values %}
|
||||
{% with page_obj.paginator.per_page as per_page %}
|
||||
<option value="{{ value }}" {% if value == per_page %}selected{% endif %}>{{ value }}</option>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
per page
|
||||
<input type="submit" value="apply" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
|
@ -1,22 +0,0 @@
|
|||
{% comment %}
|
||||
Resource usage rendered as bootstrap progress bar
|
||||
|
||||
Expected parameter: detail
|
||||
Expected structure: dictionary or object with attributes:
|
||||
- usage (int): 125
|
||||
- total (int): 200
|
||||
- unit (string): 'MB'
|
||||
- percent (int: [0, 25, 50, 75, 100]: 75
|
||||
{% endcomment %}
|
||||
|
||||
<div class="text-center">
|
||||
{% if detail %}
|
||||
{{ detail.usage }} {{ detail.unit }}
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-secondary w-{{ detail.percent }}" role="progressbar" aria-valuenow="{{ detail.usage }}"
|
||||
aria-valuemin="0" aria-valuemax="{{ detail.total }}"></div>
|
||||
</div>
|
|
@ -1,161 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2 style="margin-top: 10px;">{% trans "Welcome back" %} <strong>{{ profile.username }}</strong></h2>
|
||||
{% if profile.last_login %}
|
||||
<p>{% blocktrans with last_login=profile.last_login|date:"SHORT_DATE_FORMAT" %}Last time you logged in was: {{ last_login }}{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p>{% trans "It's the first time you log into the system, welcome on board!" %}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-deck">
|
||||
{% for resource, usage in resource_usage.items %}
|
||||
<div class="card resource-usage resource-{{ resource }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ usage.verbose_name }}</h5>
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=usage.data %}
|
||||
{% if usage.data.alert %}
|
||||
<div class="text-center mt-4">
|
||||
{{ usage.data.alert }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="card resource-usage resource-notifications">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans "Notifications" %}</h5>
|
||||
{% for message in notifications %}
|
||||
<p class="card-text">{{ message }}</p>
|
||||
{% empty %}
|
||||
<p class="card-text">{% trans "There is no notifications at this time." %}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="service-name">{% trans "Your domains and websites" %}</h1>
|
||||
<p class="service-description">{% trans "Dashboard page description." %}</p>
|
||||
|
||||
{% for domain in domains %}
|
||||
<div class="card service-card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<strong>{{ domain.name }}</strong>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% with domain.websites.0 as website %}
|
||||
{% with website.contents.0 as content %}
|
||||
<button type="button" class="btn text-secondary" data-toggle="modal" data-target="#configDetailsModal"
|
||||
data-domain="{{ domain.name }}" data-website="{{ website|yesno:'true,false' }}" data-webapp-type="{{ content.webapp.type }}" data-root-path="{{ content.path }}"
|
||||
data-url="{% url 'musician:domain-detail' domain.id %}">
|
||||
{% trans "view configuration" %} <strong class="fas fa-tools"></strong>
|
||||
</button>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="col-md text-right">
|
||||
{% comment "@slamora: orchestra doesn't have this information [won't fix] See issue #2" %}
|
||||
{% trans "Expiration date" %}: <strong>{{ domain.expiration_date|date:"SHORT_DATE_FORMAT" }}</strong>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /card-header-->
|
||||
<div class="card-body row text-center">
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Mail" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-envelope fa-3x"></i></p>
|
||||
<p class="card-text text-dark">
|
||||
{{ domain.addresses|length }} {% trans "mail addresses created" %}
|
||||
</p>
|
||||
<a class="stretched-link" href="{% url 'musician:address-list' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Mail list" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-mail-bulk fa-3x"></i></p>
|
||||
<a class="stretched-link" href="{% url 'musician:mailing-lists' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Software as a Service" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-fire fa-3x"></i></p>
|
||||
<p class="card-text text-dark">{% trans "Nothing installed" %}</p>
|
||||
<a class="stretched-link" href="{% url 'musician:saas-list' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="d-none d-lg-block col-lg-1"></div>
|
||||
<div class="col-6 col-md-3 col-lg-4">
|
||||
<h4>{% trans "Disk usage" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-hdd fa-3x"></i></p>
|
||||
<div class="w-75 m-auto">
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=domain.usage %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-lg-block col-lg-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
<!-- configuration details modal -->
|
||||
<div class="modal fade" id="configDetailsModal" tabindex="-1" role="dialog" aria-labelledby="configDetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-secondary" id="configDetailsModalLabel">{% trans "Configuration details" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="domain-ftp pb-3 border-bottom">
|
||||
<h6 class="pl-4 mb-4">{% trans "FTP access:" %}</h6>
|
||||
{# Translators: domain configuration detail modal #}
|
||||
<p>{% trans "Contact with the support team to get details concerning FTP access." %}</p>
|
||||
{% comment %}
|
||||
<!-- hidden until API provides FTP information -->
|
||||
<label>{% trans "Username" %}:</label> <span id="config-username" class="font-weight-bold">username</span><br/>
|
||||
<label>{% trans "Password:" %}</label> <span id="config-password" class="font-weight-bold">password</span>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
<div class="domain-website pt-4">
|
||||
<div id="no-website"><h6 class="pl-4">{% trans "No website configured." %}</h6></div>
|
||||
<div id="config-website">
|
||||
<label>{% trans "Root directory:" %}</label> <span id="config-root-path" class="font-weight-bold">root directory</span>
|
||||
<label>{% trans "Type:" %}</label><span id="config-webapp-type" class="font-weight-bold">type</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#domain-detail" class="btn btn-primary">{% trans "View DNS records" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extrascript %}
|
||||
<script>
|
||||
$('#configDetailsModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget); // Button that triggered the modal
|
||||
var modal = $(this);
|
||||
|
||||
// Extract info from data-* attributes
|
||||
modal.find('.modal-title').text(button.data('domain'));
|
||||
modal.find('.modal-body #config-webapp-type').text(button.data('webapp-type'));
|
||||
modal.find('.modal-body #config-root-path').text(button.data('root-path'));
|
||||
modal.find('.modal-footer .btn').attr('href', button.data('url'));
|
||||
|
||||
var nowebsite = modal.find('.modal-body #no-website');
|
||||
var websitecfg = modal.find('.modal-body #config-website');
|
||||
if(button.data('website')) {
|
||||
nowebsite.hide();
|
||||
websitecfg.show();
|
||||
} else {
|
||||
nowebsite.show();
|
||||
websitecfg.hide();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,68 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
{% for database in object_list %}
|
||||
<div class="card service-card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<strong>{{ database.name }}</strong>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
{% trans "Type" %}: <strong>{{ database.type }}</strong>
|
||||
</div>
|
||||
<div class="col-md text-right">
|
||||
{% comment "@slamora: orchestra doesn't provide this information [won't fix] See issue #3" %}
|
||||
{% trans "associated to" %}: <strong>{{ database.domain|default:"-" }}</strong>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /card-header-->
|
||||
<div class="card-body row">
|
||||
<div class="col-md-4">
|
||||
<h4>Database users</h4>
|
||||
<ul class="list-unstyled pl-2">
|
||||
{% for user in database.users %}
|
||||
{# TODO(@slamora) render in two columns #}
|
||||
<li><span class="d-inline-block w-25">{{ user.username }}</span> <i class="fas fa-user-edit"></i></li>
|
||||
{% empty %}
|
||||
<li>{% trans "No users for this database." %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3 border-left border-right">
|
||||
<h4>Database usage</h4>
|
||||
<p class="text-center"><i class="fas fa-database fa-3x"></i></p>
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=database.usage %}
|
||||
</div>
|
||||
<div class="col-md-5 text-right">
|
||||
<div class="service-manager-link">
|
||||
<a class="btn btn-primary" href="{{ database.manager_url }}" target="_blank" rel="noopener noreferrer">{% trans "Open database manager" %} <i class="fas fa-external-link-alt"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% empty %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card service-card shadow p-3 mb-5 bg-white rounded">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-4"><i class="fas fa-database fa-5x"></i></p>
|
||||
{# Translators: database page when there isn't any database. #}
|
||||
<h5 class="card-title text-dark">{% trans "Ooops! Looks like there is nothing here!" %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if object_list|length > 0 %}
|
||||
{% include "musician/components/paginator.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,30 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:dashboard' %}">{% trans "Go back" %}</a>
|
||||
|
||||
<h1 class="service-name">{% trans "DNS settings for" %} <span class="font-weight-light">{{ object.name }}</span></h1>
|
||||
<p class="service-description">{% trans "DNS settings page description." %}</p>
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 12%;">
|
||||
<col span="1" style="width: 88%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Value" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in object.records %}
|
||||
<tr>
|
||||
<td>{{ record.type }}</td>
|
||||
<td>{{ record.value }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -1,32 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if active_domain %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:address-list' %}">{% trans "Go to global" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}
|
||||
{% if active_domain %}<span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}
|
||||
</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
{% with request.resolver_match.url_name as url_name %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'address-list' %}active{% endif %}" href="{% url 'musician:address-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'address-list' %}true{% else %}false{% endif %}">{% trans "Addresses" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'mailbox-list' %}active{% endif %}" href="{% url 'musician:mailbox-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'mailbox-list' %}true{% else %}false{% endif %}">{% trans "Mailboxes" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% endwith %}
|
||||
|
||||
<div class="tab-content" id="myTabContent">
|
||||
{% block tabcontent %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,15 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:mailbox-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,15 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans with name=object.name %}Are you sure that you want remove the mailbox: "{{ name }}"?{% endblocktrans %}</p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% trans "All mailbox's messages will be <strong>deleted and cannot be recovered</strong>." %}
|
||||
</div>
|
||||
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
|
||||
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
|
||||
<a class="btn btn-secondary" href="{% url 'musician:mailbox-list' %}">{% trans 'Cancel' %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,30 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
|
||||
{% if extra_mailbox %}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
<strong>{% trans "Warning!" %}</strong> {% trans "You have reached the limit of mailboxes of your subscription so <strong>extra fees</strong> may apply." %}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="{% trans 'Close' %}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:mailbox-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% if form.instance %}
|
||||
<div class="float-right">
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:mailbox-password' view.kwargs.pk %}"><i class="fas fa-key"></i> {% trans "Change password" %}</a>
|
||||
<a class="btn btn-danger" href="{% url 'musician:mailbox-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||
{% extends "musician/mail_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
<div class="tab-pane fade show active" id="mailboxes" role="tabpanel" aria-labelledby="mailboxes-tab">
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
<col span="1" style="width: 65%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Filtering" %}</th>
|
||||
<th scope="col">{% trans "Addresses" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mailbox in object_list %}
|
||||
{# <!-- Exclude (don't render) inactive mailboxes -->#}
|
||||
{% if mailbox.is_active %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'musician:mailbox-update' mailbox.id %}">{{ mailbox.name }}</a>
|
||||
<a class="roll-hover btn btn-outline-warning" href="{% url 'musician:mailbox-password' mailbox.id %}">
|
||||
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
|
||||
</td>
|
||||
<td>{{ mailbox.filtering }}</td>
|
||||
<td>
|
||||
{% for addr in mailbox.addresses %}
|
||||
<a href="{% url 'musician:address-update' addr.data.id %}">
|
||||
{{ addr.full_address_name }}
|
||||
</a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}{# <!-- /is_active --> #}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:mailbox-create' %}">{% trans "New mailbox" %}</a>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if active_domain %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:mailing-lists' %}">{% trans "Go to global" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}{% if active_domain %} <span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 13%;">
|
||||
<col span="1" style="width: 12%;">
|
||||
<col span="1" style="width: 50%;">
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Address</th>
|
||||
<th scope="col">Admin email</th>
|
||||
<th scope="col">Configure</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for resource in object_list %}
|
||||
<tr>
|
||||
<th scope="row">{{ resource.name }}</th>
|
||||
{% if resource.is_active %}
|
||||
<td class="text-primary font-weight-bold">{% trans "Active" %}</td>
|
||||
{% else %}
|
||||
<td class="text-danger font-weight-bold">{% trans "Inactive" %}</td>
|
||||
{% endif %}
|
||||
<td>{{ resource.address_name}}</td>
|
||||
<td>{{ resource.admin_email }}</td>
|
||||
<td><a href="{{ resource.manager_url }}" target="_blank" rel="noopener noreferrer">Mailtrain <i class="fas fa-external-link-alt"></i></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -1,65 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="service-name">{% trans "Profile" %}</h1>
|
||||
<p class="service-description">{% trans "Little description on profile page." %}</p>
|
||||
|
||||
<div class="card-deck">
|
||||
<div class="card card-profile">
|
||||
<h5 class="card-header">{% trans "User information" %}</h5>
|
||||
<div class="card-body row">
|
||||
<div class="col-md">
|
||||
<div class="border-primary rounded-circle d-inline-block p-1" style="background-color: white; border: 5px solid grey">
|
||||
<img id="user-avatar" width="160" height="160" src="/static/musician/images/default-profile-picture-primary-color.png" alt="user-profile-picture">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<p class="card-text">{{ profile.username }}</p>
|
||||
<p class="card-text">{{ profile.type }}</p>
|
||||
<p class="card-text">{% trans "Preferred language:" %} {{ profile.language|language_name_local }}</p>
|
||||
</div>
|
||||
{% comment %}
|
||||
<!-- disabled until set_password is implemented -->
|
||||
<div class="col-md-12 text-right">
|
||||
<a class="btn btn-primary pl-5 pr-5" href="#">{% trans "Set new password" %}</a>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with profile.billing as contact %}
|
||||
<div class="card card-profile">
|
||||
<h5 class="card-header">{% trans "Billing information" %}</h5>
|
||||
<div class="card-body">
|
||||
<div class="form-group">{{ contact.name }}</div>
|
||||
<div class="form-group">{{ contact.address }}</div>
|
||||
<div class="form-group">
|
||||
{{ contact.zipcode }}
|
||||
{{ contact.city }}
|
||||
{{ contact.country }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ contact.vat }}
|
||||
</div>
|
||||
<!-- payment method -->
|
||||
<div class="form-group">
|
||||
{% trans "payment method:" %} {{ payment.method }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{% if payment.method == 'SEPADirectDebit' %}
|
||||
IBAN {{ payment.data.iban }}
|
||||
{% else %}
|
||||
{# <!-- "TODO handle Credit Card" --> #}
|
||||
Details: {{ payment.data }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<a href="{% url 'musician:billing' %}">{% trans "Check your last bills" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
|
@ -1,56 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
{% for saas in object_list %}
|
||||
<div class="card service-card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<strong>{{ saas.name }}</strong>
|
||||
</div>
|
||||
{% comment "Hidden until API provides this information" %}
|
||||
<div class="col-md text-right">
|
||||
{% trans "Installed on" %}: <strong>{{ saas.domain|default:"-" }}</strong>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
</div><!-- /card-header-->
|
||||
<div class="card-body row">
|
||||
<div class="col-md-4">
|
||||
<h4>{{ saas.service|capfirst }}</h4>
|
||||
<p class="text-center service-brand"><i class="fab fa-{{ saas.service }} fa-10x"></i></p>
|
||||
</div>
|
||||
<div class="col-md-3 border-left border-right">
|
||||
<h4 class="mb-3">{% trans "Service info" %}</h4>
|
||||
<label class="w-25">{% trans "active" %}:</label> <strong>{{ saas.is_active|yesno }}</strong><br/>
|
||||
{% for key, value in saas.data.items %}
|
||||
<label class="w-25">{{ key }}:</label> <strong>{{ value }}</strong><br/>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-5 text-right">
|
||||
<div class="service-manager-link">
|
||||
<a class="btn btn-primary" href="{{ saas.manager_url }}" target="_blank" rel="noopener noreferrer">{% trans "Open service admin panel" %} <i class="fas fa-external-link-alt"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card service-card shadow p-3 mb-5 bg-white rounded">
|
||||
<div class="card-body text-center">
|
||||
<p class="mb-4"><i class="fas fa-fire fa-5x"></i></p>
|
||||
{# Translators: saas page when there isn't any saas. #}
|
||||
<h5 class="card-title text-dark">{% trans "Ooops! Looks like there is nothing here!" %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,28 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n musician %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ service.verbose_name }}</h1>
|
||||
<p>{{ service.description }}</p>
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
|
||||
<tr>
|
||||
{% for field_name in service.fields %}
|
||||
<th scope="col">{{ field_name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for resource in object_list %}
|
||||
<tr>
|
||||
{% for field_name in service.fields %}
|
||||
<td>{{ resource|get_item:field_name }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -17,7 +17,12 @@ Including another URLconf
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.views.generic import RedirectView
|
||||
from django.urls import path, reverse_lazy
|
||||
from .views import LoginView, PasswordResetConfirmView, serve_did
|
||||
from .views import (
|
||||
LoginView,
|
||||
PasswordResetConfirmView,
|
||||
serve_did,
|
||||
DobleFactorSendView,
|
||||
)
|
||||
from .admin import views as views_admin
|
||||
from .user import views as views_user
|
||||
# from .verification_portal import views as views_verification_portal
|
||||
|
@ -95,6 +100,8 @@ urlpatterns = [
|
|||
path('user/credentials_presentation/demand',
|
||||
views_user.DemandAuthorizationView.as_view(),
|
||||
name='user_demand_authorization'),
|
||||
path('user/terms/', views_user.TermsAndConditionsView.as_view(),
|
||||
name='user_terms_and_conditions'),
|
||||
|
||||
# Admin
|
||||
path('admin/dashboard/', views_admin.DashboardView.as_view(),
|
||||
|
@ -177,8 +184,13 @@ urlpatterns = [
|
|||
name='admin_schemas_import_add'),
|
||||
path('admin/import', views_admin.ImportView.as_view(),
|
||||
name='admin_import'),
|
||||
path('admin/terms/', views_admin.TermsAndConditionsView.as_view(),
|
||||
name='admin_terms_and_conditions'),
|
||||
path('admin/import/new', views_admin.ImportAddView.as_view(),
|
||||
name='admin_import_add'),
|
||||
path('admin/auth/<uuid:admin2fauth>', views_admin.DobleFactorAuthView.as_view(),
|
||||
name='admin_2fauth'),
|
||||
path('admin/auth/2f/', DobleFactorSendView.as_view(), name='confirm_send_2f'),
|
||||
|
||||
path('did-registry/<str:did_id>/did.json', serve_did)
|
||||
|
||||
|
|
|
@ -16,6 +16,33 @@ class ProfileForm(forms.ModelForm):
|
|||
fields = ('first_name', 'last_name', 'email')
|
||||
|
||||
|
||||
class TermsConditionsForm(forms.Form):
|
||||
accept = forms.BooleanField(
|
||||
label=_("Accept terms and conditions of the service"),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
if data.get("accept"):
|
||||
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 RequestCredentialForm(forms.Form):
|
||||
did = forms.ChoiceField(label=_("Did"), choices=[])
|
||||
credential = forms.ChoiceField(label=_("Credential"), choices=[])
|
||||
|
|
|
@ -31,7 +31,8 @@ from django.conf import settings
|
|||
from idhub.user.forms import (
|
||||
ProfileForm,
|
||||
RequestCredentialForm,
|
||||
DemandAuthorizationForm
|
||||
DemandAuthorizationForm,
|
||||
TermsConditionsForm
|
||||
)
|
||||
from utils import certs
|
||||
from idhub.mixins import UserView
|
||||
|
@ -105,6 +106,26 @@ class CredentialsView(MyWallet, TemplateView):
|
|||
})
|
||||
return context
|
||||
|
||||
|
||||
class TermsAndConditionsView(UserView, FormView):
|
||||
template_name = "idhub/user/terms_conditions.html"
|
||||
title = _("GDPR")
|
||||
section = ""
|
||||
subtitle = _('Accept Terms and Conditions')
|
||||
icon = 'bi bi-file-earmark-medical'
|
||||
form_class = TermsConditionsForm
|
||||
success_url = reverse_lazy('idhub:user_dashboard')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['user'] = self.request.user
|
||||
kwargs['initial'] = {"accept": self.request.user.accept_gdpr}
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
user = form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class CredentialView(MyWallet, TemplateView):
|
||||
template_name = "idhub/user/credential.html"
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
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 <destination> with <subject> and <body>"
|
||||
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)
|
|
@ -1,49 +0,0 @@
|
|||
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)
|
||||
|
|
@ -1,13 +1,18 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.auth import login as auth_login
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
||||
|
||||
from idhub.models import DID
|
||||
from idhub.email.views import NotifyActivateUserByEmail
|
||||
from trustchain_idhub import settings
|
||||
|
||||
|
||||
|
@ -41,6 +46,9 @@ class LoginView(auth_views.LoginView):
|
|||
# )
|
||||
# cache.set("KEY_DIDS", encryption_key, None)
|
||||
cache.set("KEY_DIDS", sensitive_data_encryption_key, None)
|
||||
if not settings.DEVELOPMENT:
|
||||
self.request.session["2fauth"] = str(uuid.uuid4())
|
||||
return redirect(reverse_lazy('idhub:confirm_send_2f'))
|
||||
|
||||
self.request.session["key_did"] = user.encrypt_data(
|
||||
sensitive_data_encryption_key,
|
||||
|
@ -69,3 +77,23 @@ def serve_did(request, did_id):
|
|||
retval = HttpResponse(document)
|
||||
retval.headers["Content-Type"] = "application/json"
|
||||
return retval
|
||||
|
||||
|
||||
class DobleFactorSendView(LoginRequiredMixin, NotifyActivateUserByEmail, TemplateView):
|
||||
template_name = 'auth/2fadmin.html'
|
||||
subject_template_name = 'auth/2fadmin_email_subject.txt'
|
||||
email_template_name = 'auth/2fadmin_email.txt'
|
||||
html_email_template_name = 'auth/2fadmin_email.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.user.is_admin:
|
||||
raise Http404
|
||||
|
||||
f2auth = self.request.session.get("2fauth")
|
||||
if not f2auth:
|
||||
raise Http404
|
||||
|
||||
self.send_email(self.request.user, token=f2auth)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
(
|
||||
'last_login',
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name='last login'
|
||||
),
|
||||
),
|
||||
(
|
||||
'email',
|
||||
models.EmailField(
|
||||
max_length=255, unique=True, verbose_name='Email address'
|
||||
),
|
||||
),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('is_admin', models.BooleanField(default=False)),
|
||||
(
|
||||
'first_name',
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, 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)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -51,6 +51,7 @@ class User(AbstractBaseUser):
|
|||
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)
|
||||
accept_gdpr = models.BooleanField(default=False)
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||
|
||||
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(default=False)),
|
||||
('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='vp_tokens',
|
||||
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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,45 +0,0 @@
|
|||
# Generated by Django 4.2.5 on 2024-01-18 11:32
|
||||
|
||||
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',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -26,4 +26,4 @@ uharfbuzz==0.38.0
|
|||
fontTools==4.47.0
|
||||
weasyprint==60.2
|
||||
ujson==5.9.0
|
||||
didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
|
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import datetime
|
||||
import didkit
|
||||
import json
|
||||
import urllib
|
||||
import jinja2
|
||||
from django.template.backends.django import Template
|
||||
from django.template.loader import get_template
|
||||
|
@ -29,7 +30,8 @@ def webdid_from_controller_key(key):
|
|||
keydid = keydid_from_controller_key(key) # "did:key:<...>"
|
||||
pubkeyid = keydid.rsplit(":")[-1] # <...>
|
||||
document = json.loads(asyncio.run(resolve_keydid(keydid))) # Documento DID en terminos "key"
|
||||
webdid_url = f"did:web:{settings.DOMAIN}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
|
||||
domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:]
|
||||
webdid_url = f"did:web:{domain}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
|
||||
webdid_url_owner = webdid_url + "#owner"
|
||||
# Reemplazamos los campos del documento DID necesarios:
|
||||
document["id"] = webdid_url
|
||||
|
|
Loading…
Reference in New Issue