diff --git a/orchestra/apps/systemusers/admin.py b/orchestra/apps/systemusers/admin.py index a1cf0bc0..b71b98ea 100644 --- a/orchestra/apps/systemusers/admin.py +++ b/orchestra/apps/systemusers/admin.py @@ -17,6 +17,7 @@ from orchestra.forms import UserCreationForm, UserChangeForm from . import settings from .actions import grant_permission from .filters import IsMainListFilter +from .forms import SystemUserCreationForm, SystemUserChangeForm from .models import SystemUser @@ -46,8 +47,8 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende change_readonly_fields = ('username',) filter_horizontal = ('groups',) filter_by_account_fields = ('groups',) - add_form = UserCreationForm - form = UserChangeForm + add_form = SystemUserCreationForm + form = SystemUserChangeForm ordering = ('-id',) actions = (grant_permission,) change_view_actions = actions @@ -70,49 +71,13 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende def get_form(self, request, obj=None, **kwargs): form = super(SystemUserAdmin, self).get_form(request, obj=obj, **kwargs) - duplicate = lambda n: (n, n) + form.account = self.account if obj: # Has to be done here and not in the form because of strange phenomenon # derived from monkeypatching formfield.widget.render on AccountAdminMinxin, # don't ask. formfield = form.base_fields['groups'] formfield.queryset = formfield.queryset.exclude(id=obj.id) - username = obj.username - choices=( - duplicate(self.account.main_systemuser.get_home()), - duplicate(obj.get_home()), - ) - else: - username = '' - choices=( - duplicate(self.account.main_systemuser.get_home()), - duplicate(SystemUser(username=username).get_home()), - ) - form.base_fields['home'].widget = forms.Select(choices=choices) - if obj and (obj.is_main or obj.has_shell): - # hidde home option for shell users - form.base_fields['home'].widget = forms.HiddenInput() - form.base_fields['directory'].widget = forms.HiddenInput() - else: - # Some javascript for hidde home/directory inputs when convinient - form.base_fields['shell'].widget.attrs = { - 'onChange': textwrap.dedent("""\ - field = $(".form-row.field-home.field-directory"); - if ($.inArray(this.value, %s) < 0) { - field.addClass("hidden"); - } else { - field.removeClass("hidden"); - };""" % str(list(settings.SYSTEMUSERS_DISABLED_SHELLS))) - } - form.base_fields['home'].widget.attrs = { - 'onChange': textwrap.dedent("""\ - field = $(".field-box.field-directory"); - if (this.value.search("%s") > 0) { - field.addClass("hidden"); - } else { - field.removeClass("hidden"); - };""" % username) - } return form def has_delete_permission(self, request, obj=None): diff --git a/orchestra/apps/systemusers/forms.py b/orchestra/apps/systemusers/forms.py new file mode 100644 index 00000000..30667b6a --- /dev/null +++ b/orchestra/apps/systemusers/forms.py @@ -0,0 +1,72 @@ +import textwrap + +from django import forms + +from orchestra.forms import UserCreationForm, UserChangeForm + +from . import settings +from .models import SystemUser + +class SystemUserFormMixin(object): + MOCK_USERNAME = '' + + def __init__(self, *args, **kwargs): + super(SystemUserFormMixin, self).__init__(*args, **kwargs) + duplicate = lambda n: (n, n) + if self.instance.pk: + username = self.instance.username + choices=( + duplicate(self.account.main_systemuser.get_base_home()), + duplicate(self.instance.get_base_home()), + ) + else: + username = self.MOCK_USERNAME + choices=( + duplicate(self.account.main_systemuser.get_base_home()), + duplicate(SystemUser(username=username).get_base_home()), + ) + self.fields['home'].widget = forms.Select(choices=choices) + if self.instance.pk and (self.instance.is_main or self.instance.has_shell): + # hidde home option for shell users + self.fields['home'].widget = forms.HiddenInput() + self.fields['directory'].widget = forms.HiddenInput() + elif self.instance.pk and (self.instance.get_base_home() == self.instance.home): + self.fields['directory'].widget = forms.HiddenInput() + if self.instance.pk and not self.instance.is_main: + # Some javascript for hidde home/directory inputs when convinient + self.fields['shell'].widget.attrs = { + 'onChange': textwrap.dedent("""\ + field = $(".field-home, .field-directory"); + input = $("#id_home, #id_directory"); + if ($.inArray(this.value, %s) < 0) { + field.addClass("hidden"); + } else { + field.removeClass("hidden"); + input.removeAttr("type"); + };""" % str(list(settings.SYSTEMUSERS_DISABLED_SHELLS))) + } + self.fields['home'].widget.attrs = { + 'onChange': textwrap.dedent("""\ + field = $(".field-box.field-directory"); + input = $("#id_directory"); + if (this.value.search("%s") > 0) { + field.addClass("hidden"); + } else { + field.removeClass("hidden"); + input.removeAttr("type"); + };""" % username) + } + + def clean(self): + home = self.cleaned_data.get('home') + if home and self.MOCK_USERNAME in home: + username = self.cleaned_data.get('username', '') + self.cleaned_data['home'] = home.replace(self.MOCK_USERNAME, username) + + +class SystemUserCreationForm(SystemUserFormMixin, UserCreationForm): + pass + + +class SystemUserChangeForm(SystemUserFormMixin, UserChangeForm): + pass diff --git a/orchestra/apps/systemusers/models.py b/orchestra/apps/systemusers/models.py index 6bb72ffc..1ce4d7bb 100644 --- a/orchestra/apps/systemusers/models.py +++ b/orchestra/apps/systemusers/models.py @@ -2,6 +2,7 @@ import os from django.contrib.auth.hashers import make_password from django.core import validators +from django.core.exceptions import ValidationError from django.core.mail import send_mail from django.db import models from django.utils.functional import cached_property @@ -30,7 +31,7 @@ class SystemUser(models.Model): password = models.CharField(_("password"), max_length=128) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='systemusers') - home = models.CharField(_("home"), max_length=256, blank=False, + home = models.CharField(_("home"), max_length=256, blank=True, help_text=_("Starting location when login with this no-shell user.")) directory = models.CharField(_("directory"), max_length=256, blank=True, help_text=_("Optional directory relative to user's home.")) @@ -68,10 +69,26 @@ class SystemUser(models.Model): def has_shell(self): return self.shell not in settings.SYSTEMUSERS_DISABLED_SHELLS - def clean(self): - if self.has_shell or self.is_main: + def save(self, *args, **kwargs): + if not self.home: self.home = self.get_base_home() - self.directory = '' + super(SystemUser, self).save(*args, **kwargs) + + def clean(self): + # TODO do it right + if self.has_shell and self.directory: + raise ValidationError({ + 'directory': _("Directory with shell users can not be specified.") + }) + if self.pk and self.is_main and self.directory: + raise ValidationError({ + 'directory': _("Directory with main system users can not be specified.") + }) + if self.home == self.get_base_home() and self.directory: + raise ValidationError({ + 'directory': _("Directory on the user's base home is not allowed.") + }) + # TODO valid home exists def set_password(self, raw_password): self.password = make_password(raw_password) diff --git a/orchestra/apps/systemusers/serializers.py b/orchestra/apps/systemusers/serializers.py index fa6eb8ed..86e0d690 100644 --- a/orchestra/apps/systemusers/serializers.py +++ b/orchestra/apps/systemusers/serializers.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError from django.forms import widgets from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext, ugettext_lazy as _ @@ -34,6 +35,24 @@ class SystemUserSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): ) postonly_fields = ('username',) + def validate(self, attrs): + user = SystemUser( + username=attrs.get('username') or self.object.username, + shell=attrs.get('shell') or self.object.shell, + ) + if 'home' in attrs and attrs['home']: + home = attrs['home'].rstrip('/') + '/' + if user.has_shell: + if home != user.get_base_home(): + raise ValidationError({ + 'home': _("Not a valid home directory.") + }) + elif home not in (user.get_home(), self.account.main_systemuser.get_home()): + raise ValidationError({ + 'home': _("Not a valid home directory.") + }) + return attrs + def validate_password(self, attrs, source): """ POST only password """ if self.object: