Improved ACL support
This commit is contained in:
parent
b8c815edf2
commit
b5ede03858
2
TODO.md
2
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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -78,7 +78,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
|
|||
Includes <tt>MailmanVirtualDomainBackend</tt>
|
||||
"""
|
||||
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,
|
||||
})
|
||||
|
|
|
@ -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 <tt>resources.disk.allocated</tt>.
|
||||
"""
|
||||
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 = []
|
||||
|
|
|
@ -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 <tt>False</tt> mailbox.name will be used as group."
|
||||
)
|
||||
|
||||
|
||||
MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = Setting('MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
|
||||
'/etc/postfix/virtual_mailboxes'
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -12,13 +12,16 @@ from . import settings
|
|||
class UNIXUserBackend(ServiceController):
|
||||
"""
|
||||
Basic UNIX system user/group support based on <tt>useradd</tt>, <tt>usermod</tt>, <tt>userdel</tt> and <tt>groupdel</tt>.
|
||||
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, "'", '"')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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.<br>"
|
||||
help_text=("Exlude ACL operations or home locations on provided globs, relative to user's home.<br>"
|
||||
"e.g. ('logs', 'logs/apache*', 'webapps')"),
|
||||
)
|
||||
|
|
|
@ -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 }}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static "orchestra/css/hide-inline-id.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||
› <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
|
||||
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||
{% if obj %}
|
||||
› <a href="{% url opts|admin_urlname:'change' obj.pk %}">{{ obj }}</a>
|
||||
› {{ action_name }}
|
||||
{% elif add %}
|
||||
› <a href="../">{% trans "Add" %} {{ opts.verbose_name }}</a>
|
||||
› {{ action_name }}
|
||||
{% else %}
|
||||
› {{ action_name }} multiple objects
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<div style="margin:20px;">
|
||||
Set permissions for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %} system user(s).
|
||||
<ul>{{ display_objects | unordered_list }}</ul>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
<fieldset class="module aligned wide">
|
||||
{{ form.non_field_errors }}
|
||||
<div class="form-row ">
|
||||
{{ form.set_action.errors }}
|
||||
<label for="{{ form.set_action.id_for_label }}">{{ form.set_action.label }}:</label>
|
||||
{{ form.set_action }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||
<p class="help">{{ form.set_action.help_text|safe }}</p>
|
||||
</div>
|
||||
<div class="form-row ">
|
||||
<div class="field-box field-base_home">
|
||||
{{ form.base_home.errors }}
|
||||
<label for="{{ form.base_home.id_for_label }}">{{ form.base_home.label }}:</label>
|
||||
{{ form.base_home }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||
<p class="help">{{ form.base_home.help_text|safe }}</p>
|
||||
</div>
|
||||
<div class="field-box field-user_extension">
|
||||
{{ form.home_extension.errors }}
|
||||
<label for="{{ form.home_extension.id_for_label }}"></label>
|
||||
{{ form.home_extension }}
|
||||
<p class="help">{{ form.home_extension.help_text|safe }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row ">
|
||||
{{ form.permissions.errors }}
|
||||
<label for="{{ form.base_path.id_for_label }}">{{ form.permissions.label }}:</label>
|
||||
{{ form.permissions }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||
<p class="help">{{ form.permissions.help_text|safe }}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div>
|
||||
{% for obj in queryset %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
|
||||
{% endfor %}
|
||||
<input type="hidden" name="action" value="{{ action_value }}" />
|
||||
<input type="hidden" name="post" value="{{ post_value|default:'generic_confirmation' }}" />
|
||||
<input type="submit" value="{{ submit_value|default:_("Save") }}" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -2,6 +2,15 @@ import os
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from orchestra.contrib.orchestration import Operation
|
||||
|
||||
|
||||
def validate_path_exists(user, path, ):
|
||||
user.path_to_validate = path
|
||||
log = Operation.execute_action(user, 'validate_path_exists')[0]
|
||||
if 'path does not exists' in log.stderr:
|
||||
raise ValidationError(log.stderr)
|
||||
|
||||
|
||||
def validate_home(user, data, account):
|
||||
""" validates home based on account and data['shell'] """
|
||||
|
@ -25,3 +34,11 @@ def validate_home(user, data, account):
|
|||
raise ValidationError({
|
||||
'home': _("Not a valid home directory.")
|
||||
})
|
||||
if 'directory' in data and data['directory']:
|
||||
path = os.path.join(data['home'], data['directory'])
|
||||
try:
|
||||
validate_path_exists(user, path)
|
||||
except ValidationError as err:
|
||||
raise ValidationError({
|
||||
'directory': err,
|
||||
})
|
||||
|
|
|
@ -29,14 +29,13 @@ class WebAppServiceMixin(object):
|
|||
if context['under_construction_path']:
|
||||
self.append(textwrap.dedent("""\
|
||||
if [[ $CREATED == 1 && ! $(ls -A %(app_path)s) ]]; then
|
||||
{
|
||||
# Wait for other backends to do their thing or cp under construction
|
||||
# Async wait for other backends to do their thing or cp under construction
|
||||
nohup bash -c '
|
||||
sleep 10
|
||||
if [[ ! $(ls -A %(app_path)s) ]]; then
|
||||
cp -r %(under_construction_path)s %(app_path)s
|
||||
chown -R %(user)s:%(group)s %(app_path)s
|
||||
fi
|
||||
} &
|
||||
fi' &> /dev/null &
|
||||
fi""") % context
|
||||
)
|
||||
|
||||
|
|
|
@ -127,6 +127,9 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
|
|||
mv /dev/shm/restart.apache2 /dev/shm/restart.apache2.locked
|
||||
}
|
||||
state="$(grep -v "$backend" /dev/shm/restart.apache2.locked)" || is_last=1
|
||||
[[ $is_last -eq 0 ]] && {
|
||||
echo "$state" | grep -v ' RESTART$' || is_last=1
|
||||
}
|
||||
if [[ $is_last -eq 1 ]]; then
|
||||
if [[ $UPDATED_APACHE -eq 1 || "$state" =~ .*RESTART$ ]]; then
|
||||
service apache2 status && service apache2 reload || service apache2 start
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import textwrap
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -41,11 +42,19 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
|
|||
if (count(glob("%(app_path)s/*")) > 1) {
|
||||
die("App directory not empty.");
|
||||
}
|
||||
exc('mkdir -p %(app_path)s');
|
||||
exc('rm -f %(app_path)s/index.html');
|
||||
exc('wget http://wordpress.org/latest.tar.gz -O - --no-check-certificate | tar -xzvf - -C %(app_path)s --strip-components=1');
|
||||
exc('mkdir %(app_path)s/wp-content/uploads');
|
||||
exc('chmod 750 %(app_path)s/wp-content/uploads');
|
||||
shell_exec("mkdir -p %(app_path)s
|
||||
rm -f %(app_path)s/index.html
|
||||
filename=\\$(wget https://wordpress.org/latest.tar.gz --server-response --spider --no-check-certificate 2>&1 | grep filename | cut -d'=' -f2)
|
||||
mkdir -p %(cms_cache_dir)s
|
||||
if [ \\$(basename \\$(readlink %(cms_cache_dir)s/wordpress) 2> /dev/null ) != \\$filename ]; then
|
||||
wget https://wordpress.org/latest.tar.gz -O - --no-check-certificate | tee %(cms_cache_dir)s/\\$filename | tar -xzvf - -C %(app_path)s --strip-components=1
|
||||
rm -f %(cms_cache_dir)s/wordpress
|
||||
ln -s %(cms_cache_dir)s/\\$filename %(cms_cache_dir)s/wordpress
|
||||
else
|
||||
tar -xzvf %(cms_cache_dir)s/wordpress -C %(app_path)s --strip-components=1
|
||||
fi
|
||||
mkdir %(app_path)s/wp-content/uploads
|
||||
chmod 750 %(app_path)s/wp-content/uploads");
|
||||
|
||||
$config_file = file('%(app_path)s/' . 'wp-config-sample.php');
|
||||
$secret_keys = file_get_contents('https://api.wordpress.org/secret-key/1.1/salt/');
|
||||
|
@ -124,5 +133,6 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
|
|||
'db_host': settings.WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST,
|
||||
'email': webapp.account.email,
|
||||
'title': "%s blog's" % webapp.account.get_full_name(),
|
||||
'cms_cache_dir': os.path.normpath(settings.WEBAPPS_CMS_CACHE_DIR)
|
||||
})
|
||||
return replace(context, '"', "'")
|
||||
|
|
|
@ -261,3 +261,10 @@ WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = Setting('WEBAPPS_DEFAULT_MYSQL_DATABASE_HO
|
|||
WEBAPPS_MOVE_ON_DELETE_PATH = Setting('WEBAPPS_MOVE_ON_DELETE_PATH',
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
|
||||
WEBAPPS_CMS_CACHE_DIR = Setting('WEBAPPS_CMS_CACHE_DIR',
|
||||
'/tmp/orchestra_cms_cache',
|
||||
help_text="Server-side cache directori for CMS tarballs.",
|
||||
)
|
||||
|
|
|
@ -148,6 +148,9 @@ class Apache2Backend(ServiceController):
|
|||
mv /dev/shm/restart.apache2 /dev/shm/restart.apache2.locked
|
||||
}
|
||||
state="$(grep -v "$backend" /dev/shm/restart.apache2.locked)" || is_last=1
|
||||
[[ $is_last -eq 0 ]] && {
|
||||
echo "$state" | grep -v ' RESTART$' || is_last=1
|
||||
}
|
||||
if [[ $is_last -eq 1 ]]; then
|
||||
if [[ $UPDATED_APACHE -eq 1 || "$state" =~ .*RESTART$ ]]; then
|
||||
service apache2 status && service apache2 reload || service apache2 start
|
||||
|
|
Loading…
Reference in a new issue