diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index f32a0cb61..2d25fb4b2 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -2,8 +2,9 @@ # import random +import time -from rest_framework import generics +from rest_framework import generics, permissions from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView @@ -39,9 +40,10 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer pagination_class = LimitOffsetPagination - permission_classes = (IsOrgAdminOrAppUser,) + permission_classes = (permissions.AllowAny,) def get_queryset(self): + time.sleep(3) queryset = super().get_queryset()\ .prefetch_related('labels', 'nodes')\ .select_related('admin_user') diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index b6cd07c79..37bebfb84 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -2,12 +2,11 @@ from rest_framework_bulk import BulkModelViewSet from rest_framework.views import APIView, Response -from rest_condition import Or from django.views.generic.detail import SingleObjectMixin from common.utils import get_logger -from common.permissions import IsOrgAdmin, IsAppUser +from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser from ..models import Domain, Gateway from ..utils import test_gateway_connectability from .. import serializers diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index c0a4ee638..80b7ae596 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -44,7 +44,7 @@ class Gateway(AssetUser): ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) port = models.IntegerField(default=22, verbose_name=_('Port')) protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol")) - domain = models.ForeignKey(Domain, verbose_name=_("Domain")) + domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 8e9f9d250..9756d1e9d 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -35,6 +35,20 @@ .dropdown a:hover { background-color: #f1f1f1 } + + .dataTables_wrapper .dataTables_processing { + position: absolute; + top: 30%; + left: 50%; + width: 30%; + height: 40px; + margin-left: -20%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background: none; + } {% endblock %} diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 4b83ab6fc..ba490818d 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -44,7 +44,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): template_name = 'assets/asset_list.html' def get_context_data(self, **kwargs): - print(Node.root().name) + Node.root() context = { 'app': _('Assets'), 'action': _('Asset list'), diff --git a/apps/common/api.py b/apps/common/api.py index 0f440b7ab..b6658b665 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -86,7 +86,18 @@ class LDAPTestingAPI(APIView): class DjangoSettingsAPI(APIView): def get(self, request): - return Response('Danger, Close now') + if not settings.DEBUG: + return Response("Not in debug mode") + + data = {} + for k, v in settings.__dict__.items(): + if k and k.isupper(): + try: + json.dumps(v) + data[k] = v + except (json.JSONDecodeError, TypeError): + data[k] = str(v) + return Response(data) diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 5868429c3..c9015844c 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -3,6 +3,8 @@ from rest_framework import permissions from django.contrib.auth.mixins import UserPassesTestMixin +from django.shortcuts import redirect +from django.http.response import HttpResponseForbidden from orgs.utils import current_org @@ -23,6 +25,18 @@ class IsAppUser(IsValidUser): and request.user.is_app +class IsSuperUser(IsValidUser): + def has_permission(self, request, view): + return super(IsSuperUser, self).has_permission(request, view) \ + and request.user.is_superuser + + +class IsSuperUserOrAppUser(IsSuperUser): + 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 IsOrgAdmin(IsValidUser): """Allows access only to superuser""" @@ -63,3 +77,18 @@ class AdminUserRequiredMixin(UserPassesTestMixin): self.raise_exception = True return False return True + + def dispatch(self, request, *args, **kwargs): + print("Current org: {}".format(current_org)) + if not current_org: + return redirect('orgs:switch-a-org') + + if not current_org.can_admin_by(request.user): + print("{} cannot admin {}".format(request.user, current_org)) + if request.user.is_org_admin: + print("Is org admin") + return redirect('orgs:switch-a-org') + return HttpResponseForbidden() + else: + print(current_org.can_admin_by(request.user)) + return super().dispatch(request, *args, **kwargs) diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index ffc472ba0..731068d25 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -9,5 +9,5 @@ app_name = 'common' urlpatterns = [ url(r'^mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'), url(r'^ldap/testing/$', api.LDAPTestingAPI.as_view(), name='ldap-testing'), - url(r'^django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), + # url(r'^django-settings/$', api.DjangoSettingsAPI.as_view(), name='django-settings'), ] diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 37246fa16..00bd788cd 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -66,6 +66,7 @@ INSTALLED_APPS = [ 'audits.apps.AuditsConfig', 'rest_framework', 'rest_framework_swagger', + 'drf_yasg', 'django_filters', 'bootstrap3', 'captcha', @@ -230,7 +231,7 @@ LOGGING = { 'level': LOG_LEVEL, }, 'django_auth_ldap': { - 'handlers': ['console', 'ansible_logs'], + 'handlers': ['console', 'file'], 'level': "INFO", }, # 'django.db': { @@ -294,6 +295,7 @@ REST_FRAMEWORK = { 'common.permissions.IsOrgAdmin', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.BasicAuthentication', 'users.authentication.AccessKeyAuthentication', 'users.authentication.AccessTokenAuthentication', 'users.authentication.PrivateTokenAuthentication', @@ -375,7 +377,7 @@ CACHES = { 'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '', 'host': CONFIG.REDIS_HOST or '127.0.0.1', 'port': CONFIG.REDIS_PORT or 6379, - 'db':CONFIG.REDIS_DB_CACHE or 4, + 'db': CONFIG.REDIS_DB_CACHE or 4, } } } @@ -425,3 +427,12 @@ TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION or 3600 DISPLAY_PER_PAGE = CONFIG.DISPLAY_PER_PAGE or 25 DEFAULT_EXPIRED_YEARS = 70 USER_GUIDE_URL = "" + + +SWAGGER_SETTINGS = { + 'SECURITY_DEFINITIONS': { + 'basic': { + 'type': 'basic' + } + }, +} \ No newline at end of file diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 921481c27..e76b4f3a2 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -9,13 +9,28 @@ from rest_framework.response import Response from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from django.utils.encoding import iri_to_uri +from rest_framework import permissions -from rest_framework.schemas import get_schema_view -from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer +# from rest_framework.schemas import get_schema_view +# from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer +from drf_yasg.views import get_schema_view +from drf_yasg import openapi from .views import IndexView, LunaView -schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]) +# schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]) +schema_view = get_schema_view( + openapi.Info( + title="Snippets API", + default_version='v1', + description="Test description", + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="contact@snippets.local"), + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=(permissions.AllowAny,), +) api_url_pattern = re.compile(r'^/api/(?P\w+)/(?P\w+)/(?P.*)$') @@ -85,5 +100,7 @@ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ if settings.DEBUG: urlpatterns += [ - url(r'^docs/', schema_view, name="docs"), + url(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'), + url(r'^docs/', schema_view.with_ui('swagger', cache_timeout=None), name="docs"), + url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='redoc'), ] diff --git a/apps/jumpserver/views.py b/apps/jumpserver/views.py index 323f71dd2..4dc3a3dc7 100644 --- a/apps/jumpserver/views.py +++ b/apps/jumpserver/views.py @@ -4,16 +4,15 @@ from django.http import HttpResponse from django.views.generic import TemplateView, View from django.utils import timezone from django.db.models import Count -from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect from users.models import User from assets.models import Asset from terminal.models import Session -from orgs.mixins import OrgViewGenericMixin +from common.permissions import AdminUserRequiredMixin -class IndexView(LoginRequiredMixin, OrgViewGenericMixin, TemplateView): +class IndexView(AdminUserRequiredMixin, TemplateView): template_name = 'index.html' session_week = None diff --git a/apps/ops/api.py b/apps/ops/api.py index ff8e71795..4fd1111d7 100644 --- a/apps/ops/api.py +++ b/apps/ops/api.py @@ -19,6 +19,8 @@ class TaskViewSet(viewsets.ModelViewSet): queryset = Task.objects.all() serializer_class = TaskSerializer permission_classes = (IsOrgAdmin,) + label = None + help_text = '' class TaskRun(generics.RetrieveAPIView): diff --git a/apps/orgs/api.py b/apps/orgs/api.py new file mode 100644 index 000000000..14d837841 --- /dev/null +++ b/apps/orgs/api.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import viewsets + +from common.permissions import IsOrgAdminOrAppUser +from .models import Organization +from .serializers import OrgSerializer + + +class OrgViewSet(viewsets.ModelViewSet): + queryset = Organization.objects.all() + serializer_class = OrgSerializer + permission_classes = (IsOrgAdminOrAppUser,) diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py index 215518e68..244eebdb9 100644 --- a/apps/orgs/mixins.py +++ b/apps/orgs/mixins.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # +from threading import local + from django.db import models from django.shortcuts import redirect import warnings @@ -11,9 +13,6 @@ from .utils import current_org, set_current_org from .models import Organization logger = get_logger(__file__) - -from threading import local - tl = local() __all__ = [ @@ -73,7 +72,7 @@ class OrgModelMixin(models.Model): class OrgViewGenericMixin: def dispatch(self, request, *args, **kwargs): - print("Crrent org: {}".format(current_org)) + print("Current org: {}".format(current_org)) if not current_org: return redirect('orgs:switch-a-org') @@ -83,6 +82,8 @@ class OrgViewGenericMixin: print("Is org admin") return redirect('orgs:switch-a-org') return HttpResponseForbidden() + else: + print(current_org.can_admin_by(request.user)) return super().dispatch(request, *args, **kwargs) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py new file mode 100644 index 000000000..8a2de4582 --- /dev/null +++ b/apps/orgs/serializers.py @@ -0,0 +1,10 @@ + +from rest_framework.serializers import ModelSerializer +from .models import Organization + + +class OrgSerializer(ModelSerializer): + class Meta: + model = Organization + fields = '__all__' + read_only_fields = ['id', 'created_by', 'date_created'] diff --git a/apps/orgs/urls/api_urls.py b/apps/orgs/urls/api_urls.py index 8c672a159..8f3d46b73 100644 --- a/apps/orgs/urls/api_urls.py +++ b/apps/orgs/urls/api_urls.py @@ -2,10 +2,17 @@ # from django.conf.urls import url -from .. import views + +from rest_framework.routers import DefaultRouter +from .. import api + app_name = 'orgs' +router = DefaultRouter() +router.register(r'orgs', api.OrgViewSet, 'org') + urlpatterns = [ - # url(r'^(?P[0-9a-zA-Z\-]{36})/$', views.OrgDetailView.as_view(), name='asset-index') -] \ No newline at end of file +] + +urlpatterns += router.urls diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index acf578447..80a5a9a46 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -16,6 +16,8 @@ _thread_locals = local() def get_org_from_request(request): oid = request.session.get("oid") + if not oid: + oid = request.META.get("HTTP_X_JMS_ORG") org = Organization.get_instance(oid) return org diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index c18f12224..e9edd7637 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -250,6 +250,8 @@ function initTree() { {#$.fn.zTree.init($("#assetTree"), setting);#} $.fn.zTree.init($("#assetTree"), setting, zNodes); zTree = $.fn.zTree.getZTreeObj("assetTree"); + var root = zTree.getNodes()[0]; + zTree.expandNode(root); {#selectQueryNode();#} }); } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 07405fb4f..4cc0a3e1e 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -250,6 +250,22 @@ function makeLabel(data) { var jumpserver = {}; jumpserver.checked = false; jumpserver.selected = {}; +jumpserver.language = { + processing: "加载中", + search: "搜索", + lengthMenu: "每页 _MENU_", + info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项", + infoFiltered: "", + infoEmpty: "", + zeroRecords: "没有匹配项", + emptyTable: "没有记录", + paginate: { + first: "«", + previous: "‹", + next: "›", + last: "»" + } +}; jumpserver.initDataTable = function (options) { // options = { // ele *: $('#dataTable_id'), @@ -293,21 +309,7 @@ jumpserver.initDataTable = function (options) { }, columns: options.columns || [], select: options.select || select, - language: { - search: "搜索", - lengthMenu: "每页 _MENU_", - info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项", - infoFiltered: "", - infoEmpty: "", - zeroRecords: "没有匹配项", - emptyTable: "没有记录", - paginate: { - first: "«", - previous: "‹", - next: "›", - last: "»" - } - }, + language: jumpserver.language, lengthMenu: [[10, 15, 25, 50, -1], [10, 15, 25, 50, "All"]] }); table.on('select', function(e, dt, type, indexes) { @@ -343,6 +345,16 @@ jumpserver.initDataTable = function (options) { return table; }; +jumpserver.initStaticTable = function (selector) { + $(selector).DataTable({ + "searching": false, + "bInfo": false, + "paging": false, + "order": [], + "language": jumpserver.language + }); +}; + jumpserver.initServerSideDataTable = function (options) { // options = { // ele *: $('#dataTable_id'), @@ -374,11 +386,11 @@ jumpserver.initServerSideDataTable = function (options) { selector: 'td:first-child' }; var table = ele.DataTable({ - pageLength: options.pageLength || 15, - dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', - order: options.order || [], + // pageLength: options.pageLength || 15, + // dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', + // order: options.order || [], // select: options.select || 'multi', - buttons: [], + // buttons: [], columnDefs: columnDefs, serverSide: true, processing: true, @@ -432,21 +444,7 @@ jumpserver.initServerSideDataTable = function (options) { }, columns: options.columns || [], select: options.select || select, - language: { - search: "搜索", - lengthMenu: "每页 _MENU_", - info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项", - infoFiltered: "", - infoEmpty: "", - zeroRecords: "没有匹配项", - emptyTable: "没有记录", - paginate: { - first: "«", - previous: "‹", - next: "›", - last: "»" - } - }, + language: jumpserver.language, lengthMenu: [[10, 15, 25, 50], [10, 15, 25, 50]] }); table.selected = []; diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index ed2e67921..a792beb48 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -64,9 +64,12 @@ {% trans 'Web terminal' %} + {% if request.user.is_superuser %}
  • {% trans 'Terminal' %}
  • + {% endif %} +{% if request.user.is_superuser %}
  • {% trans 'Job Center' %} @@ -75,6 +78,7 @@
  • {% trans 'Task list' %}
  • +{% endif %}
  • {% trans 'Audits' %} @@ -92,11 +96,13 @@ {#
  • {% trans 'File download' %}
  • #} {# #} {##} +{% if request.user.is_superuser %}
  • {% trans 'Settings' %}
  • +{% endif %}