diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py index 63dc3e882..171cfa6d9 100644 --- a/apps/assets/signals_handler.py +++ b/apps/assets/signals_handler.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # - -from django.db.models.signals import post_save, post_init, m2m_changed, pre_save +from django.db.models.signals import post_save, post_init from django.dispatch import receiver from django.utils.translation import gettext as _ diff --git a/apps/common/api.py b/apps/common/api.py new file mode 100644 index 000000000..270c81095 --- /dev/null +++ b/apps/common/api.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +from rest_framework.views import APIView +from rest_framework.views import Response +from django.core.mail import get_connection, send_mail +from django.utils.translation import ugettext_lazy as _ + +from .permissions import IsSuperUser +from .serializers import MailTestSerializer + + +class MailTestingAPI(APIView): + permission_classes = (IsSuperUser,) + serializer_class = MailTestSerializer + success_message = _("Test mail sent to {}, please check") + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + email_host_user = serializer.validated_data["EMAIL_HOST_USER"] + kwargs = { + "host": serializer.validated_data["EMAIL_HOST"], + "port": serializer.validated_data["EMAIL_PORT"], + "username": serializer.validated_data["EMAIL_HOST_USER"], + "password": serializer.validated_data["EMAIL_HOST_PASSWORD"], + "use_ssl": serializer.validated_data["EMAIL_USE_SSL"], + "use_tls": serializer.validated_data["EMAIL_USE_TLS"] + } + connection = get_connection(timeout=5, **kwargs) + + try: + connection.open() + except Exception as e: + return Response({"error": str(e)}, status=401) + + try: + send_mail("Test", "Test smtp setting", email_host_user, + [email_host_user], connection=connection) + except Exception as e: + return Response({"error": str(e)}, status=401) + + return Response({"msg": self.success_message.format(email_host_user)}) diff --git a/apps/common/apps.py b/apps/common/apps.py index 6664a6438..bc6db2151 100644 --- a/apps/common/apps.py +++ b/apps/common/apps.py @@ -5,3 +5,9 @@ from django.apps import AppConfig class CommonConfig(AppConfig): name = 'common' + + def ready(self): + from . import signals_handler + from .signals import django_ready + django_ready.send(self.__class__) + return super().ready() diff --git a/apps/common/forms.py b/apps/common/forms.py new file mode 100644 index 000000000..60cb1ae37 --- /dev/null +++ b/apps/common/forms.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +import json + +from django import forms +from django.utils.translation import ugettext_lazy as _ +from django.db import transaction + +from .models import Setting + + +def to_model_value(value): + try: + return json.dumps(value) + except json.JSONDecodeError: + return None + + +def to_form_value(value): + try: + return json.loads(value) + except json.JSONDecodeError: + return '' + + +class BaseForm(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.is_bound: + settings = Setting.objects.all() + for name, field in self.fields.items(): + db_value = getattr(settings, name).value + if db_value: + field.initial = to_form_value(db_value) + + def save(self): + if not self.is_bound: + raise ValueError("Form is not bound") + + if self.is_valid(): + with transaction.atomic(): + for name, value in self.cleaned_data.items(): + field = self.fields[name] + if isinstance(field.widget, forms.PasswordInput) and not value: + continue + defaults = { + 'name': name, + 'value': to_model_value(value) + } + Setting.objects.update_or_create(defaults=defaults, name=name) + else: + raise ValueError(self.errors) + + +class EmailSettingForm(BaseForm): + EMAIL_HOST = forms.CharField( + max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org' + ) + EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25) + EMAIL_HOST_USER = forms.CharField( + max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org' + ) + EMAIL_HOST_PASSWORD = forms.CharField( + max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput, + required=False, help_text=_("Some provider use token except password") + ) + EMAIL_USE_SSL = forms.BooleanField( + label=_("Use SSL"), initial=False, required=False, + help_text=_("If SMTP port is 465, may be select") + ) + EMAIL_USE_TLS = forms.BooleanField( + label=_("Use TLS"), initial=False, required=False, + help_text=_("If SMTP port is 587, may be select") + ) diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 1371cf65b..2832424c6 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -1,10 +1,10 @@ # coding: utf-8 -import inspect from django.db import models from django.http import JsonResponse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.mixins import UserPassesTestMixin class NoDeleteQuerySet(models.query.QuerySet): @@ -113,4 +113,14 @@ class DatetimeSearchMixin: ) else: self.date_to = timezone.now() - return super().get(request, *args, **kwargs) \ No newline at end of file + return super().get(request, *args, **kwargs) + + +class AdminUserRequiredMixin(UserPassesTestMixin): + def test_func(self): + if not self.request.user.is_authenticated: + return False + elif not self.request.user.is_superuser: + self.raise_exception = True + return False + return True diff --git a/apps/common/models.py b/apps/common/models.py index beeb30826..3d6ee6008 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,2 +1,47 @@ -from django.db import models +import json +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + + +class SettingQuerySet(models.QuerySet): + def __getattr__(self, item): + instances = self.filter(name=item) + if len(instances) == 1: + return instances[0] + else: + return Setting() + + +class SettingManager(models.Manager): + def get_queryset(self): + return SettingQuerySet(self.model, using=self._db) + + +class Setting(models.Model): + name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) + value = models.TextField(verbose_name=_("Value")) + enabled = models.BooleanField(verbose_name=_("Enabled"), default=True) + comment = models.TextField(verbose_name=_("Comment")) + + objects = SettingManager() + + def __str__(self): + return self.name + + @classmethod + def refresh_all_settings(cls): + settings_list = cls.objects.all() + for setting in settings_list: + setting.refresh_setting() + + def refresh_setting(self): + try: + value = json.loads(self.value) + except json.JSONDecodeError: + return + setattr(settings, self.name, value) + + class Meta: + db_table = "settings" diff --git a/apps/common/permissions.py b/apps/common/permissions.py new file mode 100644 index 000000000..6a1cb8230 --- /dev/null +++ b/apps/common/permissions.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import permissions + + +class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): + """Allows access to valid user, is active and not expired""" + + def has_permission(self, request, view): + return super(IsValidUser, self).has_permission(request, view) \ + and request.user.is_valid + + +class IsAppUser(IsValidUser): + """Allows access only to app user """ + + def has_permission(self, request, view): + return super(IsAppUser, self).has_permission(request, view) \ + and request.user.is_app + + +class IsSuperUser(IsValidUser): + """Allows access only to superuser""" + + def has_permission(self, request, view): + return super(IsSuperUser, self).has_permission(request, view) \ + and request.user.is_superuser + + +class IsSuperUserOrAppUser(IsValidUser): + """Allows access between superuser and app user""" + + def has_permission(self, request, view): + return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ + and (request.user.is_superuser or request.user.is_app) + + +class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser): + def has_permission(self, request, view): + if IsValidUser.has_permission(self, request, view) \ + and request.method in permissions.SAFE_METHODS: + return True + else: + return IsSuperUserOrAppUser.has_permission(self, request, view) + + +class IsCurrentUserOrReadOnly(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + return obj == request.user diff --git a/apps/common/serializers.py b/apps/common/serializers.py new file mode 100644 index 000000000..37e6555a3 --- /dev/null +++ b/apps/common/serializers.py @@ -0,0 +1,10 @@ +from rest_framework import serializers + + +class MailTestSerializer(serializers.Serializer): + EMAIL_HOST = serializers.CharField(max_length=1024, required=True) + EMAIL_PORT = serializers.IntegerField(default=25) + EMAIL_HOST_USER = serializers.CharField(max_length=1024) + EMAIL_HOST_PASSWORD = serializers.CharField() + EMAIL_USE_SSL = serializers.BooleanField(default=False) + EMAIL_USE_TLS = serializers.BooleanField(default=False) diff --git a/apps/common/signals.py b/apps/common/signals.py new file mode 100644 index 000000000..de8a84139 --- /dev/null +++ b/apps/common/signals.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# + +from django.dispatch import Signal + +django_ready = Signal() diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py new file mode 100644 index 000000000..6b54706db --- /dev/null +++ b/apps/common/signals_handler.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# + +from django.dispatch import receiver +from django.db.models.signals import post_save + +from .models import Setting +from .utils import get_logger +from .signals import django_ready + + +logger = get_logger(__file__) + + +@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier") +def refresh_settings_on_changed(sender, instance=None, **kwargs): + logger.debug("Receive setting item change") + logger.debug(" - refresh setting: {}".format(instance.name)) + if instance: + instance.refresh_setting() + + +@receiver(django_ready, dispatch_uid="my_unique_identifier") +def refresh_all_settings_on_django_ready(sender, **kwargs): + logger.debug("Receive django ready signal") + logger.debug(" - fresh all settings") + Setting.refresh_all_settings() diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html new file mode 100644 index 000000000..d6d7fdf16 --- /dev/null +++ b/apps/common/templates/common/email_setting.html @@ -0,0 +1,73 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% block content %} +