diff --git a/TODO.md b/TODO.md
index d1724296..d8dfef36 100644
--- a/TODO.md
+++ b/TODO.md
@@ -354,3 +354,5 @@ make django admin taskstate uncollapse fucking traceback, ( if exists ?)
resorce monitoring more efficient, less mem an better queries for calc current data
# best_price rating method
+
+# select contact with one result: redirect
diff --git a/orchestra/contrib/accounts/admin.py b/orchestra/contrib/accounts/admin.py
index 2d5e4e6c..e0a7a61e 100644
--- a/orchestra/contrib/accounts/admin.py
+++ b/orchestra/contrib/accounts/admin.py
@@ -58,7 +58,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
add_form = AccountCreationForm
form = UserChangeForm
filter_horizontal = ()
- change_readonly_fields = ('username', 'main_systemuser_link')
+ change_readonly_fields = ('username', 'main_systemuser_link', 'is_active')
change_form_template = 'admin/accounts/account/change_form.html'
actions = [disable, list_contacts, service_report, SendEmail(), delete_related_services]
change_view_actions = [disable, service_report]
@@ -139,8 +139,14 @@ class AccountListAdmin(AccountAdmin):
'original_model': original_model,
}
context.update(extra_context or {})
- return super(AccountListAdmin, self).changelist_view(request,
- extra_context=context)
+ response = super(AccountListAdmin, self).changelist_view(request, extra_context=context)
+ if hasattr(response, 'context_data'):
+ # user has submitted a change list change, we redirect directly to the add view
+ # if there is only one result
+ queryset = response.context_data['cl'].queryset
+ if len(queryset) == 1:
+ return HttpResponseRedirect('../?account=%i' % queryset[0].pk)
+ return response
class AccountAdminMixin(object):
@@ -283,8 +289,7 @@ class AccountAdminMixin(object):
request_copy.pop('account')
request.GET = request_copy
context.update(extra_context or {})
- return super(AccountAdminMixin, self).changelist_view(request,
- extra_context=context)
+ return super(AccountAdminMixin, self).changelist_view(request, extra_context=context)
class SelectAccountAdminMixin(AccountAdminMixin):
diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py
index c666ade3..99c2b04a 100644
--- a/orchestra/contrib/domains/backends.py
+++ b/orchestra/contrib/domains/backends.py
@@ -177,7 +177,12 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
def commit(self):
""" ideally slave should be restarted after master """
- self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi')
+ self.append(textwrap.dedent("""\
+ if [[ $UPDATED == 1 ]]; then
+ nohup bash -c 'sleep 1 && service bind9 reload' &> /dev/null &
+ fi
+ """)
+ )
def get_context(self, domain):
context = {
diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py
index f73743bb..7fae0ba1 100644
--- a/orchestra/contrib/domains/models.py
+++ b/orchestra/contrib/domains/models.py
@@ -252,7 +252,8 @@ class Record(models.Model):
def clean(self):
""" validates record value based on its type """
# validate value
- self.value = self.value.lower().strip()
+ if self.type != self.TXT:
+ self.value = self.value.lower().strip()
choices = {
self.MX: validators.validate_mx_record,
self.NS: validators.validate_zone_label,
diff --git a/orchestra/contrib/domains/validators.py b/orchestra/contrib/domains/validators.py
index a5d786d4..e3e8114e 100644
--- a/orchestra/contrib/domains/validators.py
+++ b/orchestra/contrib/domains/validators.py
@@ -124,5 +124,5 @@ def validate_zone(zone):
if check.exit_code == 127:
logger.error("Cannot validate domain zone: %s not installed." % checkzone)
elif check.exit_code == 1:
- errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
+ errors = re.compile(r'zone.*: (.*)').findall(check.stdout.decode('utf8'))[:-1]
raise ValidationError(', '.join(errors))
diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py
index c4174644..f50aeeef 100644
--- a/orchestra/contrib/lists/backends.py
+++ b/orchestra/contrib/lists/backends.py
@@ -78,7 +78,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
Includes MailmanVirtualDomainBackend
"""
verbose_name = "Mailman"
- addresses = [
+ address_suffixes = [
'',
'-admin',
'-bounces',
@@ -99,9 +99,12 @@ class MailmanBackend(MailmanVirtualDomainBackend):
def get_virtual_aliases(self, context):
aliases = ['# %(banner)s' % context]
- for address in self.addresses:
- context['address'] = address
- aliases.append("%(address_name)s%(address)s@%(domain)s\t%(name)s%(address)s" % context)
+ for suffix in self.address_suffixes:
+ context['suffix'] = suffix
+ # Because mailman doesn't properly handle lists aliases we need two virtual aliases
+ aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
+ # And another with the original list name; Mailman generates links with it
+ aliases.append("%(name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
return '\n'.join(aliases)
def save(self, mail_list):
@@ -122,7 +125,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
UPDATED_VIRTUAL_ALIAS=1
else
if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
- sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
+ sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
echo "${aliases}" >> %(virtual_alias)s
UPDATED_VIRTUAL_ALIAS=1
@@ -159,7 +162,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
context = self.get_context(mail_list)
self.exclude_virtual_alias_domain(context)
self.append(textwrap.dedent("""
- sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
+ sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s""") % context
)
self.append(textwrap.dedent("""
@@ -201,7 +204,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
'domain': mail_list.address_domain or settings.LISTS_DEFAULT_DOMAIN,
'address_name': mail_list.get_address_name(),
'address_domain': mail_list.address_domain,
- 'address_regex': '\|'.join(self.addresses),
+ 'suffixes_regex': '\|'.join(self.address_suffixes),
'admin': mail_list.admin_email,
'mailman_root': settings.LISTS_MAILMAN_ROOT_DIR,
})
diff --git a/orchestra/contrib/mailboxes/backends.py b/orchestra/contrib/mailboxes/backends.py
index 30c48aeb..e007c9f1 100644
--- a/orchestra/contrib/mailboxes/backends.py
+++ b/orchestra/contrib/mailboxes/backends.py
@@ -48,12 +48,16 @@ class SieveFilteringMixin(object):
class UNIXUserMaildirBackend(SieveFilteringMixin, ServiceController):
"""
Assumes that all system users on this servers all mail accounts.
- If you want to have system users AND mailboxes on the same server you should consider using virtual mailboxes
+ If you want to have system users AND mailboxes on the same server you should consider using virtual mailboxes.
+ Supports quota allocation via resources.disk.allocated.
"""
SHELL = '/dev/null'
verbose_name = _("UNIX maildir user")
model = 'mailboxes.Mailbox'
+ doc_settings = (settings,
+ ('MAILBOXES_USE_ACCOUNT_AS_GROUP',)
+ )
def save(self, mailbox):
context = self.get_context(mailbox)
@@ -89,7 +93,7 @@ class UNIXUserMaildirBackend(SieveFilteringMixin, ServiceController):
context = self.get_context(mailbox)
self.append('mv %(home)s %(home)s.deleted || exit_code=$?' % context)
self.append(textwrap.dedent("""
- { sleep 2 && killall -u %(user)s -s KILL; } &
+ nohup bash -c '{ sleep 2 && killall -u %(user)s -s KILL; }' &> /dev/null &
killall -u %(user)s || true
userdel %(user)s || true
groupdel %(user)s || true""") % context
@@ -98,7 +102,7 @@ class UNIXUserMaildirBackend(SieveFilteringMixin, ServiceController):
def get_context(self, mailbox):
context = {
'user': mailbox.name,
- 'group': mailbox.name,
+ 'group': mailbox.account.username if settings.MAILBOXES_USE_ACCOUNT_AS_GROUP else mailbox.name,
'name': mailbox.name,
'password': mailbox.password if mailbox.active else '*%s' % mailbox.password,
'home': mailbox.get_home(),
@@ -147,7 +151,7 @@ class DovecotPostfixPasswdVirtualUserBackend(SieveFilteringMixin, ServiceControl
def delete(self, mailbox):
context = self.get_context(mailbox)
self.append(textwrap.dedent("""\
- { sleep 2 && killall -u %(uid)s -s KILL; } &
+ nohup bash -c 'sleep 2 && killall -u %(uid)s -s KILL' &> /dev/null &
killall -u %(uid)s || true
sed -i '/^%(user)s:.*/d' %(passwd_path)s
sed -i '/^%(user)s@%(mailbox_domain)s\s.*/d' %(virtual_mailbox_maps)s
@@ -224,10 +228,10 @@ class PostfixAddressVirtualDomainBackend(ServiceController):
domain = context['domain']
if domain.name != context['local_domain'] and self.is_local_domain(domain):
self.append(textwrap.dedent("""
- [[ $(grep '^\s*%(domain)s\s*$' %(virtual_alias_domains)s) ]] || {
+ if [[ ! $(grep '^\s*%(domain)s\s*$' %(virtual_alias_domains)s) ]]; then
echo '%(domain)s' >> %(virtual_alias_domains)s
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
- }""") % context
+ fi""") % context
)
def is_last_domain(self, domain):
@@ -237,9 +241,10 @@ class PostfixAddressVirtualDomainBackend(ServiceController):
domain = context['domain']
if self.is_last_domain(domain):
self.append(textwrap.dedent("""\
- sed -i '/^%(domain)s\s*/d;{!q0;q1}' %(virtual_alias_domains)s && \\
+ if [[ $(grep '^%(domain)s\s*$' %(virtual_alias_domains)s) ]]; then
+ sed -i '/^%(domain)s\s*/d' %(virtual_alias_domains)s
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
- """) % context
+ fi""") % context
)
def save(self, address):
@@ -307,9 +312,10 @@ class PostfixAddressBackend(PostfixAddressVirtualDomainBackend):
else:
logger.warning("Address %i is empty" % address.pk)
self.append(textwrap.dedent("""
- sed -i '/^%(email)s\s/d;{!q0;q1}' %(virtual_alias_maps)s && \\
+ if [[ $(grep '^%(email)s\s' %(virtual_alias_maps)s) ]]; then
+ sed -i '/^%(email)s\s/d' %(virtual_alias_maps)s
UPDATED_VIRTUAL_ALIAS_MAPS=1
- """) % context
+ fi""") % context
)
# Virtual mailbox stuff
# destination = []
diff --git a/orchestra/contrib/mailboxes/settings.py b/orchestra/contrib/mailboxes/settings.py
index bd59983a..3cbc3f39 100644
--- a/orchestra/contrib/mailboxes/settings.py
+++ b/orchestra/contrib/mailboxes/settings.py
@@ -45,6 +45,12 @@ MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH',
)
+MAILBOXES_USE_ACCOUNT_AS_GROUP = Setting('MAILBOXES_USE_ACCOUNT_AS_GROUP',
+ False,
+ help_text="Group used for system user based mailboxes. If False mailbox.name will be used as group."
+)
+
+
MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = Setting('MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
'/etc/postfix/virtual_mailboxes'
)
diff --git a/orchestra/contrib/orchestration/helpers.py b/orchestra/contrib/orchestration/helpers.py
index c0c0f8e4..e93748f1 100644
--- a/orchestra/contrib/orchestration/helpers.py
+++ b/orchestra/contrib/orchestration/helpers.py
@@ -87,9 +87,9 @@ def message_user(request, logs):
if log.state != log.EXCEPTION:
# EXCEPTION logs are not stored on the database
ids.append(log.pk)
- if log.state in (log.SUCCESS, log.NOTHING):
+ if log.is_success:
successes += 1
- elif log.state in (log.RECEIVED, log.STARTED):
+ elif not log.has_finished:
async += 1
async_ids.append(log.id)
errors = total-successes-async
diff --git a/orchestra/contrib/orchestration/management/commands/orchestrate.py b/orchestra/contrib/orchestration/management/commands/orchestrate.py
index 33cf0d0b..d4a35ee3 100644
--- a/orchestra/contrib/orchestration/management/commands/orchestrate.py
+++ b/orchestra/contrib/orchestration/management/commands/orchestrate.py
@@ -79,7 +79,7 @@ class Command(BaseCommand):
route, __ = key
backend, operations = value
servers.append(str(route.host))
- self.stdout.write('# Execute on %s' % server.name)
+ self.stdout.write('# Execute on %s' % route.host)
for method, commands in backend.scripts:
script = '\n'.join(commands)
self.stdout.write(script)
diff --git a/orchestra/contrib/orchestration/manager.py b/orchestra/contrib/orchestration/manager.py
index 3136353e..8e3390c2 100644
--- a/orchestra/contrib/orchestration/manager.py
+++ b/orchestra/contrib/orchestration/manager.py
@@ -28,7 +28,7 @@ def keep_log(execute, log, operations):
log = kwargs['log']
try:
log = execute(*args, **kwargs)
- if log.state != log.SUCCESS:
+ if not log.is_success:
send_report(execute, args, log)
except Exception as e:
trace = traceback.format_exc()
diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py
index a5c735d9..328176a9 100644
--- a/orchestra/contrib/orchestration/models.py
+++ b/orchestra/contrib/orchestration/models.py
@@ -96,6 +96,10 @@ class BackendLog(models.Model):
def has_finished(self):
return self.state not in (self.STARTED, self.RECEIVED)
+ @property
+ def is_success(self):
+ return self.state in (self.SUCCESS, self.NOTHING)
+
def backend_class(self):
return ServiceBackend.get_backend(self.backend)
diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py
index 8a92ebf7..13179a0a 100644
--- a/orchestra/contrib/systemusers/backends.py
+++ b/orchestra/contrib/systemusers/backends.py
@@ -12,13 +12,16 @@ from . import settings
class UNIXUserBackend(ServiceController):
"""
Basic UNIX system user/group support based on useradd, usermod, userdel and groupdel.
+ Autodetects and uses ACL if available, for better permission management.
"""
verbose_name = _("UNIX user")
model = 'systemusers.SystemUser'
- actions = ('save', 'delete', 'set_permission', 'validate_path')
- doc_settings = (settings,
- ('SYSTEMUSERS_DEFAULT_GROUP_MEMBERS', 'SYSTEMUSERS_MOVE_ON_DELETE_PATH')
- )
+ actions = ('save', 'delete', 'set_permission', 'validate_path_exists')
+ doc_settings = (settings, (
+ 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
+ 'SYSTEMUSERS_MOVE_ON_DELETE_PATH',
+ 'SYSTEMUSERS_FORBIDDEN_PATHS'
+ ))
def save(self, user):
context = self.get_context(user)
@@ -33,15 +36,24 @@ class UNIXUserBackend(ServiceController):
else
useradd %(user)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
fi
- mkdir -p %(home)s
- chmod 750 %(home)s
- chown %(user)s:%(user)s %(home)s""") % context
+ mkdir -p %(base_home)s
+ chmod 750 %(base_home)s
+ chown %(user)s:%(user)s %(base_home)s""") % context
)
if context['home'] != context['base_home']:
self.append(textwrap.dedent("""
- mkdir -p %(base_home)s
- chmod 750 %(base_home)s
- chown %(user)s:%(user)s %(base_home)s""") % context
+ if [[ $(mount | grep "^$(df %(home)s|grep '^/')\s" | grep acl) ]]; then
+ chown %(mainuser)s:%(mainuser)s %(home)s
+ # Home access
+ setfacl -m u:%(user)s:--x '%(mainuser_home)s'
+ # Grant perms to future files within the directory
+ setfacl -m d:u:%(user)s:rwx %(home)s
+ # Grant access to main user
+ setfacl -m d:u:%(mainuser)s:rwx %(home)s
+ else
+ chmod g+rxw %(home)s
+ chown %(user)s:%(user)s %(home)s
+ fi""") % context
)
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
context['member'] = member
@@ -54,7 +66,7 @@ class UNIXUserBackend(ServiceController):
if not context['user']:
return
self.append(textwrap.dedent("""\
- { sleep 2 && killall -u %(user)s -s KILL; } &
+ nohup bash -c 'sleep 2 && killall -u %(user)s -s KILL' &> /dev/null &
killall -u %(user)s || true
userdel %(user)s || exit_code=$?
groupdel %(group)s || exit_code=$?
@@ -71,15 +83,12 @@ class UNIXUserBackend(ServiceController):
'perm_action': user.set_perm_action,
'perm_home': user.set_perm_base_home,
'perm_to': os.path.join(user.set_perm_base_home, user.set_perm_home_extension),
- 'exclude': '',
})
-
exclude_acl = []
- for exclude in settings.SYSTEMUSERS_EXLUDE_ACL_PATHS:
- context['exclude'] = exclude
- exclude_acl.append('-not -path "%(perm_home)s/%(exclude)s"' % context)
- if exclude_acl:
- context['exclude'] = ' \\\n -a '.join(exclude_acl)
+ for exclude in settings.SYSTEMUSERS_FORBIDDEN_PATHS:
+ context['exclude_acl'] = exclude
+ exclude_acl.append('-not -path "%(perm_to)s/%(exclude_acl)s"' % context)
+ context['exclude_acl'] = ' \\\n -a '.join(exclude_acl) if exclude_acl else ''
if user.set_perm_perms == 'read-write':
context['perm_perms'] = 'rwx' if user.set_perm_action == 'grant' else '---'
elif user.set_perm_perms == 'read-only':
@@ -91,9 +100,9 @@ class UNIXUserBackend(ServiceController):
# Home access
setfacl -m u:%(user)s:--x '%(perm_home)s'
# Grant perms to existing and future files
- find '%(perm_to)s' %(exclude)s \\
+ find '%(perm_to)s' %(exclude_acl)s \\
-exec setfacl -m u:%(user)s:%(perm_perms)s {} \\;
- find '%(perm_to)s' -type d %(exclude)s \\
+ find '%(perm_to)s' -type d %(exclude_acl)s \\
-exec setfacl -m d:u:%(user)s:%(perm_perms)s {} \\;
# Account group as the owner of new files
chmod g+s '%(perm_to)s'
@@ -102,28 +111,27 @@ class UNIXUserBackend(ServiceController):
if not user.is_main:
self.append(textwrap.dedent("""\
# Grant access to main user
- find '%(perm_to)s' -type d %(exclude)s \\
+ find '%(perm_to)s' -type d %(exclude_acl)s \\
-exec setfacl -m d:u:%(mainuser)s:rwx {} \\;
""") % context
)
elif user.set_perm_action == 'revoke':
self.append(textwrap.dedent("""\
# Revoke permissions
- find '%(perm_to)s' %(exclude)s \\
+ find '%(perm_to)s' %(exclude_acl)s \\
-exec setfacl -m u:%(user)s:%(perm_perms)s {} \\;
""") % context
)
else:
raise NotImplementedError()
- def validate_path(self, user):
+ def validate_path_exists(self, user):
context = {
- 'perm_to': os.path.join(user.set_perm_base_home, user.set_perm_home_extension)
+ 'path': user.path_to_validate,
}
self.append(textwrap.dedent("""\
- if [[ ! -e '%(perm_to)s' ]]; then
- echo "%(perm_to)s path does not exists." >&2
- exit 1
+ if [[ ! -e '%(path)s' ]]; then
+ echo "%(path)s path does not exists." >&2
fi
""") % context
)
@@ -143,6 +151,7 @@ class UNIXUserBackend(ServiceController):
'mainuser': user.username if user.is_main else user.account.username,
'home': user.get_home(),
'base_home': user.get_base_home(),
+ 'mainuser_home': user.main.get_home(),
}
context['deleted_home'] = settings.SYSTEMUSERS_MOVE_ON_DELETE_PATH % context
return replace(context, "'", '"')
diff --git a/orchestra/contrib/systemusers/forms.py b/orchestra/contrib/systemusers/forms.py
index 825cbd74..6340444e 100644
--- a/orchestra/contrib/systemusers/forms.py
+++ b/orchestra/contrib/systemusers/forms.py
@@ -4,12 +4,11 @@ from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext, ugettext_lazy as _
-from orchestra.contrib.orchestration import Operation
from orchestra.forms import UserCreationForm, UserChangeForm
from . import settings
from .models import SystemUser
-from .validators import validate_home
+from .validators import validate_home, validate_path_exists
class SystemUserFormMixin(object):
@@ -66,11 +65,13 @@ class SystemUserFormMixin(object):
def clean(self):
super(SystemUserFormMixin, self).clean()
- home = self.cleaned_data.get('home')
+ cleaned_data = self.cleaned_data
+ home = 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)
- validate_home(self.instance, self.cleaned_data, self.account)
+ username = cleaned_data.get('username', '')
+ cleaned_data['home'] = home.replace(self.MOCK_USERNAME, username)
+ validate_home(self.instance, cleaned_data, self.account)
+ return cleaned_data
class SystemUserCreationForm(SystemUserFormMixin, UserCreationForm):
@@ -111,14 +112,11 @@ class PermissionForm(forms.Form):
def clean(self):
cleaned_data = super(PermissionForm, self).clean()
- user = self.instance
- user.set_perm_action = cleaned_data['set_action']
- user.set_perm_base_home = cleaned_data['base_home']
- user.set_perm_home_extension = cleaned_data['home_extension']
- user.set_perm_perms = cleaned_data['permissions']
- log = Operation.execute_action(user, 'validate_path')[0]
- if 'path does not exists' in log.stderr:
+ path = os.path.join(cleaned_data['base_home'], cleaned_data['home_extension'])
+ try:
+ validate_path_exists(self.instance, path)
+ except ValidationError as err:
raise ValidationError({
- 'home_extension': log.stderr,
+ 'home_extension': err,
})
return cleaned_data
diff --git a/orchestra/contrib/systemusers/models.py b/orchestra/contrib/systemusers/models.py
index 90aaa929..ca3f2df1 100644
--- a/orchestra/contrib/systemusers/models.py
+++ b/orchestra/contrib/systemusers/models.py
@@ -1,3 +1,4 @@
+import fnmatch
import os
from django.contrib.auth.hashers import make_password
@@ -64,6 +65,10 @@ class SystemUser(models.Model):
return self.account.main_systemuser_id == self.pk
return self.account.username == self.username
+ @cached_property
+ def main(self):
+ return self.account.main_systemuser
+
@property
def has_shell(self):
return self.shell not in settings.SYSTEMUSERS_DISABLED_SHELLS
@@ -84,16 +89,20 @@ class SystemUser(models.Model):
if self.home:
self.home = os.path.normpath(self.home)
if self.directory:
- directory_error = None
+ self.directory = os.path.normpath(self.directory)
+ dir_errors = []
if self.has_shell:
- directory_error = _("Directory with shell users can not be specified.")
+ dir_errors.append(_("Directory with shell users can not be specified."))
elif self.account_id and self.is_main:
- directory_error = _("Directory with main system users can not be specified.")
+ dir_errors.append(_("Directory with main system users can not be specified."))
elif self.home == self.get_base_home():
- directory_error = _("Directory on the user's base home is not allowed.")
- if directory_error:
+ dir_errors.append(_("Directory on the user's base home is not allowed."))
+ for pattern in settings.SYSTEMUSERS_FORBIDDEN_PATHS:
+ if fnmatch.fnmatch(self.directory, pattern):
+ dir_errors.append(_("Provided directory is forbidden."))
+ if dir_errors:
raise ValidationError({
- 'directory': directory_error,
+ 'directory': [ValidationError(error) for error in dir_errors]
})
if self.has_shell and self.home and self.home != self.get_base_home():
raise ValidationError({
diff --git a/orchestra/contrib/systemusers/settings.py b/orchestra/contrib/systemusers/settings.py
index 8a42463e..33efff1c 100644
--- a/orchestra/contrib/systemusers/settings.py
+++ b/orchestra/contrib/systemusers/settings.py
@@ -60,8 +60,8 @@ SYSTEMUSERS_MOVE_ON_DELETE_PATH = Setting('SYSTEMUSERS_MOVE_ON_DELETE_PATH',
)
-SYSTEMUSERS_EXLUDE_ACL_PATHS = Setting('SYSTEMUSERS_EXLUDE_ACL_PATHS',
+SYSTEMUSERS_FORBIDDEN_PATHS = Setting('SYSTEMUSERS_FORBIDDEN_PATHS',
(),
- help_text=("Exlude ACL operations on provided globs, relative to user's home.
"
+ help_text=("Exlude ACL operations or home locations on provided globs, relative to user's home.
"
"e.g. ('logs', 'logs/apache*', 'webapps')"),
)
diff --git a/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/set_permission.html b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/set_permission.html
new file mode 100644
index 00000000..8570b74f
--- /dev/null
+++ b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/set_permission.html
@@ -0,0 +1,74 @@
+{% extends "admin/base_site.html" %}
+{% load i18n l10n %}
+{% load url from future %}
+{% load admin_urls static utils %}
+
+{% block extrastyle %}
+{{ block.super }}
+
+
+{% endblock %}
+
+{% block breadcrumbs %}
+