2021-10-05 11:10:53 +00:00
|
|
|
import logging
|
2021-10-08 11:33:09 +00:00
|
|
|
import smtplib
|
2022-03-11 16:36:51 +00:00
|
|
|
import datetime
|
2023-09-27 12:00:25 +00:00
|
|
|
import requests
|
|
|
|
import json
|
2021-10-05 11:10:53 +00:00
|
|
|
|
2020-01-23 16:37:08 +00:00
|
|
|
from django.conf import settings
|
2021-10-14 09:59:59 +00:00
|
|
|
from django.contrib import messages
|
2019-11-13 11:26:35 +00:00
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2021-10-08 11:33:09 +00:00
|
|
|
from django.core.mail import mail_managers
|
2022-02-24 21:09:17 +00:00
|
|
|
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect
|
2019-10-29 09:58:54 +00:00
|
|
|
from django.urls import reverse_lazy
|
2020-01-23 16:37:08 +00:00
|
|
|
from django.utils import translation
|
2021-10-14 10:56:50 +00:00
|
|
|
from django.utils.html import format_html
|
2019-10-31 09:46:54 +00:00
|
|
|
from django.utils.http import is_safe_url
|
2019-12-10 13:02:26 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2019-12-17 14:15:58 +00:00
|
|
|
from django.views import View
|
2019-10-30 12:05:46 +00:00
|
|
|
from django.views.generic.base import RedirectView, TemplateView
|
2019-12-06 09:28:34 +00:00
|
|
|
from django.views.generic.detail import DetailView
|
2021-10-01 11:36:52 +00:00
|
|
|
from django.views.generic.edit import DeleteView, FormView
|
2019-10-31 13:08:49 +00:00
|
|
|
from django.views.generic.list import ListView
|
2021-06-23 11:47:27 +00:00
|
|
|
from requests.exceptions import HTTPError
|
2019-10-10 07:18:34 +00:00
|
|
|
|
2022-02-28 18:05:06 +00:00
|
|
|
from . import get_version
|
2023-09-27 12:00:25 +00:00
|
|
|
from . import api
|
2019-10-30 13:06:55 +00:00
|
|
|
from .auth import login as auth_login
|
|
|
|
from .auth import logout as auth_logout
|
2021-10-14 09:09:59 +00:00
|
|
|
from .forms import LoginForm, MailboxChangePasswordForm, MailboxCreateForm, MailboxUpdateForm, MailForm
|
2019-12-06 09:28:34 +00:00
|
|
|
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
|
|
|
|
UserTokenRequiredMixin)
|
2021-10-08 11:33:09 +00:00
|
|
|
from .models import (Address, Bill, DatabaseService, Mailbox,
|
|
|
|
MailinglistService, PaymentSource, SaasService)
|
2019-12-12 14:01:51 +00:00
|
|
|
from .settings import ALLOWED_RESOURCES
|
2020-02-17 11:39:49 +00:00
|
|
|
from .utils import get_bootstraped_percent
|
2019-10-25 11:33:37 +00:00
|
|
|
|
2021-10-05 11:10:53 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2023-09-27 12:00:25 +00:00
|
|
|
|
2019-10-30 13:06:55 +00:00
|
|
|
class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
2019-10-25 11:33:37 +00:00
|
|
|
template_name = "musician/dashboard.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Dashboard'),
|
|
|
|
}
|
2019-10-29 09:58:54 +00:00
|
|
|
|
2019-10-30 13:08:14 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
2019-12-12 13:19:39 +00:00
|
|
|
domains = self.orchestra.retrieve_domain_list()
|
2019-10-30 13:08:14 +00:00
|
|
|
|
2019-12-10 13:02:26 +00:00
|
|
|
# TODO(@slamora) update when backend supports notifications
|
|
|
|
notifications = []
|
|
|
|
|
2019-12-12 14:01:51 +00:00
|
|
|
# show resource usage based on plan definition
|
|
|
|
profile_type = context['profile'].type
|
|
|
|
|
2020-02-17 11:39:49 +00:00
|
|
|
# TODO(@slamora) update when backend provides resource usage data
|
|
|
|
resource_usage = {
|
|
|
|
'disk': {
|
|
|
|
'verbose_name': _('Disk usage'),
|
|
|
|
'data': {
|
|
|
|
# 'usage': 534,
|
|
|
|
# 'total': 1024,
|
|
|
|
# 'unit': 'MB',
|
|
|
|
# 'percent': 50,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'traffic': {
|
|
|
|
'verbose_name': _('Traffic'),
|
|
|
|
'data': {
|
|
|
|
# 'usage': 300,
|
|
|
|
# 'total': 2048,
|
|
|
|
# 'unit': 'MB/month',
|
|
|
|
# 'percent': 25,
|
|
|
|
},
|
|
|
|
},
|
2021-10-14 10:56:50 +00:00
|
|
|
'mailbox': self.get_mailbox_usage(profile_type),
|
2020-02-17 11:39:49 +00:00
|
|
|
}
|
|
|
|
|
2019-10-30 13:08:14 +00:00
|
|
|
context.update({
|
2019-12-12 13:19:39 +00:00
|
|
|
'domains': domains,
|
2019-12-10 13:02:26 +00:00
|
|
|
'resource_usage': resource_usage,
|
|
|
|
'notifications': notifications,
|
2019-10-30 13:08:14 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
2021-10-14 10:56:50 +00:00
|
|
|
def get_mailbox_usage(self, profile_type):
|
|
|
|
allowed_mailboxes = ALLOWED_RESOURCES[profile_type]['mailbox']
|
|
|
|
total_mailboxes = len(self.orchestra.retrieve_mailbox_list())
|
2022-02-28 18:05:06 +00:00
|
|
|
mailboxes_left = allowed_mailboxes - total_mailboxes
|
2021-10-14 10:56:50 +00:00
|
|
|
|
|
|
|
alert = ''
|
|
|
|
if mailboxes_left < 0:
|
|
|
|
alert = format_html("<span class='text-danger'>{} extra mailboxes</span>", mailboxes_left * -1)
|
|
|
|
elif mailboxes_left <= 1:
|
|
|
|
alert = format_html("<span class='text-warning'>{} mailbox left</span>", mailboxes_left)
|
|
|
|
|
|
|
|
return {
|
|
|
|
'verbose_name': _('Mailbox usage'),
|
|
|
|
'data': {
|
|
|
|
'usage': total_mailboxes,
|
|
|
|
'total': allowed_mailboxes,
|
|
|
|
'alert': alert,
|
|
|
|
'unit': 'mailboxes',
|
|
|
|
'percent': get_bootstraped_percent(total_mailboxes, allowed_mailboxes),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-10-29 09:58:54 +00:00
|
|
|
|
2019-11-20 19:07:35 +00:00
|
|
|
class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
|
|
|
template_name = "musician/profile.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('User profile'),
|
|
|
|
}
|
2019-11-20 19:07:35 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
try:
|
|
|
|
pay_source = self.orchestra.retrieve_service_list(
|
|
|
|
PaymentSource.api_name)[0]
|
|
|
|
except IndexError:
|
|
|
|
pay_source = {}
|
|
|
|
context.update({
|
|
|
|
'payment': PaymentSource.new_from_json(pay_source)
|
|
|
|
})
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
2022-02-28 18:05:06 +00:00
|
|
|
|
|
|
|
def profile_set_language(request, code):
|
2022-02-24 21:09:17 +00:00
|
|
|
# set user language as active language
|
2022-02-28 18:05:06 +00:00
|
|
|
|
2022-02-26 18:54:27 +00:00
|
|
|
if any(x[0] == code for x in settings.LANGUAGES):
|
2022-02-24 21:09:17 +00:00
|
|
|
# http://127.0.0.1:8080/profile/setLang/es
|
2022-02-26 18:54:27 +00:00
|
|
|
user_language = code
|
2022-02-24 21:09:17 +00:00
|
|
|
translation.activate(user_language)
|
|
|
|
|
|
|
|
response = HttpResponseRedirect('/dashboard')
|
|
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
|
|
|
|
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
response = HttpResponseNotFound('Languague not found')
|
|
|
|
return response
|
|
|
|
|
2019-11-20 19:07:35 +00:00
|
|
|
|
2019-10-31 16:16:51 +00:00
|
|
|
class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView):
|
|
|
|
"""Base list view to all services"""
|
2019-11-13 11:26:35 +00:00
|
|
|
service_class = None
|
2020-01-07 12:53:59 +00:00
|
|
|
template_name = "musician/service_list.html"
|
2019-10-31 16:16:51 +00:00
|
|
|
|
2019-11-13 10:08:19 +00:00
|
|
|
def get_queryset(self):
|
2019-11-13 11:26:35 +00:00
|
|
|
if self.service_class is None or self.service_class.api_name is None:
|
2019-11-13 10:08:19 +00:00
|
|
|
raise ImproperlyConfigured(
|
|
|
|
"ServiceListView requires a definiton of 'service'")
|
|
|
|
|
2019-12-17 10:57:59 +00:00
|
|
|
queryfilter = self.get_queryfilter()
|
2019-11-20 19:07:35 +00:00
|
|
|
json_qs = self.orchestra.retrieve_service_list(
|
2019-12-17 10:57:59 +00:00
|
|
|
self.service_class.api_name,
|
|
|
|
querystring=queryfilter,
|
|
|
|
)
|
2019-11-20 09:29:39 +00:00
|
|
|
return [self.service_class.new_from_json(data) for data in json_qs]
|
2019-11-13 10:08:19 +00:00
|
|
|
|
2019-12-17 10:57:59 +00:00
|
|
|
def get_queryfilter(self):
|
|
|
|
"""Does nothing by default. Should be implemented on subclasses"""
|
|
|
|
return ''
|
|
|
|
|
2019-11-13 10:08:19 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context.update({
|
|
|
|
'service': self.service_class,
|
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2019-12-17 13:48:21 +00:00
|
|
|
class BillingView(ServiceListView):
|
|
|
|
service_class = Bill
|
|
|
|
template_name = "musician/billing.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Billing'),
|
|
|
|
}
|
2019-12-17 13:48:21 +00:00
|
|
|
|
2022-03-11 11:16:29 +00:00
|
|
|
def get_queryset(self):
|
|
|
|
qs = super().get_queryset()
|
|
|
|
qs = sorted(qs, key=lambda x: x.created_on, reverse=True)
|
2022-03-11 16:36:51 +00:00
|
|
|
for q in qs:
|
2022-03-17 18:23:36 +00:00
|
|
|
q.created_on = datetime.datetime.strptime(q.created_on, "%Y-%m-%d")
|
2022-03-11 11:16:29 +00:00
|
|
|
return qs
|
|
|
|
|
2019-12-17 13:48:21 +00:00
|
|
|
|
2019-12-17 14:15:58 +00:00
|
|
|
class BillDownloadView(CustomContextMixin, UserTokenRequiredMixin, View):
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Download bill'),
|
|
|
|
}
|
|
|
|
|
2019-12-17 14:15:58 +00:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
pk = self.kwargs.get('pk')
|
|
|
|
bill = self.orchestra.retrieve_bill_document(pk)
|
|
|
|
|
|
|
|
return HttpResponse(bill)
|
|
|
|
|
|
|
|
|
2019-11-13 10:08:19 +00:00
|
|
|
class MailView(ServiceListView):
|
2021-07-02 11:08:06 +00:00
|
|
|
service_class = Address
|
2021-09-24 12:31:29 +00:00
|
|
|
template_name = "musician/addresses.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Mail addresses'),
|
|
|
|
}
|
2019-11-13 10:08:19 +00:00
|
|
|
|
|
|
|
def get_queryset(self):
|
2019-12-17 10:16:23 +00:00
|
|
|
# retrieve mails applying filters (if any)
|
|
|
|
queryfilter = self.get_queryfilter()
|
2020-02-17 10:07:21 +00:00
|
|
|
addresses = self.orchestra.retrieve_mail_address_list(
|
|
|
|
querystring=queryfilter
|
2019-12-17 10:16:23 +00:00
|
|
|
)
|
2019-11-13 10:08:19 +00:00
|
|
|
return addresses
|
2019-10-30 13:23:46 +00:00
|
|
|
|
2019-12-17 10:16:23 +00:00
|
|
|
def get_queryfilter(self):
|
|
|
|
"""Retrieve query params (if any) to filter queryset"""
|
|
|
|
domain_id = self.request.GET.get('domain')
|
2019-12-17 10:32:38 +00:00
|
|
|
if domain_id:
|
|
|
|
return "domain={}".format(domain_id)
|
2019-12-17 10:16:23 +00:00
|
|
|
|
2019-12-17 10:32:38 +00:00
|
|
|
return ''
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
domain_id = self.request.GET.get('domain')
|
|
|
|
if domain_id:
|
|
|
|
context.update({
|
|
|
|
'active_domain': self.orchestra.retrieve_domain(domain_id)
|
|
|
|
})
|
2021-09-24 12:31:29 +00:00
|
|
|
context['mailboxes'] = self.orchestra.retrieve_mailbox_list()
|
2019-12-17 10:32:38 +00:00
|
|
|
return context
|
2019-12-17 10:16:23 +00:00
|
|
|
|
2019-10-30 13:23:46 +00:00
|
|
|
|
2021-06-23 11:47:27 +00:00
|
|
|
class MailCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
2021-07-02 11:08:06 +00:00
|
|
|
service_class = Address
|
2021-10-01 11:36:52 +00:00
|
|
|
template_name = "musician/address_form.html"
|
2021-06-23 11:47:27 +00:00
|
|
|
form_class = MailForm
|
2021-09-27 11:52:27 +00:00
|
|
|
success_url = reverse_lazy("musician:address-list")
|
2021-06-24 11:19:54 +00:00
|
|
|
extra_context = {'service': service_class}
|
2021-06-23 11:47:27 +00:00
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
kwargs['domains'] = self.orchestra.retrieve_domain_list()
|
|
|
|
kwargs['mailboxes'] = self.orchestra.retrieve_mailbox_list()
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# handle request errors e.g. 400 validation
|
|
|
|
try:
|
2021-06-24 11:08:16 +00:00
|
|
|
serialized_data = form.serialize()
|
|
|
|
self.orchestra.create_mail_address(serialized_data)
|
|
|
|
except HTTPError as e:
|
|
|
|
form.add_error(field='__all__', error=e)
|
|
|
|
return self.form_invalid(form)
|
|
|
|
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
|
|
|
class MailUpdateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
2021-07-02 11:08:06 +00:00
|
|
|
service_class = Address
|
2021-10-01 11:36:52 +00:00
|
|
|
template_name = "musician/address_form.html"
|
2021-06-24 11:08:16 +00:00
|
|
|
form_class = MailForm
|
2021-09-27 11:52:27 +00:00
|
|
|
success_url = reverse_lazy("musician:address-list")
|
2021-06-24 11:19:54 +00:00
|
|
|
extra_context = {'service': service_class}
|
2021-06-24 11:08:16 +00:00
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
instance = self.orchestra.retrieve_mail_address(self.kwargs['pk'])
|
|
|
|
|
|
|
|
kwargs.update({
|
|
|
|
'instance': instance,
|
|
|
|
'domains': self.orchestra.retrieve_domain_list(),
|
|
|
|
'mailboxes': self.orchestra.retrieve_mailbox_list(),
|
|
|
|
})
|
|
|
|
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# handle request errors e.g. 400 validation
|
|
|
|
try:
|
|
|
|
serialized_data = form.serialize()
|
|
|
|
self.orchestra.update_mail_address(self.kwargs['pk'], serialized_data)
|
2021-06-23 11:47:27 +00:00
|
|
|
except HTTPError as e:
|
|
|
|
form.add_error(field='__all__', error=e)
|
|
|
|
return self.form_invalid(form)
|
|
|
|
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2021-10-01 11:36:52 +00:00
|
|
|
class AddressDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
|
|
|
|
template_name = "musician/address_check_delete.html"
|
|
|
|
success_url = reverse_lazy("musician:address-list")
|
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
obj = self.orchestra.retrieve_mail_address(self.kwargs['pk'])
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
|
|
|
try:
|
|
|
|
self.orchestra.delete_mail_address(self.object.id)
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.success(self.request, _('Address deleted!'))
|
2021-10-01 11:36:52 +00:00
|
|
|
except HTTPError as e:
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
2021-10-06 09:07:22 +00:00
|
|
|
logger.error(e)
|
2021-10-01 11:36:52 +00:00
|
|
|
|
|
|
|
return HttpResponseRedirect(self.success_url)
|
|
|
|
|
|
|
|
|
2019-10-31 16:16:51 +00:00
|
|
|
class MailingListsView(ServiceListView):
|
2019-11-13 10:42:23 +00:00
|
|
|
service_class = MailinglistService
|
2019-12-04 11:37:35 +00:00
|
|
|
template_name = "musician/mailinglists.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Mailing lists'),
|
|
|
|
}
|
2019-10-31 13:09:49 +00:00
|
|
|
|
2019-12-17 10:57:59 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
domain_id = self.request.GET.get('domain')
|
|
|
|
if domain_id:
|
|
|
|
context.update({
|
|
|
|
'active_domain': self.orchestra.retrieve_domain(domain_id)
|
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
|
|
|
def get_queryfilter(self):
|
|
|
|
"""Retrieve query params (if any) to filter queryset"""
|
|
|
|
# TODO(@slamora): this is not working because backend API
|
|
|
|
# doesn't support filtering by domain
|
|
|
|
domain_id = self.request.GET.get('domain')
|
|
|
|
if domain_id:
|
|
|
|
return "domain={}".format(domain_id)
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
2021-09-24 12:31:29 +00:00
|
|
|
class MailboxesView(ServiceListView):
|
2021-09-27 10:40:52 +00:00
|
|
|
service_class = Mailbox
|
2021-09-24 12:31:29 +00:00
|
|
|
template_name = "musician/mailboxes.html"
|
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Mailboxes'),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-10-05 11:10:53 +00:00
|
|
|
class MailboxCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
|
|
|
service_class = Mailbox
|
|
|
|
template_name = "musician/mailbox_form.html"
|
|
|
|
form_class = MailboxCreateForm
|
|
|
|
success_url = reverse_lazy("musician:mailbox-list")
|
2021-10-05 11:31:09 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context.update({
|
|
|
|
'extra_mailbox': self.is_extra_mailbox(context['profile']),
|
|
|
|
'service': self.service_class,
|
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
|
|
|
def is_extra_mailbox(self, profile):
|
|
|
|
number_of_mailboxes = len(self.orchestra.retrieve_mailbox_list())
|
|
|
|
return number_of_mailboxes >= profile.allowed_resources('mailbox')
|
2021-10-05 11:10:53 +00:00
|
|
|
|
2021-10-07 12:10:25 +00:00
|
|
|
def get_form_kwargs(self):
|
|
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
kwargs.update({
|
|
|
|
'addresses': self.orchestra.retrieve_mail_address_list(),
|
|
|
|
})
|
|
|
|
|
|
|
|
return kwargs
|
|
|
|
|
2021-10-05 11:10:53 +00:00
|
|
|
def form_valid(self, form):
|
|
|
|
serialized_data = form.serialize()
|
|
|
|
status, response = self.orchestra.create_mailbox(serialized_data)
|
|
|
|
|
|
|
|
if status >= 400:
|
|
|
|
if status == 400:
|
|
|
|
# handle errors & add to form (they will be rendered)
|
|
|
|
form.add_error(field=None, error=response)
|
|
|
|
else:
|
|
|
|
logger.error("{}: {}".format(status, response[:120]))
|
|
|
|
msg = "Sorry, an error occurred while processing your request ({})".format(status)
|
|
|
|
form.add_error(field='__all__', error=msg)
|
2021-10-07 12:10:25 +00:00
|
|
|
return self.form_invalid(form)
|
2021-10-05 11:10:53 +00:00
|
|
|
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2021-10-07 11:51:31 +00:00
|
|
|
class MailboxUpdateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
|
|
|
service_class = Mailbox
|
|
|
|
template_name = "musician/mailbox_form.html"
|
|
|
|
form_class = MailboxUpdateForm
|
|
|
|
success_url = reverse_lazy("musician:mailbox-list")
|
|
|
|
extra_context = {'service': service_class}
|
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
instance = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
|
|
|
|
|
|
|
|
kwargs.update({
|
|
|
|
'instance': instance,
|
|
|
|
'addresses': self.orchestra.retrieve_mail_address_list(),
|
|
|
|
})
|
|
|
|
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
serialized_data = form.serialize()
|
|
|
|
status, response = self.orchestra.update_mailbox(self.kwargs['pk'], serialized_data)
|
|
|
|
|
|
|
|
if status >= 400:
|
|
|
|
if status == 400:
|
|
|
|
# handle errors & add to form (they will be rendered)
|
|
|
|
form.add_error(field=None, error=response)
|
|
|
|
else:
|
|
|
|
logger.error("{}: {}".format(status, response[:120]))
|
|
|
|
msg = "Sorry, an error occurred while processing your request ({})".format(status)
|
|
|
|
form.add_error(field='__all__', error=msg)
|
|
|
|
|
|
|
|
return self.form_invalid(form)
|
|
|
|
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2021-10-06 09:07:22 +00:00
|
|
|
class MailboxDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
|
|
|
|
template_name = "musician/mailbox_check_delete.html"
|
|
|
|
success_url = reverse_lazy("musician:mailbox-list")
|
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
obj = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
|
|
|
try:
|
|
|
|
self.orchestra.delete_mailbox(self.object.id)
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.success(self.request, _('Mailbox deleted!'))
|
2021-10-06 09:07:22 +00:00
|
|
|
except HTTPError as e:
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
2021-10-06 09:07:22 +00:00
|
|
|
logger.error(e)
|
|
|
|
|
2021-10-08 11:33:09 +00:00
|
|
|
self.notify_managers(self.object)
|
|
|
|
|
2021-10-06 09:07:22 +00:00
|
|
|
return HttpResponseRedirect(self.success_url)
|
|
|
|
|
2021-10-08 11:33:09 +00:00
|
|
|
def notify_managers(self, mailbox):
|
|
|
|
user = self.get_context_data()['profile']
|
|
|
|
subject = 'Mailbox {} ({}) deleted | Musician'.format(mailbox.id, mailbox.name)
|
|
|
|
content = (
|
|
|
|
"User {} ({}) has deleted its mailbox {} ({}) via musician.\n"
|
|
|
|
"The mailbox has been marked as inactive but has not been removed."
|
|
|
|
).format(user.username, user.full_name, mailbox.id, mailbox.name)
|
|
|
|
|
|
|
|
try:
|
|
|
|
mail_managers(subject, content, fail_silently=False)
|
|
|
|
except (smtplib.SMTPException, ConnectionRefusedError):
|
|
|
|
logger.error("Error sending email to managers", exc_info=True)
|
|
|
|
|
2021-10-06 09:07:22 +00:00
|
|
|
|
2021-10-14 09:09:59 +00:00
|
|
|
class MailboxChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
|
|
|
template_name = "musician/mailbox_change_password.html"
|
|
|
|
form_class = MailboxChangePasswordForm
|
|
|
|
success_url = reverse_lazy("musician:mailbox-list")
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
self.object = self.get_object()
|
|
|
|
context.update({
|
|
|
|
'object': self.object,
|
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
obj = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
data = {
|
|
|
|
'password': form.cleaned_data['password2']
|
|
|
|
}
|
|
|
|
status, response = self.orchestra.set_password_mailbox(self.kwargs['pk'], data)
|
2021-10-14 09:59:59 +00:00
|
|
|
|
|
|
|
if status < 400:
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.success(self.request, _('Password updated!'))
|
2021-10-14 09:59:59 +00:00
|
|
|
else:
|
2021-10-14 10:05:42 +00:00
|
|
|
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
2021-10-14 09:59:59 +00:00
|
|
|
logger.error("{}: {}".format(status, str(response)[:100]))
|
|
|
|
|
2021-10-14 09:09:59 +00:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2019-11-13 11:27:25 +00:00
|
|
|
class DatabasesView(ServiceListView):
|
2019-11-20 09:29:39 +00:00
|
|
|
template_name = "musician/databases.html"
|
2019-11-13 11:27:25 +00:00
|
|
|
service_class = DatabaseService
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Databases'),
|
|
|
|
}
|
2019-10-30 13:23:46 +00:00
|
|
|
|
|
|
|
|
2019-12-06 09:28:34 +00:00
|
|
|
class SaasView(ServiceListView):
|
|
|
|
service_class = SaasService
|
2019-10-30 13:23:46 +00:00
|
|
|
template_name = "musician/saas.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Software as a Service'),
|
|
|
|
}
|
2019-10-30 13:23:46 +00:00
|
|
|
|
|
|
|
|
2019-12-13 14:08:01 +00:00
|
|
|
class DomainDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView):
|
|
|
|
template_name = "musician/domain_detail.html"
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Domain details'),
|
|
|
|
}
|
2019-12-13 14:08:01 +00:00
|
|
|
|
|
|
|
def get_queryset(self):
|
2019-12-17 09:25:10 +00:00
|
|
|
# Return an empty list to avoid a request to retrieve all the
|
|
|
|
# user domains. We will get a 404 if the domain doesn't exists
|
|
|
|
# while invoking `get_object`
|
|
|
|
return []
|
2019-12-13 14:08:01 +00:00
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
if queryset is None:
|
|
|
|
queryset = self.get_queryset()
|
|
|
|
|
|
|
|
pk = self.kwargs.get(self.pk_url_kwarg)
|
2019-12-17 09:25:10 +00:00
|
|
|
domain = self.orchestra.retrieve_domain(pk)
|
2019-12-13 14:08:01 +00:00
|
|
|
|
|
|
|
return domain
|
|
|
|
|
|
|
|
|
2023-09-27 12:00:25 +00:00
|
|
|
class AllowCodeView(RedirectView):
|
|
|
|
"""
|
|
|
|
Log in the user with OAuth2.
|
|
|
|
"""
|
|
|
|
permanent = False
|
|
|
|
success_url = reverse_lazy('musician:dashboard')
|
|
|
|
userinfo = None
|
|
|
|
|
|
|
|
def get_token(self):
|
2023-12-21 12:19:32 +00:00
|
|
|
oidc_provider = settings.OIDC_PROVIDER.strip("/")
|
|
|
|
domain = settings.DOMAIN.strip("/")
|
|
|
|
url = f"{oidc_provider}/application/o/token/"
|
2023-09-27 12:00:25 +00:00
|
|
|
client_id = settings.CLIENT_ID
|
|
|
|
client_secret = settings.CLIENT_SECRET
|
|
|
|
self.code = self.request.GET.get('code')
|
2023-12-21 12:19:32 +00:00
|
|
|
data = {
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
'code': self.code,
|
|
|
|
'redirect_uri': f"{domain}/allow_code",
|
|
|
|
}
|
2023-09-27 12:00:25 +00:00
|
|
|
auth = (client_id, client_secret)
|
|
|
|
msg = requests.post(url, data=data, auth=auth)
|
|
|
|
self.token = msg.text
|
|
|
|
|
2023-12-21 12:19:32 +00:00
|
|
|
def get_user_info(self):
|
|
|
|
# DELETE THIS METHOD IS ONLY A TEST
|
|
|
|
if self.userinfo:
|
|
|
|
return self.username
|
|
|
|
if 'error' in self.token:
|
|
|
|
return
|
|
|
|
|
|
|
|
if 'access_token' not in self.token:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not isinstance(self.token, str):
|
|
|
|
return
|
|
|
|
|
|
|
|
self.token = json.loads(self.token)
|
|
|
|
oidc_provider = settings.OIDC_PROVIDER.strip("/")
|
|
|
|
url = f"{oidc_provider}/application/o/userinfo/"
|
|
|
|
access_token = self.token.get('access_token')
|
|
|
|
token_type = self.token.get('token_type', 'Bearer')
|
|
|
|
if not access_token or not token_type:
|
|
|
|
return
|
|
|
|
|
|
|
|
headers = {"Authorization": f"{token_type} {access_token}"}
|
|
|
|
msg = requests.get(url, headers=headers)
|
|
|
|
self.userinfo = json.loads(msg.text)
|
|
|
|
self.username = self.userinfo.get("username")
|
|
|
|
# import pdb; pdb.set_trace()
|
|
|
|
return self.username
|
|
|
|
|
2023-09-27 12:00:25 +00:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Logs in the user.
|
|
|
|
"""
|
|
|
|
self.get_token()
|
2023-12-21 12:19:32 +00:00
|
|
|
# Delete this line
|
2023-09-27 12:00:25 +00:00
|
|
|
# self.get_user_info()
|
|
|
|
orchestra = api.Orchestra(token=self.token)
|
|
|
|
self.orchestra_token = orchestra.auth_token
|
|
|
|
self.user = orchestra.retrieve_profile()
|
|
|
|
username = self.user.username
|
|
|
|
auth_login(self.request, username, self.orchestra_token)
|
|
|
|
|
|
|
|
# set user language as active language
|
|
|
|
user_language = self.user.language
|
|
|
|
translation.activate(user_language)
|
|
|
|
|
|
|
|
response = HttpResponseRedirect(self.get_success_url())
|
|
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
|
|
|
|
|
|
|
|
return response
|
|
|
|
# return super().get(*args, **kwargs)
|
|
|
|
|
|
|
|
def get_success_url(self):
|
|
|
|
url = self.get_redirect_url()
|
|
|
|
return url or self.success_url
|
|
|
|
|
|
|
|
def get_redirect_url(self):
|
|
|
|
"""Return the user-originating redirect URL if it's safe."""
|
|
|
|
redirect_to = self.success_url
|
|
|
|
url_is_safe = is_safe_url(
|
|
|
|
url=redirect_to,
|
|
|
|
allowed_hosts={self.request.get_host()},
|
|
|
|
require_https=self.request.is_secure(),
|
|
|
|
)
|
|
|
|
return redirect_to if url_is_safe else ''
|
|
|
|
|
|
|
|
|
2019-10-29 09:58:54 +00:00
|
|
|
class LoginView(FormView):
|
|
|
|
template_name = 'auth/login.html'
|
|
|
|
form_class = LoginForm
|
|
|
|
success_url = reverse_lazy('musician:dashboard')
|
2019-10-31 09:46:54 +00:00
|
|
|
redirect_field_name = 'next'
|
2020-01-07 12:53:59 +00:00
|
|
|
extra_context = {
|
|
|
|
# Translators: This message appears on the page title
|
|
|
|
'title': _('Login'),
|
|
|
|
'version': get_version(),
|
|
|
|
}
|
2019-10-30 12:05:46 +00:00
|
|
|
|
|
|
|
def get_form_kwargs(self):
|
|
|
|
kwargs = super().get_form_kwargs()
|
|
|
|
kwargs['request'] = self.request
|
|
|
|
return kwargs
|
|
|
|
|
2023-09-27 12:00:25 +00:00
|
|
|
def get_oidc_url(self):
|
|
|
|
client_id = settings.CLIENT_ID
|
2023-12-21 12:19:32 +00:00
|
|
|
oidc_provider = settings.OIDC_PROVIDER.strip("/")
|
|
|
|
domain = settings.DOMAIN.strip("/")
|
|
|
|
if not client_id or not oidc_provider:
|
2023-09-27 12:00:25 +00:00
|
|
|
return
|
|
|
|
|
2023-12-21 12:19:32 +00:00
|
|
|
url = f'{oidc_provider}/application/o/authorize/?client_id={client_id}'
|
|
|
|
url += f'&scope=openid+musician&response_type=code&nonce=abc'
|
|
|
|
url += f'&redirect_uri={domain}/allow_code&response_type=code&nonce=abc'
|
2023-09-27 12:00:25 +00:00
|
|
|
return url
|
|
|
|
|
2019-10-30 12:05:46 +00:00
|
|
|
def form_valid(self, form):
|
|
|
|
"""Security check complete. Log the user in."""
|
|
|
|
auth_login(self.request, form.username, form.token)
|
2020-01-23 16:37:08 +00:00
|
|
|
|
|
|
|
# set user language as active language
|
|
|
|
user_language = form.user.language
|
|
|
|
translation.activate(user_language)
|
|
|
|
|
|
|
|
response = HttpResponseRedirect(self.get_success_url())
|
|
|
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
|
|
|
|
|
|
|
|
return response
|
2019-10-30 12:05:46 +00:00
|
|
|
|
2019-10-31 09:46:54 +00:00
|
|
|
def get_success_url(self):
|
|
|
|
url = self.get_redirect_url()
|
|
|
|
return url or self.success_url
|
|
|
|
|
|
|
|
def get_redirect_url(self):
|
|
|
|
"""Return the user-originating redirect URL if it's safe."""
|
|
|
|
redirect_to = self.request.POST.get(
|
|
|
|
self.redirect_field_name,
|
|
|
|
self.request.GET.get(self.redirect_field_name, '')
|
|
|
|
)
|
|
|
|
url_is_safe = is_safe_url(
|
|
|
|
url=redirect_to,
|
|
|
|
allowed_hosts={self.request.get_host()},
|
|
|
|
require_https=self.request.is_secure(),
|
|
|
|
)
|
|
|
|
return redirect_to if url_is_safe else ''
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context.update({
|
|
|
|
self.redirect_field_name: self.get_redirect_url(),
|
2023-09-27 12:00:25 +00:00
|
|
|
'oidc_provider': self.get_oidc_url(),
|
2019-10-31 09:46:54 +00:00
|
|
|
**(self.extra_context or {})
|
|
|
|
})
|
|
|
|
return context
|
|
|
|
|
2019-10-30 12:05:46 +00:00
|
|
|
|
|
|
|
class LogoutView(RedirectView):
|
|
|
|
"""
|
|
|
|
Log out the user.
|
|
|
|
"""
|
|
|
|
permanent = False
|
|
|
|
pattern_name = 'musician:login'
|
|
|
|
|
|
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Logs out the user.
|
|
|
|
"""
|
|
|
|
auth_logout(self.request)
|
|
|
|
return super().get_redirect_url(*args, **kwargs)
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
"""Logout may be done via POST."""
|
|
|
|
return self.get(request, *args, **kwargs)
|