diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 1d62eadcb..221420ce0 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -90,6 +90,7 @@ 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) @@ -120,15 +121,15 @@ class AdminUserForm(forms.ModelForm): 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: - print(private_key_file) admin_user.private_key = private_key_file.read() admin_user.save() return self.instance class Meta: model = AdminUser - fields = ['name', 'username', 'password', 'private_key_file', 'as_default', 'comment'] + fields = ['name', 'username', 'auto_generate_key', 'password', 'private_key_file', 'as_default', 'comment'] widgets = { 'name': forms.TextInput(attrs={'placeholder': _('Name')}), 'username': forms.TextInput(attrs={'placeholder': _('Username')}), @@ -138,3 +139,76 @@ class AdminUserForm(forms.ModelForm): 'username': '* required', } + +class SystemUserForm(forms.ModelForm): + # Admin user assets define, let user select, save it in form not in view + assets = forms.ModelMultipleChoiceField(queryset=Asset.objects.all(), + label=_('Asset'), + required=False, + widget=forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Select assets')}) + ) + asset_groups = forms.ModelMultipleChoiceField(queryset=AssetGroup.objects.all(), + label=_('Asset group'), + required=False, + widget=forms.SelectMultiple( + attrs={'class': 'select2', + 'data-placeholder': _('Select asset groups')}) + ) + 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) + # Need use upload private key file except paste private key content + private_key_file = forms.FileField(required=False) + + def __init__(self, *args, **kwargs): + # When update a admin user instance, initial it + if kwargs.get('instance'): + initial = kwargs.get('initial', {}) + initial['assets'] = kwargs['instance'].assets.all() + initial['asset_groups'] = kwargs['instance'].asset_groups.all() + super(SystemUserForm, self).__init__(*args, **kwargs) + + def _save_m2m(self): + # Save assets relation with admin user + super(SystemUserForm, self)._save_m2m() + assets = self.cleaned_data['assets'] + asset_groups = self.cleaned_data['asset_groups'] + self.instance.assets.clear() + self.instance.assets.add(*tuple(assets)) + self.instance.asset_groups.clear() + self.instance.asset_groups.add(*tuple(asset_groups)) + + def save(self, commit=True): + # Because we define custom field, so we need rewrite :method: `save` + system_user = super(SystemUserForm, self).save(commit=commit) + password = self.cleaned_data['password'] + private_key_file = self.cleaned_data['private_key_file'] + + if password: + system_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: + system_user.private_key = private_key_file.read() + system_user.save() + return self.instance + + class Meta: + model = SystemUser + fields = [ + 'name', 'username', 'protocol', 'auto_generate_key', 'password', 'private_key_file', 'as_default', + 'auto_push', 'auto_update', 'sudo', 'comment', 'shell', 'home', 'uid', + ] + widgets = { + 'name': forms.TextInput(attrs={'placeholder': _('Name')}), + 'username': forms.TextInput(attrs={'placeholder': _('Username')}), + } + help_texts = { + 'name': '* required', + 'username': '* required', + 'auth_push': 'Auto push system user to asset', + 'auth_update': 'Auto update system user ssh key', + } \ No newline at end of file diff --git a/apps/assets/models.py b/apps/assets/models.py index 213a49822..6701d65ce 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -135,28 +135,72 @@ class SystemUser(models.Model): ('telnet', 'telnet'), ) name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) - username = models.CharField(max_length=16, blank=True, verbose_name=_('Username')) - password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) - protocol = models.CharField(max_length=16, default='ssh', verbose_name=_('Protocol')) - private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) - public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) - is_default = models.BooleanField(default=True, verbose_name=_('As default')) + username = models.CharField(max_length=16, verbose_name=_('Username')) + _password = models.CharField(max_length=256, blank=True, verbose_name=_('Password')) + protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) + _private_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH private key')) + _public_key = models.CharField(max_length=4096, blank=True, verbose_name=_('SSH public key')) + as_default = models.BooleanField(default=False, verbose_name=_('As default')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_update = models.BooleanField(default=True, verbose_name=_('Auto update pass/key')) - sudo = models.TextField(max_length=4096, blank=True, verbose_name=_('Sudo')) - shell = models.CharField(max_length=64, blank=True, verbose_name=_('Shell')) + sudo = models.TextField(max_length=4096, default='/user/bin/whoami', verbose_name=_('Sudo')) + shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) 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) + uid = models.IntegerField(null=True, blank=True, verbose_name=_('Uid')) + date_created = models.DateTimeField(auto_now=True) created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) - comment = models.CharField(max_length=128, blank=True, verbose_name=_('Comment')) + comment = models.TextField(max_length=128, blank=True, verbose_name=_('Comment')) 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 = 'system_user' + @classmethod + def generate_fake(cls, count=100): + from random import seed + 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 AssetGroup(models.Model): name = models.CharField(max_length=64, unique=True, verbose_name=_('Name')) @@ -206,7 +250,7 @@ class Asset(models.Model): password = models.CharField(max_length=256, null=True, blank=True, verbose_name=_("Admin password")) admin_user = models.ForeignKey(AdminUser, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user")) - system_user = models.ManyToManyField(SystemUser, blank=True, verbose_name=_("System User")) + system_user = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User")) idc = models.ForeignKey(IDC, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('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')) @@ -227,7 +271,7 @@ class Asset(models.Model): comment = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Comment')) def __unicode__(self): - return '%(ip)s:%(port)d' % {'ip': self.ip, 'port': self.port} + return '%(ip)s:%(port)s' % {'ip': self.ip, 'port': self.port} def initial(self): pass diff --git a/apps/assets/templates/assets/admin_user_create_update.html b/apps/assets/templates/assets/admin_user_create_update.html index b340b7c3a..7738adec0 100644 --- a/apps/assets/templates/assets/admin_user_create_update.html +++ b/apps/assets/templates/assets/admin_user_create_update.html @@ -13,7 +13,7 @@
-
{% trans 'Create asset group' %}
+
{% trans 'Create admin user' %}
@@ -31,6 +31,12 @@ {% csrf_token %} {{ form.name|bootstrap_horizontal }} {{ form.username|bootstrap_horizontal }} +
+ +
+ {{ form.auto_generate_key}} +
+
{{ form.password|bootstrap_horizontal }} {{ form.private_key_file|bootstrap_horizontal }}
diff --git a/apps/assets/templates/assets/system_user_create_update.html b/apps/assets/templates/assets/system_user_create_update.html new file mode 100644 index 000000000..cfb40e8f8 --- /dev/null +++ b/apps/assets/templates/assets/system_user_create_update.html @@ -0,0 +1,88 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap %} +{% block custom_head_css_js %} + + +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+ {% csrf_token %} + {{ form.name|bootstrap_horizontal }} + {{ form.username|bootstrap_horizontal }} + {{ form.protocol|bootstrap_horizontal }} +
+ +
+ {{ form.auto_generate_key}} +
+
+ {{ form.password|bootstrap_horizontal }} + {{ form.private_key_file|bootstrap_horizontal }} +
+ +
+ {{ form.as_default}} +
+
+
+ +
+ {{ form.auto_push}} +
+
+
+ +
+ {{ form.auto_update}} +
+
+ {{ form.assets|bootstrap_horizontal }} + {{ form.asset_groups|bootstrap_horizontal }} + {{ form.sudo|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} + {{ form.home|bootstrap_horizontal }} + {{ form.shell|bootstrap_horizontal }} + {{ form.uid|bootstrap_horizontal }} + +
+
+ + +
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html new file mode 100644 index 000000000..12f2b0bdf --- /dev/null +++ b/apps/assets/templates/assets/system_user_list.html @@ -0,0 +1,43 @@ +{% extends '_list_base.html' %} +{% load i18n %} +{% load common_tags %} +{% block content_left_head %} + {% trans "Create system user" %} +{% endblock %} + +{% block table_head %} + {% trans 'ID' %} + {% trans 'Name' %} + {% trans 'Username' %} + {% trans 'Asset num' %} + {% trans 'Asset group num' %} + {% trans 'Lost connection' %} + {% trans 'Comment' %} + +{% endblock %} + +{% block table_body %} + {% for system_user in system_user_list %} + + {{ system_user.id }} + + + {{ system_user.name }} + + + {{ system_user.username }} + {{ system_user.assets.count }} + {{ system_user.asset_groups.count }} + {{ system_user.assets.count }} + {{ system_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 631dc7189..d19f294a4 100644 --- a/apps/assets/urls.py +++ b/apps/assets/urls.py @@ -33,5 +33,10 @@ urlpatterns = [ 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'^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'^api/v1.0/', include(router.urls)), ] diff --git a/apps/assets/views.py b/apps/assets/views.py index 7ff0292a5..47c92c234 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -11,7 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser -from .forms import AssetForm, AssetGroupForm, IDCForm, AdminUserForm +from .forms import AssetForm, AssetGroupForm, IDCForm, AdminUserForm, SystemUserForm from .hands import AdminUserRequiredMixin @@ -294,3 +294,68 @@ class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView): model = AdminUser template_name = 'assets/delete_confirm.html' success_url = 'assets:admin-user-list' + + +class SystemUserListView(AdminUserRequiredMixin, ListView): + model = SystemUser + paginate_by = settings.CONFIG.DISPLAY_PER_PAGE + context_object_name = 'system_user_list' + template_name = 'assets/system_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(SystemUserListView, self).get_context_data(**kwargs) + + def get_queryset(self): + # Todo: Default order by lose asset connection num + self.queryset = super(SystemUserListView, 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 SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): + model = SystemUser + form_class = SystemUserForm + template_name = 'assets/system_user_create_update.html' + success_url = reverse_lazy('assets:system-user-list') + success_message = _('Create system user %s successfully.') + + def get_context_data(self, **kwargs): + context = { + 'app': 'assets', + 'action': 'Create system user' + } + kwargs.update(context) + return super(SystemUserCreateView, self).get_context_data(**kwargs) + + def get_success_message(self, cleaned_data): + return self.success_message % ( + reverse_lazy('assets:system-user-detail', kwargs={'pk': self.object.pk}), + self.object.name, + ) + + +class SystemUserUpdateView(UpdateView): + pass + + +class SystemUserDetailView(DetailView): + pass + + +class SystemUserDeleteView(DeleteView): + pass + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 07ac9bc25..9eee251ca 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -22,7 +22,7 @@
  • {% trans 'Asset group' %}
  • {% trans 'IDC' %}
  • {% trans 'Admin user' %}
  • -
  • {% trans 'System user' %}
  • +
  • {% trans 'System user' %}
  • {% trans 'Label' %}