diff --git a/apps/assets/hands.py b/apps/assets/hands.py index e87060ecd..3dab35ec7 100644 --- a/apps/assets/hands.py +++ b/apps/assets/hands.py @@ -11,4 +11,4 @@ """ - +from users.utils import AdminUserRequiredMixin diff --git a/apps/assets/migrations/0001_initial.py b/apps/assets/migrations/0001_initial.py index e2ae4c8a1..94a2908dd 100644 --- a/apps/assets/migrations/0001_initial.py +++ b/apps/assets/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-09-05 12:50 +# Generated by Django 1.10 on 2016-09-07 15:11 from __future__ import unicode_literals from django.db import migrations, models @@ -24,12 +24,12 @@ class Migration(migrations.Migration): ('private_key', models.CharField(blank=True, max_length=4096, null=True, verbose_name='SSH private key')), ('is_default', models.BooleanField(default=True, verbose_name='As default')), ('auto_update', models.BooleanField(default=True, verbose_name='Auto update pass/key')), - ('date_added', models.DateTimeField(auto_now=True, null=True)), + ('date_created', models.DateTimeField(auto_now=True, null=True)), ('create_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ], options={ - 'db_table': 'adminuser', + 'db_table': 'admin_user', }, ), migrations.CreateModel( @@ -55,9 +55,9 @@ class Migration(migrations.Migration): ('sn', models.CharField(blank=True, max_length=128, null=True, unique=True, verbose_name='Serial number')), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')), - ('date_added', models.DateTimeField(auto_now=True, null=True, verbose_name='Date added')), + ('date_created', models.DateTimeField(auto_now=True, null=True, verbose_name='Date added')), ('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')), - ('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')), + ('admin_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')), ], options={ 'db_table': 'asset', @@ -70,23 +70,24 @@ class Migration(migrations.Migration): ('key', models.CharField(blank=True, max_length=64, null=True, verbose_name='KEY')), ('value', models.CharField(blank=True, max_length=64, null=True, verbose_name='VALUE')), ('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')), - ('date_added', models.DateTimeField(auto_now=True, null=True)), + ('date_created', models.DateTimeField(auto_now=True, null=True)), ('comment', models.TextField(blank=True, verbose_name='Comment')), ], options={ - 'db_table': 'assetextend', + 'db_table': 'asset_extend', }, ), migrations.CreateModel( name='AssetGroup', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(blank=True, max_length=64, null=True, unique=True, verbose_name='Name')), - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('name', models.CharField(max_length=64, unique=True, verbose_name='Name')), + ('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now=True, null=True, verbose_name='Date added')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ], options={ - 'db_table': 'assetgroup', + 'db_table': 'asset_group', }, ), migrations.CreateModel( @@ -99,7 +100,7 @@ class Migration(migrations.Migration): ('phone', models.CharField(blank=True, max_length=32, verbose_name='Phone')), ('address', models.CharField(blank=True, max_length=128, verbose_name='Address')), ('network', models.TextField(blank=True, verbose_name='Network')), - ('date_added', models.DateField(auto_now=True, null=True, verbose_name='Date added')), + ('date_created', models.DateTimeField(auto_now=True, null=True, verbose_name='Date added')), ('operator', models.CharField(blank=True, max_length=32, verbose_name='Operator')), ('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')), ('comment', models.TextField(blank=True, verbose_name='Comment')), @@ -115,7 +116,7 @@ class Migration(migrations.Migration): ('key', models.CharField(blank=True, max_length=64, null=True, verbose_name='KEY')), ('value', models.CharField(blank=True, max_length=64, null=True, verbose_name='VALUE')), ('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')), - ('date_added', models.DateTimeField(auto_now=True, null=True)), + ('date_created', models.DateTimeField(auto_now=True, null=True)), ('comment', models.CharField(blank=True, max_length=128, verbose_name='Comment')), ('asset', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.Asset', verbose_name='Asset')), ], @@ -124,7 +125,7 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='SysUser', + name='SystemUser', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), @@ -140,14 +141,19 @@ class Migration(migrations.Migration): ('shell', models.CharField(blank=True, max_length=64, verbose_name='Shell')), ('home', models.CharField(blank=True, max_length=64, verbose_name='Home')), ('uid', models.IntegerField(blank=True, verbose_name='Uid')), - ('date_added', models.DateTimeField(auto_now=True, null=True)), + ('date_created', models.DateTimeField(auto_now=True, null=True)), ('create_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')), ('comment', models.CharField(blank=True, max_length=128, verbose_name='Comment')), ], options={ - 'db_table': 'sysuser', + 'db_table': 'system_user', }, ), + migrations.AddField( + model_name='assetgroup', + name='system_users', + field=models.ManyToManyField(blank=True, related_name='asset_groups', to='assets.SystemUser'), + ), migrations.AddField( model_name='asset', name='env', @@ -156,12 +162,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='groups', - field=models.ManyToManyField(blank=True, null=True, to='assets.AssetGroup', verbose_name='Asset groups'), + field=models.ManyToManyField(related_name='assets', to='assets.AssetGroup', verbose_name='Asset groups'), ), migrations.AddField( model_name='asset', name='idc', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.IDC', verbose_name='IDC'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.IDC', verbose_name='IDC'), ), migrations.AddField( model_name='asset', @@ -170,8 +176,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='asset', - name='sys_user', - field=models.ManyToManyField(blank=True, null=True, to='assets.SysUser', verbose_name='System User'), + name='system_user', + field=models.ManyToManyField(blank=True, to='assets.SystemUser', verbose_name='System User'), ), migrations.AddField( model_name='asset', diff --git a/apps/assets/models.py b/apps/assets/models.py index 642966e92..f968f05b0 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -5,6 +5,8 @@ from django.db import models import logging from django.utils.translation import ugettext_lazy as _ +from common.utils import encrypt, decrypt + logger = logging.getLogger(__name__) @@ -68,20 +70,64 @@ class AssetExtend(models.Model): class AdminUser(models.Model): name = models.CharField(max_length=128, unique=True, null=True, blank=True, verbose_name=_('Name')) username = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Username')) - password = models.CharField(max_length=256, null=True, blank=True, verbose_name=_('Password')) - private_key = models.CharField(max_length=4096, null=True, blank=True, verbose_name=_('SSH private key')) - is_default = models.BooleanField(default=True, verbose_name=_('As default')) - auto_update = models.BooleanField(default=True, verbose_name=_('Auto update pass/key')) - date_created = models.DateTimeField(auto_now=True, null=True, blank=True) - create_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + _password = models.CharField(max_length=256, null=True, blank=True, verbose_name=_('Password')) + _private_key = models.CharField(max_length=4096, null=True, blank=True, verbose_name=_('SSH private key')) + _public_key = models.CharField(max_length=4096, null=True, blank=True, verbose_name=_('SSH public key')) + as_default = models.BooleanField(default=True, verbose_name=_('As default')) comment = models.TextField(blank=True, verbose_name=_('Comment')) + date_created = models.DateTimeField(auto_now=True, null=True, blank=True) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) def __unicode__(self): return self.name + @property + def password(self): + return decrypt(self._password) + + @password.setter + def password(self, password_raw): + self._password = encrypt(password_raw) + + @property + def private_key(self): + return decrypt(self._private_key) + + @private_key.setter + def private_key(self, private_key_raw): + self._private_key = encrypt(private_key_raw) + + @property + def public_key(self): + return decrypt(self._public_key) + + @public_key.setter + def public_key(self, public_key_raw): + self._public_key = encrypt(public_key_raw) + class Meta: db_table = 'admin_user' + @classmethod + def generate_fake(cls, count=100): + from random import seed, choice + import forgery_py + from django.db import IntegrityError + + seed() + for i in range(count): + obj = cls(name=forgery_py.name.full_name(), + username=forgery_py.internet.user_name(), + password=forgery_py.lorem_ipsum.word(), + comment=forgery_py.lorem_ipsum.sentence(), + created_by='Fake') + try: + obj.save() + logger.debug('Generate fake asset group: %s' % obj.name) + except IntegrityError: + print('Error continue') + continue + class SystemUser(models.Model): PROTOCOL_CHOICES = ( @@ -102,7 +148,7 @@ class SystemUser(models.Model): home = models.CharField(max_length=64, blank=True, verbose_name=_('Home')) uid = models.IntegerField(blank=True, verbose_name=_('Uid')) date_created = models.DateTimeField(auto_now=True, null=True) - create_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) + created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) comment = models.CharField(max_length=128, blank=True, verbose_name=_('Comment')) def __unicode__(self): @@ -223,3 +269,7 @@ class Label(models.Model): class Meta: db_table = 'label' + +def generate_fake(): + for cls in (Asset, AssetGroup, IDC): + cls.generate_fake() diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html new file mode 100644 index 000000000..d7aae75aa --- /dev/null +++ b/apps/assets/templates/assets/admin_user_list.html @@ -0,0 +1,41 @@ +{% extends '_list_base.html' %} +{% load i18n %} +{% load common_tags %} +{% block content_left_head %} + {% trans "Create admin user" %} +{% 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/urls.py b/apps/assets/urls.py index 821fe144d..631dc7189 100644 --- a/apps/assets/urls.py +++ b/apps/assets/urls.py @@ -28,5 +28,10 @@ urlpatterns = [ 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'^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'), # url(r'^api/v1.0/', include(router.urls)), ] diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 2fb5bfe7f..9a42d3031 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,23 +1,4 @@ # ~*~ coding: utf-8 ~*~ # -from django.contrib.auth.mixins import UserPassesTestMixin -from django.urls import reverse_lazy - -from common.tasks import send_mail_async -from common.utils import reverse -from users.models import User - - -try: - import cStringIO as StringIO -except ImportError: - import StringIO - - -class AdminUserRequiredMixin(UserPassesTestMixin): - login_url = reverse_lazy('users:login') - - def test_func(self): - return self.request.user.is_staff diff --git a/apps/assets/views.py b/apps/assets/views.py index 16c54214c..2319b81f7 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -13,9 +13,9 @@ from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateVi from django.urls import reverse_lazy from django.views.generic.detail import DetailView, SingleObjectMixin -from .models import Asset, AssetGroup, IDC, AssetExtend +from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser from .forms import AssetForm, AssetGroupForm, IDCForm -from .utils import AdminUserRequiredMixin +from .hands import AdminUserRequiredMixin class AssetCreateView(CreateView): @@ -50,7 +50,7 @@ class AssetDetailView(DetailView): template_name = 'assets/asset_detail.html' -class AssetGroupCreateView(CreateView): +class AssetGroupCreateView(AdminUserRequiredMixin, CreateView): model = AssetGroup form_class = AssetGroupForm template_name = 'assets/asset_group_create.html' @@ -72,7 +72,7 @@ class AssetGroupCreateView(CreateView): return super(AssetGroupCreateView, self).form_valid(form) -class AssetGroupListView(ListView): +class AssetGroupListView(AdminUserRequiredMixin, ListView): model = AssetGroup paginate_by = settings.CONFIG.DISPLAY_PER_PAGE context_object_name = 'asset_group_list' @@ -101,7 +101,7 @@ class AssetGroupListView(ListView): return self.queryset -class AssetGroupDetailView(SingleObjectMixin, ListView): +class AssetGroupDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView): template_name = 'assets/asset_group_detail.html' paginate_by = settings.CONFIG.DISPLAY_PER_PAGE @@ -122,7 +122,7 @@ class AssetGroupDetailView(SingleObjectMixin, ListView): return super(AssetGroupDetailView, self).get_context_data(**kwargs) -class AssetGroupUpdateView(UpdateView): +class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView): model = AssetGroup form_class = AssetGroupForm template_name = 'assets/asset_group_create.html' @@ -138,13 +138,13 @@ class AssetGroupUpdateView(UpdateView): return super(AssetGroupUpdateView, self).get_context_data(**kwargs) -class AssetGroupDeleteView(DeleteView): +class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView): template_name = 'assets/delete_confirm.html' model = AssetGroup success_url = reverse_lazy('assets:asset-group-list') -class IDCListView(ListView): +class IDCListView(AdminUserRequiredMixin, ListView): model = IDC paginate_by = settings.CONFIG.DISPLAY_PER_PAGE context_object_name = 'idc_list' @@ -173,7 +173,7 @@ class IDCListView(ListView): return self.queryset -class IDCCreateView(CreateView): +class IDCCreateView(AdminUserRequiredMixin, CreateView): model = IDC form_class = IDCForm template_name = 'assets/idc_create.html' @@ -188,13 +188,59 @@ class IDCCreateView(CreateView): return super(IDCCreateView, self).get_context_data(**kwargs) -class IDCUpdateView(UpdateView): +class IDCUpdateView(AdminUserRequiredMixin, UpdateView): pass -class IDCDetailView(DetailView): +class IDCDetailView(AdminUserRequiredMixin, DetailView): pass -class IDCDeleteView(DeleteView): +class IDCDeleteView(AdminUserRequiredMixin, DeleteView): + pass + + +class AdminUserListView(AdminUserRequiredMixin, ListView): + model = AdminUser + 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', '') + } + kwargs.update(context) + return super(AdminUserListView, self).get_context_data(**kwargs) + + def get_queryset(self): + # Todo: Default group 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, CreateView): + pass + + +class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView): + pass + + +class AdminUserDetailView(AdminUserRequiredMixin, DetailView): + pass + + +class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView): pass diff --git a/apps/common/utils.py b/apps/common/utils.py index 7c3df178a..4451f2532 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from django.shortcuts import reverse as dj_reverse from django.conf import settings +from django.core import signing def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None, external=False): @@ -21,3 +22,12 @@ def get_object_or_none(model, **kwargs): except model.DoesNotExist: obj = None return obj + + +def encrypt(*args, **kwargs): + return signing.dumps(*args, **kwargs) + + +def decrypt(*args, **kwargs): + return signing.loads(*args, **kwargs) + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 8f77cb47d..07ac9bc25 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -21,7 +21,7 @@
  • {% trans 'Asset' %}
  • {% trans 'Asset group' %}
  • {% trans 'IDC' %}
  • -
  • {% trans 'Admin user' %}
  • +
  • {% trans 'Admin user' %}
  • {% trans 'System user' %}
  • {% trans 'Label' %}
  • @@ -30,10 +30,10 @@ {% trans 'Perms' %} diff --git a/apps/users/models.py b/apps/users/models.py index bbf89239f..f78c6082d 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -14,46 +14,9 @@ from django.dispatch import receiver from django.db import IntegrityError from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token - from django.core import signing -# class Role(models.Model): -# name = models.CharField('name', max_length=80, unique=True) -# permissions = models.ManyToManyField( -# Permission, -# verbose_name='permissions', -# blank=True, -# ) -# date_created = models.DateTimeField(auto_now_add=True) -# created_by = models.CharField(max_length=100) -# comment = models.CharField(max_length=80, blank=True) -# -# def __unicode__(self): -# return self.name -# -# def delete(self, using=None, keep_parents=False): -# if self.users.all().count() > 0: -# raise OperationalError('Role %s has some member, should not be delete.' % self.name) -# else: -# return super(Role, self).delete(using=using, keep_parents=keep_parents) -# -# class Meta: -# db_table = 'role' -# -# @classmethod -# def initial(cls): -# roles = { -# 'Administrator': {'permissions': Permission.objects.all(), 'comment': '管理员'}, -# 'User': {'permissions': [], 'comment': '用户'}, -# 'Auditor': {'permissions': Permission.objects.filter(content_type__app_label='audits'), -# 'comment': '审计员'}, -# } - -# for role_name, props in roles.items(): -# if not cls.objects.filter(name=role_name): -# role = cls.objects.create(name=role_name, comment=props.get('comment', ''), created_by='System') -# if props.get('permissions'): -# role.permissions = props.get('permissions') +from common.utils import encrypt, decrypt class UserGroup(models.Model): @@ -65,6 +28,11 @@ class UserGroup(models.Model): def __unicode__(self): return self.name + def has_member(self, user): + if user in self.users.all(): + return True + return False + class Meta: db_table = 'user-group' @@ -113,8 +81,8 @@ class User(AbstractUser): phone = models.CharField(max_length=20, blank=True, verbose_name=_('Phone')) enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP')) secret_key_otp = models.CharField(max_length=16, blank=True) - private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key')) - public_key = models.CharField(max_length=1000, blank=True, verbose_name=_('ssh public key')) + _private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key')) + _public_key = models.CharField(max_length=1000, blank=True, verbose_name=_('ssh public key')) comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment')) is_first_login = models.BooleanField(default=False) date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, @@ -131,8 +99,8 @@ class User(AbstractUser): #: user = User(username='example', ...) #: user.set_password('password') @password_raw.setter - def password_raw(self, raw_password): - self.set_password(raw_password) + def password_raw(self, password_raw_): + self.set_password(password_raw_) @property def is_expired(self): @@ -141,6 +109,22 @@ class User(AbstractUser): else: return True + @property + def private_key(self): + return decrypt(self._private_key) + + @private_key.setter + def private_key(self, private_key_raw): + self._private_key = encrypt(private_key_raw) + + @property + def public_key(self): + return decrypt(self._public_key) + + @public_key.setter + def public_key(self, public_key_raw): + self._public_key = encrypt(public_key_raw) + @property def is_superuser(self): if self.role == 'Admin': @@ -198,6 +182,11 @@ class User(AbstractUser): def generate_reset_token(self): return signing.dumps({'reset': self.id, 'email': self.email}) + def is_member_of(self, user_group): + if user_group in self.groups.all(): + return True + return False + @classmethod def validate_reset_token(cls, token, max_age=3600): try: