* [Update] 添加org api, 升级到django 2.0

* [Update] fix some bug

* [Update] 修改一些bug
pull/1580/head^2
老广 2018-07-24 22:21:12 -05:00 committed by GitHub
parent c816875f28
commit b156f4ad16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 200 additions and 87 deletions

View File

@ -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')

View File

@ -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

View File

@ -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"))

View File

@ -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;
}
</style>
{% endblock %}

View File

@ -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'),

View File

@ -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)

View File

@ -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)

View File

@ -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'),
]

View File

@ -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'
}
},
}

View File

@ -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<app>\w+)/(?P<version>\w+)/(?P<extra>.*)$')
@ -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<format>\.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'),
]

View File

@ -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

View File

@ -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):

14
apps/orgs/api.py Normal file
View File

@ -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,)

View File

@ -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)

10
apps/orgs/serializers.py Normal file
View File

@ -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']

View File

@ -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<pk>[0-9a-zA-Z\-]{36})/$', views.OrgDetailView.as_view(), name='asset-index')
]
]
urlpatterns += router.urls

View File

@ -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

View File

@ -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();#}
});
}

View File

@ -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 = [];

View File

@ -64,9 +64,12 @@
<span class="nav-label">{% trans 'Web terminal' %}</span>
</a>
</li>
{% if request.user.is_superuser %}
<li id="terminal"><a href="{% url 'terminal:terminal-list' %}">{% trans 'Terminal' %}</a></li>
{% endif %}
</ul>
</li>
{% if request.user.is_superuser %}
<li id="ops">
<a>
<i class="fa fa-coffee" style="width: 14px"></i> <span class="nav-label">{% trans 'Job Center' %}</span><span class="fa arrow"></span>
@ -75,6 +78,7 @@
<li id="task"><a href="{% url 'ops:task-list' %}">{% trans 'Task list' %}</a></li>
</ul>
</li>
{% endif %}
<li id="audits">
<a>
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
@ -92,11 +96,13 @@
{# <li id="download"><a href="">{% trans 'File download' %}</a></li>#}
{# </ul>#}
{#</li>#}
{% if request.user.is_superuser %}
<li id="settings">
<a href="{% url 'settings:basic-setting' %}">
<i class="fa fa-gears"></i> <span class="nav-label">{% trans 'Settings' %}</span><span class="label label-info pull-right"></span>
</a>
</li>
{% endif %}
<script>
$(document).ready(function () {

View File

@ -150,12 +150,7 @@
});
}
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": []
});
jumpserver.initStaticTable('table');
$('.select2').select2({
dropdownAutoWidth: true,
width: "auto"

View File

@ -10,10 +10,10 @@ from .. import api
app_name = 'terminal'
router = routers.DefaultRouter()
router.register(r'terminal', api.TerminalViewSet, 'terminal2')
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'terminal', api.TerminalViewSet, 'terminal')
router.register(r'command', api.CommandViewSet, 'command')
router.register(r'sessions', api.SessionViewSet, 'session')
router.register(r'status', api.StatusViewSet, 'session')

View File

@ -195,7 +195,7 @@ class User(AbstractUser):
@property
def is_org_admin(self):
if self.is_superuser or self.admin_orgs:
if self.is_superuser or self.admin_orgs.exists():
return True
else:
return False
@ -223,7 +223,7 @@ class User(AbstractUser):
self.is_active = True
super().save(*args, **kwargs)
if current_org and current_org.is_real():
self.orgs.add(current_org)
self.orgs.add(current_org.id)
@property
def private_token(self):

View File

@ -80,12 +80,7 @@
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"bInfo" : false,
"paging": false,
"order": []
});
jumpserver.initStaticTable('table');
$('#date .input-daterange').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",

View File

@ -1,7 +1,6 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext as _
from django.urls import reverse_lazy
from django.views.generic.base import TemplateView
@ -11,9 +10,8 @@ from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger
from common.const import create_success_msg, update_success_msg
from orgs.mixins import OrgViewGenericMixin
from ..models import User, UserGroup
from common.permissions import AdminUserRequiredMixin
from ..models import User, UserGroup
from .. import forms
__all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
@ -21,7 +19,7 @@ __all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
logger = get_logger(__name__)
class UserGroupListView(AdminUserRequiredMixin, OrgViewGenericMixin, TemplateView):
class UserGroupListView(AdminUserRequiredMixin, TemplateView):
template_name = 'users/user_group_list.html'
def get_context_data(self, **kwargs):
@ -33,8 +31,7 @@ class UserGroupListView(AdminUserRequiredMixin, OrgViewGenericMixin, TemplateVie
return super().get_context_data(**kwargs)
class UserGroupCreateView(AdminUserRequiredMixin, OrgViewGenericMixin,
SuccessMessageMixin, CreateView):
class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = UserGroup
form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html'
@ -50,8 +47,7 @@ class UserGroupCreateView(AdminUserRequiredMixin, OrgViewGenericMixin,
return super().get_context_data(**kwargs)
class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin,
OrgViewGenericMixin, UpdateView):
class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
model = UserGroup
form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html'
@ -71,7 +67,7 @@ class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin,
return super().get_context_data(**kwargs)
class UserGroupDetailView(AdminUserRequiredMixin, OrgViewGenericMixin, DetailView):
class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
model = UserGroup
context_object_name = 'user_group'
template_name = 'users/user_group_detail.html'
@ -87,7 +83,7 @@ class UserGroupDetailView(AdminUserRequiredMixin, OrgViewGenericMixin, DetailVie
return super().get_context_data(**kwargs)
class UserGroupGrantedAssetView(AdminUserRequiredMixin, OrgViewGenericMixin, DetailView):
class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView):
model = UserGroup
template_name = 'users/user_group_granted_asset.html'
context_object_name = 'user_group'

View File

@ -308,7 +308,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
file_storage = default_storage
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and not request.user.is_first_login:
if request.user.is_authenticated and not request.user.is_first_login:
return redirect(reverse('index'))
return super().dispatch(request, *args, **kwargs)

View File

@ -57,7 +57,7 @@ __all__ = [
logger = get_logger(__name__)
class UserListView(TemplateView):
class UserListView(AdminUserRequiredMixin, TemplateView):
template_name = 'users/user_list.html'
def get_context_data(self, **kwargs):

View File

@ -14,7 +14,8 @@ coreapi==2.3.3
coreschema==0.0.4
cryptography==2.1.4
decorator==4.1.2
Django==1.11
#Django==1.11
#Django==2.0.7
django-auth-ldap==1.3.0
django-bootstrap3==9.1.0
django-celery-beat==1.1.1
@ -24,7 +25,8 @@ django-ranged-response==0.2.0
django-redis-cache==1.7.1
django-rest-swagger==2.1.2
django-simple-captcha==0.5.6
djangorestframework==3.7.3
#djangorestframework==3.7.3
djangorestframework==3.8.2
djangorestframework-bulk==0.2.1
docutils==0.14
ecdsa==0.13
@ -69,3 +71,4 @@ sshpubkeys==2.2.0
uritemplate==3.0.0
urllib3==1.22
vine==1.1.4
drf-yasg==1.9.1