diff --git a/apps/assets/api.py b/apps/assets/api.py index 2aa8c564c..f6d6926bb 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -1,33 +1,29 @@ # ~*~ coding: utf-8 ~*~ -from rest_framework import serializers -from rest_framework import viewsets, serializers, generics +from rest_framework import viewsets, generics, mixins from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView +from django.shortcuts import get_object_or_404 from common.mixins import BulkDeleteApiMixin from common.utils import get_object_or_none, signer from .hands import IsSuperUserOrTerminalUser, IsSuperUser -from .models import AssetGroup, Asset, IDC, SystemUser -from .serializers import AssetBulkUpdateSerializer +from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser +from . import serializers -class AssetGroupSerializer(serializers.ModelSerializer): - class Meta: - model = AssetGroup +class AssetViewSet(viewsets.ModelViewSet): + """API endpoint that allows Asset to be viewed or edited.""" + queryset = Asset.objects.all() + serializer_class = serializers.AssetSerializer - -class AssetSerializer(serializers.ModelSerializer): - class Meta: - model = Asset - # fields = ('id', 'title', 'code', 'linenos', 'language', 'style') - - -class IDCSerializer(serializers.ModelSerializer): - class Meta: - model = IDC - # fields = ('id', 'title', 'code', 'linenos', 'language', 'style') + def get_queryset(self): + queryset = super(AssetViewSet, self).get_queryset() + idc = self.request.query_params.get('idc', '') + if idc: + queryset = queryset.filter(idc__id=idc) + return queryset class AssetGroupViewSet(viewsets.ModelViewSet): @@ -35,26 +31,44 @@ class AssetGroupViewSet(viewsets.ModelViewSet): some other comment """ queryset = AssetGroup.objects.all() - serializer_class = AssetGroupSerializer + serializer_class = serializers.AssetGroupSerializer -class AssetViewSet(viewsets.ModelViewSet): - """API endpoint that allows Asset to be viewed or edited.""" - queryset = Asset.objects.all() - serializer_class = AssetSerializer - - -class IDCViewSet(viewsets.ReadOnlyModelViewSet): +class IDCViewSet(viewsets.ModelViewSet): """API endpoint that allows IDC to be viewed or edited.""" queryset = IDC.objects.all() - serializer_class = IDCSerializer + serializer_class = serializers.IDCSerializer permission_classes = (IsSuperUser,) +class AdminUserViewSet(viewsets.ModelViewSet): + queryset = AdminUser.objects.all() + serializer_class = serializers.AdminUserSerializer + permission_classes = (IsSuperUser,) + + +class SystemUserViewSet(viewsets.ModelViewSet): + queryset = SystemUser.objects.all() + serializer_class = serializers.SystemUserSerializer + permission_classes = (IsSuperUser,) + + +# class IDCAssetsApi(generics.ListAPIView): +# model = IDC +# serializer_class = serializers.AssetSerializer +# +# def get(self, request, *args, **kwargs): +# filter_kwargs = {self.lookup_field: self.kwargs[self.lookup_field]} +# self.object = get_object_or_404(self.model, **filter_kwargs) +# return super(IDCAssetsApi, self).get(request, *args, **kwargs) +# +# def get_queryset(self): +# return self.object.assets.all() + class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): queryset = Asset.objects.all() - serializer_class = AssetBulkUpdateSerializer + serializer_class = serializers.AssetSerializer permission_classes = (IsSuperUser,) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 5ec97d042..09fcc9a24 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -1,8 +1,9 @@ # coding:utf-8 from django import forms +from django.utils.translation import gettext_lazy as _ from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser, Tag -from django.utils.translation import gettext_lazy as _ +from common.utils import validate_ssh_private_key, ssh_pubkey_gen # class AssetForm(forms.ModelForm): @@ -23,12 +24,10 @@ from django.utils.translation import gettext_lazy as _ # class AssetCreateForm(forms.ModelForm): - def __init__(self, *args, **kwargs): instance = kwargs.get('instance', None) if instance: initial = kwargs.get('initial', {}) - #tags = instance.tags.all() initial['tags'] = [t.pk for t in kwargs['instance'].tags.all()] super(AssetCreateForm, self).__init__(*args, **kwargs) @@ -141,7 +140,6 @@ class AdminUserForm(forms.ModelForm): widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) ) - auto_generate_key = forms.BooleanField(required=True, initial=True) # Form field name can not start with `_`, so redefine it, password = forms.CharField(widget=forms.PasswordInput, max_length=100, min_length=8, strip=True, help_text=_('If also set private key, use that first'), required=False) @@ -166,21 +164,36 @@ class AdminUserForm(forms.ModelForm): # Because we define custom field, so we need rewrite :method: `save` admin_user = super(AdminUserForm, self).save(commit=commit) password = self.cleaned_data['password'] - private_key_file = self.cleaned_data['private_key_file'] + private_key = self.cleaned_data['private_key_file'] + public_key = ssh_pubkey_gen(private_key) if password: admin_user.password = password - print(password) - # Todo: Validate private key file, and generate public key - # Todo: Auto generate private key and public key - if private_key_file: - admin_user.private_key = private_key_file.read() + if private_key: + admin_user.private_key = private_key + admin_user.public_key = public_key admin_user.save() - return self.instance + return admin_user + + def clean_private_key_file(self): + private_key_file = self.cleaned_data['private_key_file'] + if private_key_file: + private_key = private_key_file.read() + if not validate_ssh_private_key(private_key): + raise forms.ValidationError(_('Invalid private key')) + return private_key + return private_key_file + + def clean(self): + password = self.cleaned_data['password'] + private_key_file = self.cleaned_data.get('private_key_file', '') + + if not (password or private_key_file): + raise forms.ValidationError(_('Password and private key file must be input one')) class Meta: model = AdminUser - fields = ['name', 'username', 'auto_generate_key', 'password', 'private_key_file', 'as_default', 'comment'] + fields = ['name', 'username', 'password', 'private_key_file', 'comment'] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), diff --git a/apps/assets/models.py b/apps/assets/models.py index 1103d0b4c..1b13b8621 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -6,8 +6,9 @@ from django.db import models from django.core import serializers import logging from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError -from common.utils import signer +from common.utils import signer, validate_ssh_private_key logger = logging.getLogger(__name__) @@ -91,13 +92,21 @@ class AssetExtend(models.Model): unique_together = ('key', 'value') +def private_key_validator(value): + if not validate_ssh_private_key(value): + raise ValidationError( + _('%(value)s is not an even number'), + params={'value': value}, + ) + + class AdminUser(models.Model): name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) username = models.CharField(max_length=16, verbose_name=_('Username')) - _password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) - _private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) + _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + _private_key = models.CharField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), + validators=[private_key_validator,]) _public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) - as_default = models.BooleanField(default=False, verbose_name=_('As default')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, null=True) created_by = models.CharField(max_length=32, null=True, verbose_name=_('Created by')) @@ -107,7 +116,7 @@ class AdminUser(models.Model): @property def password(self): - return decrypt(self._password) + return signer.unsign(self._password) @password.setter def password(self, password_raw): @@ -129,6 +138,10 @@ class AdminUser(models.Model): def public_key(self, public_key_raw): self._public_key = signer.sign(public_key_raw) + @property + def assets_amount(self): + return self.assets.count() + class Meta: db_table = 'admin_user' @@ -216,6 +229,10 @@ class SystemUser(models.Model): assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups() return list(assets) + @property + def assets_amount(self): + return self.assets.count() + class Meta: db_table = 'system_user' @@ -298,9 +315,8 @@ class Asset(models.Model): admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user")) system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User")) - idc = models.ForeignKey(IDC, null=True, related_name='assets', + idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('IDC'),) - # default=get_default_idc) mac_address = models.CharField(max_length=20, null=True, blank=True, verbose_name=_("Mac address")) brand = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Brand')) cpu = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU')) @@ -329,6 +345,7 @@ class Asset(models.Model): def __unicode__(self): return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port} + @property def is_valid(self): warning = '' if not self.is_active: diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index f5b53fb68..86acbff37 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -1,24 +1,86 @@ # -*- coding: utf-8 -*- from django.utils.translation import ugettext_lazy as _ from rest_framework import viewsets, serializers,generics -from .models import AssetGroup, Asset, IDC, AssetExtend +from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser from common.mixins import BulkDeleteApiMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin -class AssetBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): - # group_display = serializers.SerializerMethodField() - # active_display = serializers.SerializerMethodField() - #groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all()) +class AssetGroupSerializer(serializers.ModelSerializer): + assets_amount = serializers.SerializerMethodField() + # assets = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + + class Meta: + model = AssetGroup + + @staticmethod + def get_assets_amount(obj): + return obj.assets.count() + + +class AdminUserSerializer(serializers.ModelSerializer): + class Meta: + model = AdminUser + + def get_field_names(self, declared_fields, info): + fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info) + fields.append('assets_amount') + return fields + + +class SystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + exclude = ('_password', '_private_key', '_public_key') + + def get_field_names(self, declared_fields, info): + fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) + fields.append('assets_amount') + return fields + + +class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): + system_users = SystemUserSerializer(many=True, read_only=True) + admin_user = AdminUserSerializer(many=False, read_only=True) class Meta(object): model = Asset list_serializer_class = BulkListSerializer - fields = ['id', 'port', 'idc'] - # def get_group_display(self, obj): - # return " ".join([group.name for group in obj.groups.all()]) - # - # def get_active_display(self, obj): - # # TODO: user ative state - # return not (obj.is_expired and obj.is_active) \ No newline at end of file + +class AssetGrantedSerializer(serializers.ModelSerializer): + system_users = SystemUserSerializer(many=True, read_only=True) + is_inherited = serializers.SerializerMethodField() + system_users_join = serializers.SerializerMethodField() + + class Meta(object): + model = Asset + fields = ("id", "hostname", "ip", "port", "system_users", "is_inherited", + "is_active", "system_users_join", "comment") + + @staticmethod + def get_is_inherited(obj): + if getattr(obj, 'inherited', ''): + return True + else: + return False + + @staticmethod + def get_system_users_join(obj): + return ', '.join([system_user.username for system_user in obj.system_users.all()]) + + +class IDCSerializer(serializers.ModelSerializer): + assets_amount = serializers.SerializerMethodField() + + class Meta: + model = IDC + + @staticmethod + def get_assets_amount(obj): + return obj.assets.count() + + def get_field_names(self, declared_fields, info): + fields = super(IDCSerializer, self).get_field_names(declared_fields, info) + fields.append('assets_amount') + return fields diff --git a/apps/assets/templates/assets/admin_user_create_update.html b/apps/assets/templates/assets/admin_user_create_update.html index 7738adec0..575ab667a 100644 --- a/apps/assets/templates/assets/admin_user_create_update.html +++ b/apps/assets/templates/assets/admin_user_create_update.html @@ -31,20 +31,8 @@ {% csrf_token %} {{ form.name|bootstrap_horizontal }} {{ form.username|bootstrap_horizontal }} -
- -
- {{ form.auto_generate_key}} -
-
{{ form.password|bootstrap_horizontal }} {{ form.private_key_file|bootstrap_horizontal }} -
- -
- {{ form.as_default}} -
-
{{ form.assets|bootstrap_horizontal }} {{ form.comment|bootstrap_horizontal }} diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index ed41a5cfe..ea35972a0 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -1,41 +1,66 @@ {% extends '_base_list.html' %} -{% load i18n %} -{% load common_tags %} -{% block content_left_head %} - {% trans "Create admin user" %} +{% load i18n static %} +{% block table_search %} +{% endblock %} +{% block table_container %} +
+ {% trans "Create admin user" %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'Username' %}{% trans 'Asset num' %}{% trans 'Lost connection' %}{% trans 'Comment' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + {% endblock %} -{% block table_head %} - {% trans 'ID' %} - {% trans 'Name' %} - {% trans 'Username' %} - {% trans 'Asset num' %} - {% trans 'Lost connection' %} - {% trans 'Comment' %} - -{% endblock %} -{% block table_body %} - {% for admin_user in admin_user_list %} - - {{ admin_user.id }} - - - {{ admin_user.name }} - - - {{ admin_user.username }} - {{ admin_user.assets.count }} - {{ admin_user.assets.count }} - {{ admin_user.comment|truncatewords:8 }} - - - {% trans 'Script' %} - - {% trans 'Refresh' %} - {% trans 'Update' %} - {% trans 'Delete' %} - - - {% endfor %} -{% endblock %} + diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index e49540738..1a4aa38d5 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -30,12 +30,11 @@
- +
- - + {% endblock %} {% block custom_foot_js %} @@ -44,10 +43,9 @@ $('.select2').select2(); $("#id_tags").select2({ tags: true, - maximumSelectionLength: 8, //最多能够选择的个数 + maximumSelectionLength: 8 //最多能够选择的个数 //closeOnSelect: false }); }) - {% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/asset_group_create.html b/apps/assets/templates/assets/asset_group_create.html index f1df67d53..f2dbfd3ee 100644 --- a/apps/assets/templates/assets/asset_group_create.html +++ b/apps/assets/templates/assets/asset_group_create.html @@ -5,13 +5,6 @@ {% block custom_head_css_js %} - {% endblock %} {% block content %}
@@ -94,7 +87,7 @@ div.dataTables_wrapper div.dataTables_filter, $(document).ready(function () { $('.select2').select2(); $('.select2-system-user').select2(); - }) + }); $('#add_asset').on('click',function(){ $('#modal').modal('show'); @@ -104,7 +97,7 @@ div.dataTables_wrapper div.dataTables_filter, show: false, backdrop: 'static', keyboard: 'false', - remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}", + remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}" }); $('#modal').on('show.bs.modal',function(){ diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index cf2fa8e90..aadd5c388 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -7,19 +7,11 @@ {% endblock %} {% block content_left_head %}{% endblock %} @@ -80,7 +72,8 @@ div.dataTables_wrapper div.dataTables_filter, {% trans 'Update' %} - {% trans 'Delete' %} + + {% trans 'Delete' %} {% endfor %} @@ -190,18 +183,18 @@ div.dataTables_wrapper div.dataTables_filter, }else{ $(this).addClass('selected'); this.children[0].children[0].checked=1; - }; + } }); $('#btn_bulk_update').on('click',function(){ var column2 = table.rows('.selected').data(); var id_list = []; var plain_id_list = []; - var the_url = "{% url 'assets:asset-bulk-update-api' %}"; + var the_url = "{% url 'api-assets:asset-bulk-update' %}"; for(var i=0;i +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {% trans 'IDC assets' %} {{ idc.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + +
+ + {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Type' %}{% trans 'Valid' %}
+
+
+
+
+
+
+ {% trans 'Attach to assets ' %} +
+
+ + + + + + + + + + + +
+ +
+ +
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/idc_detail.html b/apps/assets/templates/assets/idc_detail.html new file mode 100644 index 000000000..dc3143213 --- /dev/null +++ b/apps/assets/templates/assets/idc_detail.html @@ -0,0 +1,138 @@ +{% extends 'base.html' %} +{% load common_tags %} +{% load users_tags %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ idc.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Name' %}:{{ idc.name }}
{% trans 'Bandwidth' %}:{{ idc.bandwidth }}
{% trans 'Contact' %}:{{ idc.contact }}
{% trans 'Phone' %}:{{ idc.phone }}
{% trans 'Address' %}:{{ idc.address }}
{% trans 'Intranet' %}:{{ idc.Intranet }}
{% trans 'Extranet' %}:{{ idc.extranet }}
{% trans 'Operator' %}:{{ idc.operator }}
{% trans 'Date created' %}:{{ system_user.date_created }}
{% trans 'Created by' %}:{{ asset_group.created_by }}
{% trans 'Comment' %}:{{ system_user.comment }}
+
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/idc_list.html b/apps/assets/templates/assets/idc_list.html index 810b2a102..f9ee96bb7 100644 --- a/apps/assets/templates/assets/idc_list.html +++ b/apps/assets/templates/assets/idc_list.html @@ -1,43 +1,64 @@ {% extends '_base_list.html' %} -{% load i18n %} -{% load common_tags %} -{% block content_left_head %} - {% trans "Create IDC" %} -{% endblock %} - -{% block table_head %} - - - - {% trans 'Name' %} - {% trans 'Asset num' %} - {% trans 'Contact' %} - {% trans 'Phone' %} - {% trans 'operation' %} -{% endblock %} - -{% block table_body %} - {% for idc in idc_list %} - - - - - {{ idc.name }} - {{ idc.assets.count }} -{# {{ idc.bandwidth }}#} - {{ idc.contact }} - {{ idc.phone }} -{# {{ idc.address }}#} - - {% trans 'Update' %} - {% trans 'Delete' %} - - - {% endfor %} +{% load i18n static %} +{% block table_search %}{% endblock %} +{% block table_container %} +
+ {% trans "Create IDC" %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'Asset num' %}{% trans 'Contact' %}{% trans 'Phone' %}{% trans 'Operator' %}{% trans 'Action' %}
{% endblock %} +{% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} {% endblock %} + + diff --git a/apps/assets/urls.py b/apps/assets/urls.py deleted file mode 100644 index 825b3e4ab..000000000 --- a/apps/assets/urls.py +++ /dev/null @@ -1,73 +0,0 @@ -# coding:utf-8 -from django.conf.urls import url, include -import views -import api -# from .api import ( -# AssetGroupViewSet, AssetViewSet, IDCViewSet -# ) -# from rest_framework import routers -# router = routers.DefaultRouter() -# router.register(r'assetgroup', AssetGroupViewSet) -# router.register(r'asset', AssetViewSet) -# router.register(r'idc', IDCViewSet) -app_name = 'assets' - -urlpatterns = [ - # Resource asset url - url(r'^$', views.AssetListView.as_view(), name='asset-index'), - url(r'^asset$', views.AssetListView.as_view(), name='asset-list'), - url(r'^asset/create$', views.AssetCreateView.as_view(), name='asset-create'), - url(r'^asset/(?P[0-9]+)$', views.AssetDetailView.as_view(), name='asset-detail'), - url(r'^asset/(?P[0-9]+)/update', views.AssetUpdateView.as_view(), name='asset-update'), - url(r'^asset/(?P[0-9]+)/delete$', views.AssetDeleteView.as_view(), name='asset-delete'), - url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'), - url(r'^asset-modal-update$', views.AssetModalCreateView.as_view(), name='asset-modal-update'), - - # Resource asset group url - url(r'^asset-group$', views.AssetGroupListView.as_view(), name='asset-group-list'), - url(r'^asset-group/create$', views.AssetGroupCreateView.as_view(), name='asset-group-create'), - url(r'^asset-group/(?P[0-9]+)$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'), - url(r'^asset-group/(?P[0-9]+)/update$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'), - url(r'^asset-group/(?P[0-9]+)/delete$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'), - - url(r'^tags$', views.TagsListView.as_view(), name='asset-tag-list'), - url(r'^asset-by-tag/(?P[0-9]+)$', views.TagView.as_view(), name='asset-tags'), - url(r'^tags/create$', views.AssetTagCreateView.as_view(), name='asset-tag-create'), - url(r'^asset-tag/(?P[0-9]+)$', views.AssetTagDetailView.as_view(), name='asset-tag-detail'), - url(r'^asset-tag/(?P[0-9]+)/update$', views.AssetTagUpdateView.as_view(), name='asset-tag-update'), - url(r'^asset-tag/(?P[0-9]+)/delete$', views.AssetTagDeleteView.as_view(), name='asset-tag-delete'), - - # Resource idc url - url(r'^idc$', views.IDCListView.as_view(), name='idc-list'), - url(r'^idc/create$', views.IDCCreateView.as_view(), name='idc-create'), - url(r'^idc/(?P[0-9]+)$', views.IDCDetailView.as_view(), name='idc-detail'), - url(r'^idc/(?P[0-9]+)/update', views.IDCUpdateView.as_view(), name='idc-update'), - url(r'^idc/(?P[0-9]+)/delete$', views.IDCDeleteView.as_view(), name='idc-delete'), - - # Resource admin user url - url(r'^admin-user$', views.AdminUserListView.as_view(), name='admin-user-list'), - url(r'^admin-user/create$', views.AdminUserCreateView.as_view(), name='admin-user-create'), - url(r'^admin-user/(?P[0-9]+)$', views.AdminUserDetailView.as_view(), name='admin-user-detail'), - url(r'^admin-user/(?P[0-9]+)/update', views.AdminUserUpdateView.as_view(), name='admin-user-update'), - url(r'^admin-user/(?P[0-9]+)/delete$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'), - - # Resource system user url - url(r'^system-user$', views.SystemUserListView.as_view(), name='system-user-list'), - url(r'^system-user/create$', views.SystemUserCreateView.as_view(), name='system-user-create'), - url(r'^system-user/(?P[0-9]+)$', views.SystemUserDetailView.as_view(), name='system-user-detail'), - url(r'^system-user/(?P[0-9]+)/update', views.SystemUserUpdateView.as_view(), name='system-user-update'), - url(r'^system-user/(?P[0-9]+)/delete$', views.SystemUserDeleteView.as_view(), name='system-user-delete'), - url(r'^system-user/(?P[0-9]+)/asset$', views.SystemUserAssetView.as_view(), name='system-user-asset'), - # url(r'^system-user/(?P[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(), - # name='system-user-asset-group'), - -] - -urlpatterns += [ - url(r'^v1/assets/$', api.AssetViewSet.as_view({'get':'list'}), name='assets-list-api'), - url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update-api'), - url(r'^v1/idc/$', api.IDCViewSet.as_view({'get':'list'}), name='idc-list-json'), - url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'), -] - - diff --git a/apps/assets/urls/__init__.py b/apps/assets/urls/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/assets/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py new file mode 100644 index 000000000..ca1760404 --- /dev/null +++ b/apps/assets/urls/api_urls.py @@ -0,0 +1,23 @@ +# coding:utf-8 +from django.conf.urls import url +from .. import api +from rest_framework import routers + +app_name = 'assets' + + +router = routers.DefaultRouter() +router.register(r'v1/asset-groups', api.AssetGroupViewSet, 'asset-group') +router.register(r'v1/assets', api.AssetViewSet, 'asset') +router.register(r'v1/idc', api.IDCViewSet, 'idc') +router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') +router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') + +urlpatterns = [ + url(r'^v1/assets_bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), + # url(r'^v1/idc/(?P[0-9]+)/assets/$', api.IDCAssetsApi.as_view(), name='api-idc-assets'), + url(r'^v1/system-user/auth/', api.SystemUserAuthApi.as_view(), name='system-user-auth'), +] + +urlpatterns += router.urls + diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py new file mode 100644 index 000000000..7b1f4e1bc --- /dev/null +++ b/apps/assets/urls/views_urls.py @@ -0,0 +1,58 @@ +# coding:utf-8 +from django.conf.urls import url +from .. import views + +app_name = 'assets' + +urlpatterns = [ + # Resource asset url + url(r'^$', views.AssetListView.as_view(), name='asset-index'), + url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'), + url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'), + url(r'^asset/(?P[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'), + url(r'^asset/(?P[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'), + url(r'^asset/(?P[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'), + url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'), + url(r'^asset-modal-update$', views.AssetModalCreateView.as_view(), name='asset-modal-update'), + + # Resource asset group url + url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'), + url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'), + url(r'^asset-group/(?P[0-9]+)/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'), + url(r'^asset-group/(?P[0-9]+)/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'), + url(r'^asset-group/(?P[0-9]+)/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'), + + url(r'^tags/$', views.TagsListView.as_view(), name='asset-tag-list'), + url(r'^asset-by-tag/(?P[0-9]+)/$', views.TagView.as_view(), name='asset-tags'), + url(r'^tags/create/$', views.AssetTagCreateView.as_view(), name='asset-tag-create'), + url(r'^asset-tag/(?P[0-9]+)/$', views.AssetTagDetailView.as_view(), name='asset-tag-detail'), + url(r'^asset-tag/(?P[0-9]+)/update/$', views.AssetTagUpdateView.as_view(), name='asset-tag-update'), + url(r'^asset-tag/(?P[0-9]+)/delete/$', views.AssetTagDeleteView.as_view(), name='asset-tag-delete'), + + # Resource idc url + url(r'^idc/$', views.IDCListView.as_view(), name='idc-list'), + url(r'^idc/create/$', views.IDCCreateView.as_view(), name='idc-create'), + url(r'^idc/(?P[0-9]+)/$', views.IDCDetailView.as_view(), name='idc-detail'), + url(r'^idc/(?P[0-9]+)/update/', views.IDCUpdateView.as_view(), name='idc-update'), + url(r'^idc/(?P[0-9]+)/delete/$', views.IDCDeleteView.as_view(), name='idc-delete'), + url(r'^idc/(?P[0-9]+)/assets/$', views.IDCAssetsView.as_view(), name='idc-assets'), + + # Resource admin user url + url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'), + url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'), + url(r'^admin-user/(?P[0-9]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'), + url(r'^admin-user/(?P[0-9]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'), + url(r'^admin-user/(?P[0-9]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'), + + # Resource system user url + url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'), + url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'), + url(r'^system-user/(?P[0-9]+)/$', views.SystemUserDetailView.as_view(), name='system-user-detail'), + url(r'^system-user/(?P[0-9]+)/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'), + url(r'^system-user/(?P[0-9]+)/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'), + url(r'^system-user/(?P[0-9]+)/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'), + # url(r'^system-user/(?P[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(), + # name='system-user-asset-group'), + +] + diff --git a/apps/assets/views.py b/apps/assets/views.py index f15612de8..ea8981ba9 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -30,21 +30,21 @@ class AssetListView(AdminUserRequiredMixin, ListView): @staticmethod def sorted_by_valid_and_ip(asset): ip_list = int_seq(asset.ip.split('.')) - ip_list.insert(0, asset.is_valid()[0]) + ip_list.insert(0, asset.is_valid[0]) return ip_list def get_context_data(self, **kwargs): context = { 'app': 'Assets', 'action': 'asset list', - 'tag_list': [(i.id,i.name,i.asset_set.all().count())for i in Tag.objects.all().order_by('name')] + 'tag_list': [(i.id, i.name, i.asset_set.all().count())for i in Tag.objects.all().order_by('name')] } kwargs.update(context) return super(AssetListView, self).get_context_data(**kwargs) -class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView): +class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView): model = Asset tag_type = 'asset' form_class = AssetCreateForm @@ -58,7 +58,8 @@ class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView): return super(AssetCreateView, self).form_valid(form) def form_invalid(self, form): - print(form.errors) + if form.errors.get('__all__'): + form.errors['all'] = form.errors.get('__all__') return super(AssetCreateView, self).form_invalid(form) def get_context_data(self, **kwargs): @@ -207,7 +208,7 @@ class AssetModalListView(AdminUserRequiredMixin, ListView): plain_id_lists = self.request.GET.get('plain_id_lists') self.s = self.request.GET.get('plain_id_lists') if "," in str(self.s): - self.plain_id_lists = [int(x) for x in self.s.split(',')] + self.plain_id_lists = [int(x) for x in self.s.split(',')] else: self.plain_id_lists = [self.s] @@ -217,19 +218,19 @@ class AssetModalListView(AdminUserRequiredMixin, ListView): else: plain_id_lists = [int(self.s)] context = { - 'all_assets':plain_id_lists + 'all_assets' :plain_id_lists } kwargs.update(context) if group_id: group = AssetGroup.objects.get(id=group_id) context = { - 'all_assets':[x.id for x in group.assets.all()] + 'all_assets': [x.id for x in group.assets.all()] } kwargs.update(context) if tag_id: tag = Tag.objects.get(id=tag_id) context = { - 'all_assets':[x.id for x in tag.asset_set.all()] + 'all_assets': [x.id for x in tag.asset_set.all()] } kwargs.update(context) return super(AssetModalListView, self).get_context_data(**kwargs) @@ -341,33 +342,33 @@ class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView): success_url = reverse_lazy('assets:asset-group-list') -class IDCListView(AdminUserRequiredMixin, ListView): - model = IDC - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE - context_object_name = 'idc_list' +class IDCListView(AdminUserRequiredMixin, TemplateView): + # model = IDC + # paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + # context_object_name = 'idc_list' template_name = 'assets/idc_list.html' def get_context_data(self, **kwargs): context = { 'app': _('Assets'), 'action': _('IDC list'), - 'keyword': self.request.GET.get('keyword', '') + # 'keyword': self.request.GET.get('keyword', '') } kwargs.update(context) return super(IDCListView, self).get_context_data(**kwargs) - def get_queryset(self): - self.queryset = super(IDCListView, self).get_queryset() - self.keyword = keyword = self.request.GET.get('keyword', '') - self.sort = sort = self.request.GET.get('sort', '-date_created') - - if keyword: - self.queryset = self.queryset.filter(Q(name__icontains=keyword) | - Q(comment__icontains=keyword)) - - if sort: - self.queryset = self.queryset.order_by(sort) - return self.queryset + # def get_queryset(self): + # self.queryset = super(IDCListView, self).get_queryset() + # self.keyword = keyword = self.request.GET.get('keyword', '') + # self.sort = sort = self.request.GET.get('sort', '-date_created') + # + # if keyword: + # self.queryset = self.queryset.filter(Q(name__icontains=keyword) | + # Q(comment__icontains=keyword)) + # + # if sort: + # self.queryset = self.queryset.order_by(sort) + # return self.queryset class IDCCreateView(AdminUserRequiredMixin, CreateView): @@ -414,7 +415,15 @@ class IDCUpdateView(AdminUserRequiredMixin, UpdateView): class IDCDetailView(AdminUserRequiredMixin, DetailView): - pass + model = IDC + template_name = 'assets/idc_detail.html' + context_object_name = 'idc' + + +class IDCAssetsView(AdminUserRequiredMixin, DetailView): + model = IDC + template_name = 'assets/idc_assets.html' + context_object_name = 'idc' class IDCDeleteView(AdminUserRequiredMixin, DeleteView): @@ -423,34 +432,34 @@ class IDCDeleteView(AdminUserRequiredMixin, DeleteView): success_url = reverse_lazy('assets:idc-list') -class AdminUserListView(AdminUserRequiredMixin, ListView): +class AdminUserListView(AdminUserRequiredMixin, TemplateView): model = AdminUser - paginate_by = settings.CONFIG.DISPLAY_PER_PAGE - context_object_name = 'admin_user_list' + # paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + # context_object_name = 'admin_user_list' template_name = 'assets/admin_user_list.html' def get_context_data(self, **kwargs): context = { 'app': _('Assets'), 'action': _('Admin user list'), - 'keyword': self.request.GET.get('keyword', '') + # 'keyword': self.request.GET.get('keyword', '') } kwargs.update(context) return super(AdminUserListView, self).get_context_data(**kwargs) - def get_queryset(self): - # Todo: Default order by lose asset connection num - self.queryset = super(AdminUserListView, self).get_queryset() - self.keyword = keyword = self.request.GET.get('keyword', '') - self.sort = sort = self.request.GET.get('sort', '-date_created') - - if keyword: - self.queryset = self.queryset.filter(Q(name__icontains=keyword) | - Q(comment__icontains=keyword)) - - if sort: - self.queryset = self.queryset.order_by(sort) - return self.queryset + # def get_queryset(self): + # Todo: Default order by lose asset connection num + # self.queryset = super(AdminUserListView, self).get_queryset() + # self.keyword = keyword = self.request.GET.get('keyword', '') + # self.sort = sort = self.request.GET.get('sort', '-date_created') + # + # if keyword: + # self.queryset = self.queryset.filter(Q(name__icontains=keyword) | + # Q(comment__icontains=keyword)) + # + # if sort: + # self.queryset = self.queryset.order_by(sort) + # return self.queryset class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): @@ -475,6 +484,9 @@ class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie )) return success_message + def form_invalid(self, form): + return super(AdminUserCreateView, self).form_invalid(form) + class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): model = AdminUser diff --git a/apps/audits/api.py b/apps/audits/api.py index 605de0acd..ea906a022 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -3,14 +3,14 @@ from __future__ import absolute_import, unicode_literals -from rest_framework import generics +from rest_framework import generics, viewsets from rest_framework.views import APIView, Response from . import models, serializers from .hands import IsSuperUserOrTerminalUser, Terminal -class ProxyLogListCreateApi(generics.ListCreateAPIView): +class ProxyLogViewSet(viewsets.ModelViewSet): """User proxy to backend server need call this api. params: { @@ -34,18 +34,8 @@ class ProxyLogListCreateApi(generics.ListCreateAPIView): serializer_class = serializers.ProxyLogSerializer permission_classes = (IsSuperUserOrTerminalUser,) - def perform_create(self, serializer): - # Todo: May be save log_file - super(ProxyLogListCreateApi, self).perform_create(serializer) - -class ProxyLogDetailApi(generics.RetrieveUpdateDestroyAPIView): - queryset = models.ProxyLog.objects.all() - serializer_class = serializers.ProxyLogSerializer - permission_classes = (IsSuperUserOrTerminalUser,) - - -class CommandLogListCreateApi(generics.ListCreateAPIView): +class CommandLogViewSet(viewsets.ModelViewSet): queryset = models.CommandLog.objects.all() serializer_class = serializers.CommandLogSerializer permission_classes = (IsSuperUserOrTerminalUser,) diff --git a/apps/audits/hands.py b/apps/audits/hands.py index 36358cbf5..281440780 100644 --- a/apps/audits/hands.py +++ b/apps/audits/hands.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ # +from users.utils import AdminUserRequiredMixin from users.models import User from assets.models import Asset, SystemUser from users.backends import IsSuperUserOrTerminalUser diff --git a/apps/audits/models.py b/apps/audits/models.py index a98178a0d..b2da116e3 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -22,9 +22,7 @@ class LoginLog(models.Model): login_ip = models.GenericIPAddressField(verbose_name=_('Login ip')) login_city = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Login city')) user_agent = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('User agent')) - from_terminal = models.ForeignKey date_login = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login')) - date_logout = models.DateTimeField(null=True, verbose_name=_('Date logout')) class Meta: db_table = 'login_log' diff --git a/apps/audits/tasks.py b/apps/audits/tasks.py new file mode 100644 index 000000000..f80aa5f75 --- /dev/null +++ b/apps/audits/tasks.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# ~*~ coding: utf-8 ~*~ +# + +from celery import shared_task +from .utils import write_login_log + + +@shared_task +def write_login_log_async(*args, **kwargs): + write_login_log(*args, **kwargs) + diff --git a/apps/audits/templates/audits/login_log_list.html b/apps/audits/templates/audits/login_log_list.html new file mode 100644 index 000000000..53b4ef0a2 --- /dev/null +++ b/apps/audits/templates/audits/login_log_list.html @@ -0,0 +1,100 @@ +{% extends '_base_list.html' %} +{% load i18n %} +{% load static %} +{% load common_tags %} +{% block content_left_head %} + + +{% endblock %} + + +{% block table_search %} +
+
+
+ + + to + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+{% endblock %} + +{% block table_head %} + {% trans 'ID' %} + {% trans 'Username' %} + {% trans 'Name' %} + {% trans 'Type' %} + {% trans 'UA' %} + {% trans 'IP' %} + {% trans 'City' %} + {% trans 'Date' %} +{% endblock %} + +{% block table_body %} + {% for login_log in login_log_list %} + + + {{ login_log.id }} +{# {{ login_log.id }}#} + + {{ login_log.username }} + {{ login_log.name }} + {{ login_log.get_login_type_display }} + {% if login_log.login_type == 'W' %} + + {{ login_log.user_agent | truncatechars:20 }} + + {% else %} + {{ login_log.terminal }} + {% endif %} + {{ login_log.login_ip }} + {{ login_log.login_city }} + {{ login_log.date_login }} + + {% endfor %} +{% endblock %} + +{% block custom_foot_js %} + + +{% endblock %} + diff --git a/apps/audits/urls/__init__.py b/apps/audits/urls/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/audits/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py new file mode 100644 index 000000000..2aa429fd0 --- /dev/null +++ b/apps/audits/urls/api_urls.py @@ -0,0 +1,14 @@ +from django.conf.urls import url +from rest_framework import routers + +from .. import api + +app_name = 'audits' + + +router = routers.DefaultRouter() +router.register(r'v1/proxy-log', api.ProxyLogViewSet, 'proxy-log') +router.register(r'v1/command-log', api.CommandLogViewSet, 'command-log') + +urlpatterns = router.urls + diff --git a/apps/audits/urls.py b/apps/audits/urls/views_urls.py similarity index 56% rename from apps/audits/urls.py rename to apps/audits/urls/views_urls.py index 37962d9db..67d5d35c6 100644 --- a/apps/audits/urls.py +++ b/apps/audits/urls/views_urls.py @@ -1,8 +1,5 @@ from django.conf.urls import url - - -import api -import views +from .. import views app_name = 'audits' @@ -11,11 +8,7 @@ urlpatterns = [ url(r'^proxy-log/(?P\d+)$', views.ProxyLogDetailView.as_view(), name='proxy-log-detail'), url(r'^proxy-log/(?P\d+)/commands$', views.ProxyLogCommandsListView.as_view(), name='proxy-log-commands-list'), url(r'^command-log$', views.CommandLogListView.as_view(), name='command-log-list'), + url(r'^login-log$', views.LoginLogListView.as_view(), name='login-log-list'), ] -urlpatterns += [ - url(r'^v1/proxy-log/$', api.ProxyLogListCreateApi.as_view(), name='proxy-log-list-create-api'), - url(r'^v1/proxy-log/(?P\d+)/$', api.ProxyLogDetailApi.as_view(), name='proxy-log-detail-api'), - url(r'^v1/command-log/$', api.CommandLogListCreateApi.as_view(), name='command-log-create-list-api'), -] diff --git a/apps/audits/utils.py b/apps/audits/utils.py index 2ad77a7e8..303ba18c9 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -1,5 +1,47 @@ # ~*~ coding: utf-8 ~*~ # -from users.utils import AdminUserRequiredMixin +from __future__ import unicode_literals +import requests +import ipaddress + +from .models import LoginLog + + +def validate_ip(ip): + try: + ipaddress.ip_address(ip.decode('utf-8')) + return True + except ValueError: + print('valid error') + return False + + +def write_login_log(username, name='', login_type='W', + terminal='', login_ip='', user_agent=''): + if not (login_ip and validate_ip(login_ip)): + login_ip = '0.0.0.0' + if not name: + name = username + login_city = get_ip_city(login_ip) + LoginLog.objects.create(username=username, name=name, login_type=login_type, login_ip=login_ip, + terminal=terminal, login_city=login_city, user_agent=user_agent) + + +def get_ip_city(ip, timeout=10): + # Taobao ip api: http://ip.taobao.com//service/getIpInfo.php?ip=8.8.8.8 + # Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=js + + url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ip + r = requests.get(url, timeout=timeout) + city = 'Unknown' + if r.status_code == 200: + try: + data = r.json() + if data['code'] == 0: + city = data['data']['country'] + data['data']['city'] + except ValueError: + pass + return city + diff --git a/apps/audits/views.py b/apps/audits/views.py index 39aa3f578..61fb1ff97 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -10,13 +10,13 @@ from django.urls import reverse_lazy from django.conf import settings from django.db.models import Q -from .models import ProxyLog, CommandLog -from .utils import AdminUserRequiredMixin -from .hands import User, Asset, SystemUser +from .models import ProxyLog, CommandLog, LoginLog +from .hands import User, Asset, SystemUser, AdminUserRequiredMixin -seven_days_ago_s = (datetime.datetime.now()-datetime.timedelta(7)).strftime('%m/%d/%Y') -now_s = datetime.datetime.now().strftime('%m/%d/%Y') +date_now = timezone.localtime(timezone.now()) +now_s = date_now.strftime('%m/%d/%Y') +seven_days_ago_s = (date_now-timezone.timedelta(7)).strftime('%m/%d/%Y') class ProxyLogListView(AdminUserRequiredMixin, ListView): @@ -54,6 +54,7 @@ class ProxyLogListView(AdminUserRequiredMixin, ListView): return self.queryset def get_context_data(self, **kwargs): + print(self.date_to_s) context = { 'app': _('Audits'), 'action': _('Proxy log list'), @@ -152,3 +153,43 @@ class CommandLogListView(AdminUserRequiredMixin, ListView): } kwargs.update(context) return super(CommandLogListView, self).get_context_data(**kwargs) + + +class LoginLogListView(AdminUserRequiredMixin, ListView): + model = LoginLog + template_name = 'audits/login_log_list.html' + context_object_name = 'login_log_list' + + def get_queryset(self): + self.queryset = super(LoginLogListView, self).get_queryset() + self.keyword = keyword = self.request.GET.get('keyword', '') + self.username = username = self.request.GET.get('username', '') + self.date_from_s = date_from_s = self.request.GET.get('date_from', '%s' % seven_days_ago_s) + self.date_to_s = date_to_s = self.request.GET.get('date_to', '%s' % now_s) + + if date_from_s: + date_from = timezone.datetime.strptime(date_from_s, '%m/%d/%Y') + self.queryset = self.queryset.filter(date_login__gt=date_from) + if date_to_s: + date_to = timezone.datetime.strptime(date_to_s + ' 23:59:59', '%m/%d/%Y %H:%M:%S') + self.queryset = self.queryset.filter(date_login__lt=date_to) + if username: + self.queryset = self.queryset.filter(username=username) + if keyword: + self.queryset = self.queryset.filter(Q(username__contains=keyword) | + Q(name__icontains=keyword) | + Q(login_ip=keyword)).distinct() + return self.queryset + + def get_context_data(self, **kwargs): + context = { + 'app': _('Audits'), + 'action': _('Proxy log list'), + 'user_list': User.objects.all().order_by('username'), + 'keyword': self.keyword, + 'date_from': self.date_from_s, + 'date_to': self.date_to_s, + 'username': self.username, + } + kwargs.update(context) + return super(LoginLogListView, self).get_context_data(**kwargs) diff --git a/apps/common/__init__.py b/apps/common/__init__.py index 88fef8e67..b64e43e83 100644 --- a/apps/common/__init__.py +++ b/apps/common/__init__.py @@ -3,4 +3,3 @@ from __future__ import absolute_import # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from .celery import app as celery_app - diff --git a/apps/common/celery.py b/apps/common/celery.py index 9d006fe4a..f4ea048e5 100644 --- a/apps/common/celery.py +++ b/apps/common/celery.py @@ -1,8 +1,8 @@ # ~*~ coding: utf-8 ~*~ from __future__ import absolute_import, unicode_literals - import os +from datetime import timedelta from celery import Celery @@ -16,6 +16,5 @@ app = Celery('jumpserver') # Using a string here means the worker will not have to # pickle the object when using Windows. app.config_from_object('django.conf:settings') - app.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS]) diff --git a/apps/common/tasks.py b/apps/common/tasks.py index 8df504baa..11d0d711d 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -1,11 +1,12 @@ from __future__ import absolute_import -from celery import shared_task +# from celery import shared_task from django.core.mail import send_mail from django.conf import settings +from common import celery_app as app -@shared_task(name='send_mail_async') +@app.task def send_mail_async(*args, **kwargs): """ Using celery to send email async @@ -26,9 +27,3 @@ def send_mail_async(*args, **kwargs): args = tuple(args) send_mail(*args, **kwargs) - - -# def send_mail_async(subject, message, from_mail, recipient_list, fail_silently=False, html_message=None): -# if settings.CONFIG.MAIL_SUBJECT_PREFIX: -# subject += settings.CONFIG.MAIL_SUBJECT_PREFIX -# send_mail(subject, message, from_mail, recipient_list, fail_silently=fail_silently, html_message=html_message) diff --git a/apps/common/utils.py b/apps/common/utils.py index d3bd61ddf..269455d09 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -3,11 +3,15 @@ from __future__ import unicode_literals from six import string_types +import os from itertools import chain import string import logging import datetime +import paramiko +import paramiko +import sshpubkeys from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \ BadSignature, SignatureExpired from django.shortcuts import reverse as dj_reverse @@ -15,6 +19,11 @@ from django.conf import settings from django.core import signing from django.utils import timezone +try: + import cStringIO as StringIO +except ImportError: + import StringIO + SECRET_KEY = settings.SECRET_KEY @@ -162,4 +171,88 @@ def timesince(dt, since='', default="just now"): return default +def ssh_key_string_to_obj(text): + key_f = StringIO.StringIO(text) + key = None + try: + key = paramiko.RSAKey.from_private_key(key_f) + except paramiko.SSHException: + pass + + try: + key = paramiko.DSSKey.from_private_key(key_f) + except paramiko.SSHException: + pass + return key + + +def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost'): + if isinstance(private_key, string_types): + private_key = ssh_key_string_to_obj(private_key) + + if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)): + raise IOError('Invalid private key') + + public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % { + 'key_type': private_key.get_name(), + 'key_content': private_key.get_base64(), + 'username': username, + 'hostname': hostname, + } + return public_key + + +def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None): + """Generate user ssh private and public key + + Use paramiko RSAKey generate it. + :return private key str and public key str + """ + + if hostname is None: + hostname = os.uname()[1] + + f = StringIO.StringIO() + + try: + if type == 'rsa': + private_key_obj = paramiko.RSAKey.generate(length) + elif type == 'dsa': + private_key_obj = paramiko.DSSKey.generate(length) + else: + raise IOError('SSH private key must be `rsa` or `dsa`') + private_key_obj.write_private_key(f, password=password) + private_key = f.getvalue() + public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname) + return private_key, public_key + except IOError: + raise IOError('These is error when generate ssh key.') + + +def validate_ssh_private_key(text): + key = ssh_key_string_to_obj(text) + if key is None: + return False + else: + return True + + +def validate_ssh_public_key(text): + ssh = sshpubkeys.SSHKey(text) + try: + ssh.parse() + except sshpubkeys.InvalidKeyException: + return False + except NotImplementedError as e: + return False + return True + + +def setattr_bulk(seq, key, value): + def set_attr(obj): + setattr(obj, key, value) + return obj + return map(set_attr, seq) + + signer = Signer() \ No newline at end of file diff --git a/apps/common/views.py b/apps/common/views.py index 870cfbf79..5663b8b19 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -2,6 +2,3 @@ from __future__ import absolute_import, unicode_literals from django.shortcuts import render from django.views.generic import TemplateView - - - diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5893ee909..3f31995d6 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -14,6 +14,7 @@ import os import sys from django.urls import reverse_lazy +from datetime import timedelta # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -284,6 +285,31 @@ BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % { } CELERY_RESULT_BACKEND = BROKER_URL +# TERMINAL_HEATBEAT_INTERVAL = CONFIG.TERMINAL_HEATBEAT_INTERVAL or 30 + +# crontab job +# CELERYBEAT_SCHEDULE = { +# Check terminal is alive every 10m + # 'check_terminal_alive': { + # 'task': 'terminal.tasks.check_terminal_alive', + # 'schedule': timedelta(seconds=TERMINAL_HEATBEAT_INTERVAL), + # 'args': (), + # }, +# } + + +# Cache use redis +CACHES = { + 'default': { + 'BACKEND': 'redis_cache.RedisCache', + 'LOCATION': 'redis://%(password)s%(host)s:%(port)s/4' % { + 'password': CONFIG.REDIS_PASSWORD + '@' if CONFIG.REDIS_PASSWORD else '', + 'host': CONFIG.REDIS_HOST or '127.0.0.1', + 'port': CONFIG.REDIS_PORT or 6379, + } + } +} + # Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html CAPTCHA_IMAGE_SIZE = (75, 33) CAPTCHA_FOREGROUND_COLOR = '#001100' diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 81a5f9d15..50a4bb77d 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -22,11 +22,16 @@ from django.views.generic.base import TemplateView urlpatterns = [ url(r'^captcha/', include('captcha.urls')), url(r'^$', TemplateView.as_view(template_name='base.html'), name='index'), - url(r'^(api/)?users/', include('users.urls')), - url(r'^(api/)?assets/', include('assets.urls')), - url(r'^(api/)?perms/', include('perms.urls')), - url(r'^(api/)?audits/', include('audits.urls')), - url(r'^(api/)?terminal/', include('terminal.urls')), + url(r'^users/', include('users.urls.views_urls', namespace='users')), + url(r'^assets/', include('assets.urls.views_urls', namespace='assets')), + url(r'^perms/', include('perms.urls.views_urls', namespace='perms')), + url(r'^audits/', include('audits.urls.views_urls', namespace='audits')), + url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')), + url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')), + url(r'^api/assets/', include('assets.urls.api_urls', namespace='api-assets')), + url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')), + url(r'^api/audits/', include('audits.urls.api_urls', namespace='api-audits')), + url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b68a30613..0191d8a9c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1610,7 +1610,7 @@ msgid "" "here reset password\n" "
\n" " This link is valid for 1 hour. After it expires, request new one<\n" +"(forget_password_url)s?email=%(email)s\">request new one\n" "\n" "
\n" " ---\n" diff --git a/apps/ops/urls/__init__.py b/apps/ops/urls/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/ops/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py new file mode 100644 index 000000000..d0c6b5204 --- /dev/null +++ b/apps/ops/urls/api_urls.py @@ -0,0 +1,7 @@ +# coding:utf-8 + +from django.conf.urls import url +from rest_framework import routers + + + diff --git a/apps/ops/urls/views_urls.py b/apps/ops/urls/views_urls.py new file mode 100644 index 000000000..a29072e4f --- /dev/null +++ b/apps/ops/urls/views_urls.py @@ -0,0 +1,8 @@ +# coding:utf-8 + +from django.conf.urls import url +from .. import views + +app_name = 'ops' + + diff --git a/apps/perms/api.py b/apps/perms/api.py index c40b9343e..a2522f44b 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -2,49 +2,120 @@ # from rest_framework.views import APIView, Response -from rest_framework.generics import ListCreateAPIView +from rest_framework.generics import ListAPIView, get_object_or_404 +from rest_framework import viewsets from users.backends import IsValidUser, IsSuperUser -from .utils import get_user_granted_assets, get_user_granted_asset_groups +from common.utils import get_object_or_none +from .utils import get_user_granted_assets, get_user_granted_asset_groups, get_user_asset_permissions, \ + get_user_group_asset_permissions, get_user_group_granted_assets, get_user_group_granted_asset_groups from .models import AssetPermission +from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, AssetGroup, AssetGroupSerializer from . import serializers -class AssetPermissionListCreateApi(ListCreateAPIView): +class AssetPermissionViewSet(viewsets.ModelViewSet): queryset = AssetPermission.objects.all() serializer_class = serializers.AssetPermissionSerializer permission_classes = (IsSuperUser,) + def get_queryset(self): + queryset = super(AssetPermissionViewSet, self).get_queryset() + user_id = self.request.query_params.get('user', '') + user_group_id = self.request.query_params.get('user_group', '') -class UserAssetsApi(APIView): + if user_id and user_id.isdigit(): + user = get_object_or_404(User, id=int(user_id)) + queryset = get_user_asset_permissions(user) + + if user_group_id: + user_group = get_object_or_404(UserGroup, id=user_group_id) + queryset = get_user_group_asset_permissions(user_group) + return queryset + + def get_serializer_class(self): + if getattr(self, 'user_id', ''): + return serializers.UserAssetPermissionSerializer + return serializers.AssetPermissionSerializer + + +class RevokeUserAssetPermission(APIView): + permission_classes = (IsSuperUser,) + + def put(self, request, *args, **kwargs): + permission_id = str(request.data.get('id', '')) + user_id = str(request.data.get('user_id', '')) + + if permission_id and user_id and permission_id.isdigit() and user_id.isdigit(): + asset_permission = get_object_or_404(AssetPermission, id=int(permission_id)) + user = get_object_or_404(User, id=int(user_id)) + + if asset_permission and user: + asset_permission.users.remove(user) + return Response({'msg': 'success'}) + return Response({'msg': 'failed'}, status=404) + + +class RevokeUserGroupAssetPermission(APIView): + permission_classes = (IsSuperUser,) + + def put(self, request, *args, **kwargs): + permission_id = str(request.data.get('id', '')) + user_group_id = str(request.data.get('user_group_id', '')) + + if permission_id and user_group_id and permission_id.isdigit() and user_group_id.isdigit(): + asset_permission = get_object_or_404(AssetPermission, id=int(permission_id)) + user_group = get_object_or_404(UserGroup, id=int(user_group_id)) + + if asset_permission and user_group: + asset_permission.user_groups.remove(user_group) + return Response({'msg': 'success'}) + return Response({'msg': 'failed'}, status=404) + + +class UserGrantedAssetsApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = AssetGrantedSerializer + + def get_queryset(self): + user_id = self.kwargs.get('pk', '') + + if user_id: + user = get_object_or_404(User, id=user_id) + queryset = get_user_granted_assets(user) + else: + queryset = [] + return queryset + + +class UserGrantedAssetGroupsApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = AssetGroupSerializer + + def get_queryset(self): + user_id = self.kwargs.get('pk', '') + + if user_id: + user = get_object_or_404(User, id=user_id) + queryset = get_user_granted_asset_groups(user) + else: + queryset = [] + return queryset + + +class MyGrantedAssetsApi(ListAPIView): permission_classes = (IsValidUser,) + serializer_class = AssetGrantedSerializer - def get(self, request, *args, **kwargs): - assets_json = [] - user = request.user - + def get_queryset(self): + user = self.request.user if user: - assets = get_user_granted_assets(user) - - for asset, system_users in assets.items(): - assets_json.append({ - 'id': asset.id, - 'hostname': asset.hostname, - 'ip': asset.ip, - 'port': asset.port, - 'system_users': [ - { - 'id': system_user.id, - 'name': system_user.name, - 'username': system_user.username, - } for system_user in system_users - ], - 'comment': asset.comment - }) - - return Response(assets_json, status=200) + queryset = get_user_granted_assets(user) + else: + queryset = [] + return queryset -class UserAssetsGroupsApi(APIView): +class MyGrantedAssetsGroupsApi(APIView): permission_classes = (IsValidUser,) def get(self, request, *args, **kwargs): @@ -56,46 +127,61 @@ class UserAssetsGroupsApi(APIView): for asset in assets: for asset_group in asset.groups.all(): if asset_group.id in asset_groups: - asset_groups[asset_group.id]['asset_num'] += 1 + asset_groups[asset_group.id]['asset_amount'] += 1 else: asset_groups[asset_group.id] = { 'id': asset_group.id, 'name': asset_group.name, 'comment': asset_group.comment, - 'asset_num': 1 + 'assets_amount': 1 } - asset_groups_json = asset_groups.values() return Response(asset_groups_json, status=200) -class UserAssetsGroupAssetsApi(APIView): +class MyAssetGroupAssetsApi(ListAPIView): permission_classes = (IsValidUser,) + serializer_class = AssetGrantedSerializer - def get(self, request, *args, **kwargs): - # asset_group_id = request.query_params.get('asset_group_id', -1) - asset_group_id = kwargs.get('pk', -1) - # asset_group_name = request.query_params.get('asset_group_name', '') - user = request.user - assets_json = [] + def get_queryset(self): + queryset = [] + asset_group_id = self.kwargs.get('pk', -1) + user = self.request.user + asset_group = get_object_or_none(AssetGroup, id=asset_group_id) - if user: + if user and asset_group: assets = get_user_granted_assets(user) - for asset, system_users in assets.items(): - for asset_group in asset.groups.all(): - if str(asset_group.id) == asset_group_id: # and asset_group.name == asset_group_name: - assets_json.append({ - 'id': asset.id, - 'hostname': asset.hostname, - 'ip': asset.ip, - 'port': asset.port, - 'system_users': [ - { - 'id': system_user.id, - 'name': system_user.name, - 'username': system_user.username, - } for system_user in system_users - ], - 'comment': asset.comment - }) - return Response(assets_json, status=200) + for asset in assets: + if asset_group in asset.groups.all(): + queryset.append(asset) + return queryset + + +class UserGroupGrantedAssetsApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = AssetGrantedSerializer + + def get_queryset(self): + user_group_id = self.kwargs.get('pk', '') + + if user_group_id: + user_group = get_object_or_404(UserGroup, id=user_group_id) + queryset = get_user_group_granted_assets(user_group) + else: + queryset = [] + return queryset + + +class UserGroupGrantedAssetGroupsApi(ListAPIView): + permission_classes = (IsSuperUser,) + serializer_class = AssetGroupSerializer + + def get_queryset(self): + user_group_id = self.kwargs.get('pk', '') + + if user_group_id: + user_group = get_object_or_404(UserGroup, id=user_group_id) + queryset = get_user_group_granted_asset_groups(user_group) + else: + queryset = [] + return queryset \ No newline at end of file diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 735faecfd..eac38f7d0 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -4,6 +4,7 @@ from users.utils import AdminUserRequiredMixin from users.models import User, UserGroup from assets.models import Asset, AssetGroup, SystemUser +from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer def associate_system_users_with_assets(system_users, assets, asset_groups): diff --git a/apps/perms/serializers.py b/apps/perms/serializers.py index 78857cf0c..34df76f29 100644 --- a/apps/perms/serializers.py +++ b/apps/perms/serializers.py @@ -1,34 +1,26 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from common.utils import get_object_or_none from .models import AssetPermission +from .hands import User class AssetPermissionSerializer(serializers.ModelSerializer): - # users_amount = serializers.SerializerMethodField() - # user_groups_amount = serializers.SerializerMethodField() - # assets_amount = serializers.SerializerMethodField() - # asset_groups_amount = serializers.SerializerMethodField() - class Meta: model = AssetPermission - fields = ['id', 'name', 'users', 'user_groups', 'assets', 'asset_groups', - 'system_users', 'is_active', 'comment', 'date_expired'] - # @staticmethod - # def get_users_amount(obj): - # return obj.users.count() - # - # @staticmethod - # def get_user_groups_amount(obj): - # return obj.user_groups.count() - # - # @staticmethod - # def get_assets_amount(obj): - # return obj.assets.count() - # - # @staticmethod - # def get_asset_groups_amount(obj): - # return obj.asset_groups.count() + +class UserAssetPermissionSerializer(AssetPermissionSerializer): + is_inherited = serializers.SerializerMethodField() + + @staticmethod + def get_is_inherited(obj): + if getattr(obj, 'inherited', ''): + return True + else: + return False + diff --git a/apps/perms/urls/__init__.py b/apps/perms/urls/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/perms/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py new file mode 100644 index 000000000..faac46676 --- /dev/null +++ b/apps/perms/urls/api_urls.py @@ -0,0 +1,37 @@ +# coding:utf-8 + +from django.conf.urls import url +from rest_framework import routers +from .. import api + +app_name = 'perms' + +router = routers.DefaultRouter() +router.register('v1/asset-permissions', api.AssetPermissionViewSet, 'asset-permission') + +urlpatterns = [ + url(r'^v1/user/my/assets/$', api.MyGrantedAssetsApi.as_view(), name='my-assets'), + url(r'^v1/user/my/asset-groups/$', api.MyGrantedAssetsGroupsApi.as_view(), name='my-asset-groups'), + url(r'^v1/user/my/asset-group/(?P[0-9]+)/assets/$', api.MyAssetGroupAssetsApi.as_view(), + name='user-my-asset-group-assets'), + + # Select user permission of asset and asset group + url(r'^v1/user/(?P[0-9]+)/assets/$', api.UserGrantedAssetsApi.as_view(), name='user-assets'), + url(r'^v1/user/(?P[0-9]+)/asset-groups/$', api.UserGrantedAssetGroupsApi.as_view(), + name='user-asset-groups'), + + # Select user group permission of asset and asset group + url(r'^v1/user-group/(?P[0-9]+)/assets/$', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), + url(r'^v1/user-group/(?P[0-9]+)/asset-groups/$', api.UserGroupGrantedAssetGroupsApi.as_view(), + name='user-group-asset-groups'), + + + # Revoke permission api + url(r'^v1/asset-permissions/user/revoke/', api.RevokeUserAssetPermission.as_view(), + name='revoke-user-asset-permission'), + url(r'^v1/asset-permissions/user-group/revoke/', api.RevokeUserGroupAssetPermission.as_view(), + name='revoke-user-group-asset-permission'), +] + +urlpatterns += router.urls + diff --git a/apps/perms/urls.py b/apps/perms/urls/views_urls.py similarity index 66% rename from apps/perms/urls.py rename to apps/perms/urls/views_urls.py index 186199b56..b73d39fab 100644 --- a/apps/perms/urls.py +++ b/apps/perms/urls/views_urls.py @@ -1,8 +1,7 @@ # coding:utf-8 from django.conf.urls import url -import views -import api +from .. import views app_name = 'perms' @@ -21,14 +20,4 @@ urlpatterns = [ name='asset-permission-asset-list'), ] -urlpatterns += [ - url(r'^v1/asset-permission/$', api.AssetPermissionListCreateApi.as_view(), - name='asset-permission-list-create-api'), - url(r'^v1/user/assets/$', api.UserAssetsApi.as_view(), - name='user-assets'), - url(r'^v1/user/asset-groups/$', api.UserAssetsGroupsApi.as_view(), - name='user-asset-groups'), - url(r'^v1/user/asset-groups/(?P[0-9]+)/assets/$', api.UserAssetsGroupAssetsApi.as_view(), - name='user-asset-groups-assets'), -] diff --git a/apps/perms/utils.py b/apps/perms/utils.py index cbc6f823d..585b65808 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +from common.utils import setattr_bulk from .hands import User, UserGroup, Asset, AssetGroup, SystemUser @@ -7,7 +8,7 @@ def get_user_group_granted_asset_groups(user_group): """Return asset groups granted of the user group :param user_group: Instance of :class: ``UserGroup`` - :return: {asset1: {system_user1, }, asset1: {system_user1, system_user2]} + :return: {asset_group1: {system_user1, }, asset_group2: {system_user1, system_user2}} """ asset_groups = {} asset_permissions = user_group.asset_permissions.all() @@ -20,7 +21,6 @@ def get_user_group_granted_asset_groups(user_group): asset_groups[asset_group] |= set(asset_permission.system_users.all()) else: asset_groups[asset_group] = set(asset_permission.system_users.all()) - return asset_groups @@ -41,7 +41,6 @@ def get_user_group_granted_assets(user_group): assets[asset] |= set(asset_permission.system_users.all()) else: assets[asset] = set(asset_permission.system_users.all()) - return assets @@ -61,7 +60,7 @@ def get_user_granted_asset_groups_direct(user): if asset_group in asset_groups: asset_groups[asset_group] |= set(asset_permission.system_users.all()) else: - setattr(asset_group, 'is_inherit_from_user_group', False) + setattr(asset_group, 'inherited', False) asset_groups[asset_group] = set(asset_permission.system_users.all()) return asset_groups @@ -89,7 +88,7 @@ def get_user_granted_asset_groups_inherit_from_user_groups(user): if asset_group in asset_groups: asset_groups[asset_group] |= set(asset_permission.system_users.all()) else: - setattr(asset_group, 'is_inherit_from_user_group', True) + setattr(asset_group, 'inherited', True) asset_groups[asset_group] = set(asset_permission.system_users.all()) return asset_groups @@ -112,7 +111,6 @@ def get_user_granted_asset_groups(user): asset_groups[asset_group] |= asset_groups_direct[asset_group] else: asset_groups[asset_group] = asset_groups_direct[asset_group] - return asset_groups @@ -132,10 +130,8 @@ def get_user_granted_assets_direct(user): if asset in assets: assets[asset] |= set(asset_permission.system_users.all()) else: - setattr(asset, 'is_inherit_from_user_groups', False) - setattr(asset, 'is_inherit_from_user_groups', False) + setattr(asset, 'inherited', False) assets[asset] = set(asset_permission.system_users.all()) - return assets @@ -154,7 +150,7 @@ def get_user_granted_assets_inherit_from_user_groups(user): if asset in assets: assets[asset] |= assets_inherited[asset] else: - setattr(asset, 'is_inherit_from_user_groups', True) + setattr(asset, 'inherited', True) assets[asset] = assets_inherited[asset] return assets @@ -175,10 +171,25 @@ def get_user_granted_assets(user): assets[asset] |= assets_direct[asset] else: assets[asset] = assets_direct[asset] - return assets +def get_user_group_asset_permissions(user_group): + permissions = user_group.asset_permissions.all() + return permissions + + +def get_user_asset_permissions(user): + user_group_permissions = set() + direct_permissions = set(setattr_bulk(user.asset_permissions.all(), 'inherited', 0)) + + for user_group in user.groups.all(): + permissions = get_user_group_asset_permissions(user_group) + user_group_permissions |= set(permissions) + user_group_permissions = set(setattr_bulk(user_group_permissions, 'inherited', 1)) + return direct_permissions | user_group_permissions + + def get_user_groups_granted_in_asset(asset): pass diff --git a/apps/static/css/jumpserver.css b/apps/static/css/jumpserver.css index caa058e97..ecbde4cc4 100644 --- a/apps/static/css/jumpserver.css +++ b/apps/static/css/jumpserver.css @@ -255,3 +255,12 @@ table.dataTable tbody td.selected td i.text-navy font-size: 12px; vertical-align: middle; } + +div.dataTables_wrapper div.dataTables_filter, +.dataTables_length { + float: right !important; +} + +div.dataTables_wrapper div.dataTables_filter { + margin-left: 15px; +} \ No newline at end of file diff --git a/apps/static/css/style.css b/apps/static/css/style.css index fb30bde06..609d1cffb 100644 --- a/apps/static/css/style.css +++ b/apps/static/css/style.css @@ -1585,9 +1585,9 @@ table.dataTable thead .sorting_desc_disabled { .dataTables_wrapper { padding-bottom: 30px; } -.dataTables_length { - float: left; -} +/*.dataTables_length {*/ + /*float: left;*/ +/*}*/ .dataTables_filter label { margin-right: 5px; } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index e2bc67ba6..e107832f4 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -189,9 +189,8 @@ function activeNav() { function APIUpdateAttr(props) { // props = {url: .., body: , success: , error: , method: ,} props = props || {}; - success_message = props.success_message || 'Update Successfully!'; - fail_message = props.fail_message || 'Error occurred while updating.'; - + var success_message = props.success_message || 'Update Successfully!'; + var fail_message = props.fail_message || 'Error occurred while updating.'; $.ajax({ url: props.url, type: props.method || "PATCH", @@ -199,19 +198,18 @@ 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 (typeof props.success === 'function') { return props.success(data); - } else { - toastr.success(success_message); - } + } + }).fail(function(jqXHR, textStatue, errorThrown) { + toastr.error(fail_message); if (typeof props.error === 'function') { return props.error(errorThrown); - } else { - toastr.error(fail_message); - } + } }); - return true; + // return true; } // Sweet Alert for Delete @@ -267,6 +265,7 @@ $.fn.serializeObject = function() }; var jumpserver = {}; jumpserver.checked = false; +jumpserver.selected = {}; jumpserver.initDataTable = function (options) { // options = { // ele *: $('#dataTable_id'), @@ -285,59 +284,37 @@ jumpserver.initDataTable = function (options) { { targets: 0, orderable: false, - createdCell: function(td) { - $(td).html(''); - } - }, + createdCell: function(td, cellData) { + $(td).html(''.replace('99991937', cellData)); + }}, {className: 'text-center', targets: '_all'} ]; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; var table = ele.DataTable({ - pageLength: options.pageLength || 25, + pageLength: options.pageLength || 15, dom: options.dom || '<"#uc.pull-left"><"html5buttons"B>flti<"row m-t"<"#op.col-md-6"><"col-md-6"p>>', language: { url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json" }, order: options.order || [[ 1, 'asc' ]], - buttons: options.buttons || [ - {extend: 'excel', - exportOptions: { - modifier: { - selected: true - } - } - }, - {extend: 'pdf', - exportOptions: { - modifier: { - selected: true - } - } - }, - {extend: 'print', - customize: function (win){ - $(win.document.body).addClass('white-bg'); - $(win.document.body).css('font-size', '10px'); - $(win.document.body).find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - } - ], + select: options.select || 'multi', + buttons: [], columnDefs: columnDefs, - select: options.select || {style: 'multi'}, ajax: { url: options.ajax_url , dataSrc: "" }, - columns: options.columns || [] + columns: options.columns || [], + lengthMenu: [[15, 25, 50, -1], [15, 25, 50, "All"]] }); table.on('select', function(e, dt, type, indexes) { var $node = table[ type ]( indexes ).nodes().to$(); $node.find('input.ipt_check').prop('checked', true); + jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true }).on('deselect', function(e, dt, type, indexes) { var $node = table[ type ]( indexes ).nodes().to$(); $node.find('input.ipt_check').prop('checked', false); + jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false }).on('draw', function(){ $('#op').html(options.op_html || ''); $('#uc').html(options.uc_html || ''); @@ -353,5 +330,6 @@ jumpserver.initDataTable = function (options) { table.rows().deselect(); } }); + return table; }; diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html index 2e1a981f8..c011feb71 100644 --- a/apps/templates/_base_create_update.html +++ b/apps/templates/_base_create_update.html @@ -28,10 +28,17 @@
- {% block form %} {% endblock %} + {% if form.errors.all %} +
+ {{ form.errors.all }} +
+ {% endif %} + {% block form %} + {% endblock %}
{% endblock %} + diff --git a/apps/templates/_footer.html b/apps/templates/_footer.html index d3b860048..c43bda248 100644 --- a/apps/templates/_footer.html +++ b/apps/templates/_footer.html @@ -1,6 +1,6 @@