diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index e3f6bc6b3..ce7b8bfb4 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -85,7 +85,7 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE template_name = 'assets/admin_user_assets.html' context_object_name = 'admin_user' object = None diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index d251b7a15..b2f57e323 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -92,7 +92,7 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class AssetModalListView(AdminUserRequiredMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE model = Asset context_object_name = 'asset_modal_list' template_name = 'assets/asset_modal_list.html' diff --git a/apps/common/api.py b/apps/common/api.py index 270c81095..e8680e85e 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- # +import json + from rest_framework.views import APIView from rest_framework.views import Response +from ldap3 import Server, Connection from django.core.mail import get_connection, send_mail from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from .permissions import IsSuperUser -from .serializers import MailTestSerializer +from .serializers import MailTestSerializer, LDAPTestSerializer class MailTestingAPI(APIView): @@ -27,7 +31,6 @@ class MailTestingAPI(APIView): "use_tls": serializer.validated_data["EMAIL_USE_TLS"] } connection = get_connection(timeout=5, **kwargs) - try: connection.open() except Exception as e: @@ -40,3 +43,68 @@ class MailTestingAPI(APIView): return Response({"error": str(e)}, status=401) return Response({"msg": self.success_message.format(email_host_user)}) + else: + return Response({"error": str(serializer.errors)}, status=401) + + +class LDAPTestingAPI(APIView): + permission_classes = (IsSuperUser,) + serializer_class = LDAPTestSerializer + success_message = _("Test ldap success") + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + host = serializer.validated_data["AUTH_LDAP_SERVER_URI"] + bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"] + password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"] + use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False) + search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"] + search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"] + attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"] + + print(serializer.validated_data) + + try: + attr_map = json.loads(attr_map) + except json.JSONDecodeError: + return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401) + + server = Server(host, use_ssl=use_ssl) + conn = Connection(server, bind_dn, password) + try: + conn.bind() + except Exception as e: + return Response({"error": str(e)}, status=401) + + print(search_ou) + print(search_filter % ({"user": "*"})) + print(attr_map.values()) + ok = conn.search(search_ou, search_filter % ({"user": "*"}), + attributes=list(attr_map.values())) + if not ok: + return Response({"error": "Search no entry matched"}, status=401) + + users = [] + for entry in conn.entries: + user = {} + for attr, mapping in attr_map.items(): + if hasattr(entry, mapping): + user[attr] = getattr(entry, mapping) + users.append(user) + if len(users) > 0: + return Response({"msg": "Match {} s users".format(len(users))}) + else: + return Response({"error": "Have user but attr mapping error"}, status=401) + else: + return Response({"error": str(serializer.errors)}, status=401) + + +class DjangoSettingsAPI(APIView): + def get(self, request): + configs = {} + for i in dir(settings): + if i.isupper(): + configs[i] = str(getattr(settings, i)) + return Response(configs) + diff --git a/apps/common/fields.py b/apps/common/fields.py new file mode 100644 index 000000000..36a8bdf9a --- /dev/null +++ b/apps/common/fields.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +import json + +from django import forms +from django.utils import six +from django.core.exceptions import ValidationError + + +class DictField(forms.Field): + widget = forms.Textarea + + def to_python(self, value): + """Returns a Python boolean object.""" + # Explicitly check for the string 'False', which is what a hidden field + # will submit for False. Also check for '0', since this is what + # RadioSelect will provide. Because bool("True") == bool('1') == True, + # we don't need to handle that explicitly. + if isinstance(value, six.string_types): + try: + print(value) + value = json.loads(value) + return value + except json.JSONDecodeError: + pass + value = {} + return value + + def validate(self, value): + print(value) + if not value and self.required: + raise ValidationError(self.error_messages['required'], code='required') + + def has_changed(self, initial, data): + # Sometimes data or initial may be a string equivalent of a boolean + # so we should run it through to_python first to get a boolean value + return self.to_python(initial) != self.to_python(data) diff --git a/apps/common/forms.py b/apps/common/forms.py index 60cb1ae37..073bb671f 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from django.db import transaction from .models import Setting +from .fields import DictField def to_model_value(value): @@ -18,7 +19,10 @@ def to_model_value(value): def to_form_value(value): try: - return json.loads(value) + data = json.loads(value) + if isinstance(data, dict): + data = value + return data except json.JSONDecodeError: return '' @@ -26,23 +30,26 @@ def to_form_value(value): 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) + 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") + settings = Setting.objects.all() 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 + if value == to_form_value(getattr(settings, name).value): + continue + defaults = { 'name': name, 'value': to_model_value(value) @@ -52,6 +59,24 @@ class BaseForm(forms.Form): raise ValueError(self.errors) +class BasicSettingForm(BaseForm): + SITE_URL = forms.URLField( + label=_("Current SITE URL"), + help_text="http://jumpserver.abc.com:8080" + ) + USER_GUIDE_URL = forms.URLField( + label=_("User Guide URL"), + help_text=_("User first login update profile done redirect to it") + ) + EMAIL_SUBJECT_PREFIX = forms.CharField( + max_length=1024, label=_("Email Subject Prefix"), + initial="[Jumpserver] " + ) + AUTH_LDAP = forms.BooleanField( + label=_("Enable LDAP Auth"), initial=False, required=False + ) + + class EmailSettingForm(BaseForm): EMAIL_HOST = forms.CharField( max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org' @@ -72,3 +97,35 @@ class EmailSettingForm(BaseForm): label=_("Use TLS"), initial=False, required=False, help_text=_("If SMTP port is 587, may be select") ) + + +class LDAPSettingForm(BaseForm): + AUTH_LDAP_SERVER_URI = forms.CharField( + label=_("LDAP server"), initial='ldap://localhost:389' + ) + AUTH_LDAP_BIND_DN = forms.CharField( + label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org' + ) + AUTH_LDAP_BIND_PASSWORD = forms.CharField( + label=_("Password"), initial='', + widget=forms.PasswordInput, required=False + ) + AUTH_LDAP_SEARCH_OU = forms.CharField( + label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org' + ) + AUTH_LDAP_SEARCH_FILTER = forms.CharField( + label=_("User search filter"), initial='(cn=%(user)s)' + ) + AUTH_LDAP_USER_ATTR_MAP = DictField( + label=_("User attr map"), + initial=json.dumps({ + "username": "cn", + "name": "sn", + "email": "mail" + }) + ) + # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU + # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER + AUTH_LDAP_START_TLS = forms.BooleanField( + label=_("Use SSL"), initial=False, required=False + ) diff --git a/apps/common/models.py b/apps/common/models.py index 3d6ee6008..091b8b83a 100644 --- a/apps/common/models.py +++ b/apps/common/models.py @@ -1,8 +1,10 @@ import json +import ldap from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django_auth_ldap.config import LDAPSearch class SettingQuerySet(models.QuerySet): @@ -30,6 +32,13 @@ class Setting(models.Model): def __str__(self): return self.name + @property + def value_(self): + try: + return json.loads(self.value) + except json.JSONDecodeError: + return None + @classmethod def refresh_all_settings(cls): settings_list = cls.objects.all() @@ -43,5 +52,17 @@ class Setting(models.Model): return setattr(settings, self.name, value) + if self.name == "AUTH_LDAP": + if self.value_ and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS: + settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND) + elif not self.value_ and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS: + settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND) + + if self.name == "AUTH_LDAP_SEARCH_FILTER": + settings.AUTH_LDAP_USER_SEARCH = LDAPSearch( + settings.AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, + settings.AUTH_LDAP_SEARCH_FILTER, + ) + class Meta: db_table = "settings" diff --git a/apps/common/serializers.py b/apps/common/serializers.py index 37e6555a3..9d389776d 100644 --- a/apps/common/serializers.py +++ b/apps/common/serializers.py @@ -8,3 +8,14 @@ class MailTestSerializer(serializers.Serializer): EMAIL_HOST_PASSWORD = serializers.CharField() EMAIL_USE_SSL = serializers.BooleanField(default=False) EMAIL_USE_TLS = serializers.BooleanField(default=False) + + +class LDAPTestSerializer(serializers.Serializer): + AUTH_LDAP_SERVER_URI = serializers.CharField(max_length=1024) + AUTH_LDAP_BIND_DN = serializers.CharField(max_length=1024) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField() + AUTH_LDAP_SEARCH_OU = serializers.CharField() + AUTH_LDAP_SEARCH_FILTER = serializers.CharField() + AUTH_LDAP_USER_ATTR_MAP = serializers.CharField() + AUTH_LDAP_START_TLS = serializers.BooleanField(required=False) + diff --git a/apps/common/signals.py b/apps/common/signals.py index de8a84139..6edf140e2 100644 --- a/apps/common/signals.py +++ b/apps/common/signals.py @@ -4,3 +4,4 @@ from django.dispatch import Signal django_ready = Signal() +ldap_auth_enable = Signal(providing_args=["enabled"]) diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py index 6b54706db..076ea7925 100644 --- a/apps/common/signals_handler.py +++ b/apps/common/signals_handler.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- # - +import ldap from django.dispatch import receiver from django.db.models.signals import post_save +from django.conf import settings +from django_auth_ldap.config import LDAPSearch from .models import Setting from .utils import get_logger -from .signals import django_ready - +from .signals import django_ready, ldap_auth_enable logger = get_logger(__file__) @@ -25,3 +26,17 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): logger.debug("Receive django ready signal") logger.debug(" - fresh all settings") Setting.refresh_all_settings() + + +@receiver(ldap_auth_enable, dispatch_uid="my_unique_identifier") +def ldap_auth_on_changed(sender, enabled=True, **kwargs): + if enabled: + logger.debug("Enable LDAP auth") + if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND: + settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND) + + else: + logger.debug("Disable LDAP auth") + if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS: + settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND) + diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html new file mode 100644 index 000000000..e24f1d6a8 --- /dev/null +++ b/apps/common/templates/common/basic_setting.html @@ -0,0 +1,101 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + {% csrf_token %} + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
+ +
+
+ {{ field }} +
+
+ {{ field.help_text }} +
+
+
+ {% endif %} + {% endfor %} +
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html index d6d7fdf16..7561849bd 100644 --- a/apps/common/templates/common/email_setting.html +++ b/apps/common/templates/common/email_setting.html @@ -4,10 +4,6 @@ {% load i18n %} {% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} {% block content %}
@@ -16,10 +12,13 @@ @@ -53,6 +52,7 @@
+
@@ -69,5 +69,33 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/common/templates/common/ldap_setting.html b/apps/common/templates/common/ldap_setting.html new file mode 100644 index 000000000..26f021569 --- /dev/null +++ b/apps/common/templates/common/ldap_setting.html @@ -0,0 +1,100 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + {% csrf_token %} + {% for field in form %} + {% if not field.field|is_bool_field %} + {% bootstrap_field field layout="horizontal" %} + {% else %} +
+ +
+
+ {{ field }} +
+
+ {{ field.help_text }} +
+
+
+ {% endif %} + {% endfor %} +
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 81acabae1..a9075e66e 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -8,4 +8,6 @@ app_name = 'common' urlpatterns = [ url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), + url(r'^v1/ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), + url(r'^v1/django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), ] diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py index c2ea04207..ff8086bde 100644 --- a/apps/common/urls/view_urls.py +++ b/apps/common/urls/view_urls.py @@ -7,5 +7,7 @@ from .. import views app_name = 'common' urlpatterns = [ + url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'), url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), + url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), ] diff --git a/apps/common/utils.py b/apps/common/utils.py index 753fdd541..f366e6786 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -91,7 +91,7 @@ class Signer(metaclass=Singleton): def date_expired_default(): try: - years = int(settings.CONFIG.DEFAULT_EXPIRED_YEARS) + years = int(settings.DEFAULT_EXPIRED_YEARS) except TypeError: years = 70 return timezone.now() + timezone.timedelta(days=365*years) diff --git a/apps/common/views.py b/apps/common/views.py index f6e32af5e..ec9e54995 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,33 +1,85 @@ -from django.views.generic import View -from django.shortcuts import render +from django.views.generic import View, TemplateView +from django.shortcuts import render, redirect from django.contrib import messages from django.utils.translation import ugettext as _ -from .forms import EmailSettingForm +from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm from .mixins import AdminUserRequiredMixin +from .signals import ldap_auth_enable -class EmailSettingView(AdminUserRequiredMixin, View): +class BasicSettingView(AdminUserRequiredMixin, TemplateView): + form_class = BasicSettingForm + template_name = "common/basic_setting.html" + + def get_context_data(self, **kwargs): + context = { + 'app': _('Settings'), + 'action': _('Basic setting'), + 'form': self.form_class(), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def post(self, request): + form = self.form_class(request.POST) + if form.is_valid(): + form.save() + if "AUTH_LDAP" in form.cleaned_data: + ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"]) + messages.success(request, _("Update basic setting successfully")) + return redirect('settings:basic-setting') + else: + context = self.get_context_data() + context.update({"form": form}) + return render(request, self.template_name, context) + + +class EmailSettingView(AdminUserRequiredMixin, TemplateView): form_class = EmailSettingForm template_name = "common/email_setting.html" - def get(self, request): + def get_context_data(self, **kwargs): context = { - 'app': 'settings', - 'action': 'Email setting', - "form": EmailSettingForm(), + 'app': _('Settings'), + 'action': _('Email setting'), + 'form': self.form_class(), } - return render(request, self.template_name, context) + kwargs.update(context) + return super().get_context_data(**kwargs) def post(self, request): form = self.form_class(request.POST) if form.is_valid(): form.save() messages.success(request, _("Update email setting successfully")) + return redirect('settings:email-setting') + else: + context = self.get_context_data() + context.update({"form": form}) + return render(request, self.template_name, context) + +class LDAPSettingView(AdminUserRequiredMixin, TemplateView): + form_class = LDAPSettingForm + template_name = "common/ldap_setting.html" + + def get_context_data(self, **kwargs): context = { - 'app': 'settings', - 'action': 'Email setting', - "form": EmailSettingForm(), + 'app': _('Settings'), + 'action': _('LDAP setting'), + 'form': self.form_class(), } - return render(request, self.template_name, context) + kwargs.update(context) + return super().get_context_data(**kwargs) + + def post(self, request): + form = self.form_class(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("Update ldap setting successfully")) + return redirect('settings:ldap-setting') + else: + context = self.get_context_data() + context.update({"form": form}) + return render(request, self.template_name, context) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5d265aed3..5bdc932e3 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -15,7 +15,6 @@ import sys import ldap from django_auth_ldap.config import LDAPSearch - from django.urls import reverse_lazy @@ -303,18 +302,28 @@ AUTH_USER_MODEL = 'users.User' # Auth LDAP settings -if CONFIG.AUTH_LDAP: - AUTHENTICATION_BACKENDS.insert(0, 'django_auth_ldap.backend.LDAPBackend') - AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI - AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN - AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD - AUTH_LDAP_USER_SEARCH = LDAPSearch( - CONFIG.AUTH_LDAP_SEARCH_OU, - ldap.SCOPE_SUBTREE, - CONFIG.AUTH_LDAP_SEARCH_FILTER - ) - AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS - AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP +AUTH_LDAP = CONFIG.AUTH_LDAP +AUTH_LDAP_SERVER_URI = CONFIG.AUTH_LDAP_SERVER_URI +AUTH_LDAP_BIND_DN = CONFIG.AUTH_LDAP_BIND_DN +AUTH_LDAP_BIND_PASSWORD = CONFIG.AUTH_LDAP_BIND_PASSWORD +AUTH_LDAP_SEARCH_OU = CONFIG.AUTH_LDAP_SEARCH_OU +AUTH_LDAP_SEARCH_FILTER = CONFIG.AUTH_LDAP_SEARCH_FILTER +AUTH_LDAP_START_TLS = CONFIG.AUTH_LDAP_START_TLS +AUTH_LDAP_USER_ATTR_MAP = CONFIG.AUTH_LDAP_USER_ATTR_MAP +AUTH_LDAP_USER_SEARCH = LDAPSearch( + AUTH_LDAP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER, +) +AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU +AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER +AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER +) +AUTH_LDAP_ALWAYS_UPDATE_USER = True +AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend' + +if AUTH_LDAP: + AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND) + # Celery using redis as broker CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % { @@ -367,3 +376,7 @@ BOOTSTRAP3 = { 'set_placeholder': True, 'success_css_class': '', } + +TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600 +DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE +DEFAULT_EXPIRED_YEARS = 70 diff --git a/apps/ops/views.py b/apps/ops/views.py index 4b090a8a3..ba9e2cfeb 100644 --- a/apps/ops/views.py +++ b/apps/ops/views.py @@ -10,7 +10,7 @@ from .hands import AdminUserRequiredMixin class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE model = Task ordering = ('-date_created',) context_object_name = 'task_list' diff --git a/apps/perms/views.py b/apps/perms/views.py index 6fb2cbca7..39a901557 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -93,7 +93,7 @@ class AssetPermissionUserView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_user.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): @@ -123,7 +123,7 @@ class AssetPermissionAssetView(AdminUserRequiredMixin, ListView): template_name = 'perms/asset_permission_asset.html' context_object_name = 'asset_permission' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE object = None def get(self, request, *args, **kwargs): diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 27b73dce0..119151897 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -157,6 +157,11 @@ function APIUpdateAttr(props) { props = props || {}; var success_message = props.success_message || '更新成功!'; var fail_message = props.fail_message || '更新时发生未知错误.'; + var flash_message = true; + if (props.flash_message === false){ + flash_message = false; + } + $.ajax({ url: props.url, type: props.method || "PATCH", @@ -164,12 +169,16 @@ function APIUpdateAttr(props) { contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function(data, textStatue, jqXHR) { - toastr.success(success_message); + if (flash_message) { + toastr.success(success_message); + } if (typeof props.success === 'function') { return props.success(data); } }).fail(function(jqXHR, textStatus, errorThrown) { - toastr.error(fail_message); + if (flash_message) { + toastr.error(fail_message); + } if (typeof props.error === 'function') { return props.error(jqXHR.responseText); } diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 633858eda..1493e55dc 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -69,8 +69,8 @@ {#
  • {% trans 'File download' %}
  • #} {# #} {##} -{#
  • #} -{# #} -{# {% trans 'Settings' %}#} -{# #} -{#
  • #} \ No newline at end of file +
  • + + {% trans 'Settings' %} + +
  • \ No newline at end of file diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 2bd588ff6..8b0479d3e 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -19,7 +19,7 @@ class CommandListView(DatetimeSearchMixin, ListView): model = Command template_name = "terminal/command_list.html" context_object_name = 'command_list' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE command = user = asset = system_user = "" date_from = date_to = None diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 087e16b68..22d04fbaf 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -26,7 +26,7 @@ class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): model = Session template_name = 'terminal/session_list.html' context_object_name = 'session_list' - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE user = asset = system_user = '' date_from = date_to = None diff --git a/apps/users/authentication.py b/apps/users/authentication.py index 09321953e..647f9a776 100644 --- a/apps/users/authentication.py +++ b/apps/users/authentication.py @@ -113,7 +113,7 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication): keyword = 'Bearer' model = User - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 + expiration = settings.TOKEN_EXPIRATION or 3600 def authenticate(self, request): auth = authentication.get_authorization_header(request).split() diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 031e4fa77..4ae0e62c4 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -148,12 +148,7 @@ class User(AbstractUser): def save(self, *args, **kwargs): if not self.name: self.name = self.username - super().save(*args, **kwargs) - # Add the current user to the default group. - if not self.groups.count(): - group = UserGroup.initial() - self.groups.add(group) @property def private_token(self): @@ -253,6 +248,7 @@ class User(AbstractUser): #: Use this method initial user @classmethod def initial(cls): + from .group import UserGroup user = cls(username='admin', email='admin@jumpserver.org', name=_('Administrator'), @@ -268,6 +264,7 @@ class User(AbstractUser): from random import seed, choice import forgery_py from django.db import IntegrityError + from .group import UserGroup seed() for i in range(count): diff --git a/apps/users/signals.py b/apps/users/signals.py index 537cfb329..39e57f5a5 100644 --- a/apps/users/signals.py +++ b/apps/users/signals.py @@ -2,16 +2,19 @@ # from django.dispatch import Signal, receiver +from django.db.models.signals import post_save from common.utils import get_logger +from .models import User logger = get_logger(__file__) -on_user_created = Signal(providing_args=['user', 'request']) -@receiver(on_user_created) -def send_user_add_mail_to_user(sender, user=None, **kwargs): - from .utils import send_user_created_mail - logger.debug("Receive asset create signal, update asset hardware info") - send_user_created_mail(user) +@receiver(post_save, sender=User) +def on_user_created(sender, instance=None, created=False, **kwargs): + if created: + logger.debug("Receive user `{}` create signal".format(instance.name)) + from .utils import send_user_created_mail + logger.info(" - Sending welcome mail ...".format(instance.name)) + send_user_created_mail(instance) diff --git a/apps/users/utils.py b/apps/users/utils.py index 02ab846f5..685bf31d6 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -151,12 +151,12 @@ def check_user_valid(**kwargs): return None, _('Password or SSH public key invalid') -def refresh_token(token, user, expiration=settings.CONFIG.TOKEN_EXPIRATION or 3600): +def refresh_token(token, user, expiration=settings.TOKEN_EXPIRATION or 3600): cache.set(token, user.id, expiration) def generate_token(request, user): - expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 + expiration = settings.TOKEN_EXPIRATION or 3600 remote_addr = request.META.get('REMOTE_ADDR', '') if not isinstance(remote_addr, bytes): remote_addr = remote_addr.encode("utf-8") diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 493e4936c..a5b78cc73 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -185,7 +185,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): user.is_public_key_valid = True user.save() context = { - 'user_guide_url': settings.CONFIG.USER_GUIDE_URL + 'user_guide_url': settings.USER_GUIDE_URL } return render(self.request, 'users/first_login_done.html', context) @@ -216,7 +216,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): class LoginLogListView(DatetimeSearchMixin, ListView): template_name = 'users/login_log_list.html' model = LoginLog - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + paginate_by = settings.DISPLAY_PER_PAGE user = keyword = "" date_to = date_from = None diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 6b86ddc1b..9110e2887 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -76,7 +76,6 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): user = form.save(commit=False) user.created_by = self.request.user.username or 'System' user.save() - on_user_created.send(self.__class__, user=user) return super().form_valid(form)