Improved ACL support

This commit is contained in:
Marc Aymerich 2015-05-12 12:38:40 +00:00
parent b8c815edf2
commit b5ede03858
23 changed files with 248 additions and 87 deletions

View File

@ -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

View File

@ -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):

View File

@ -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 = {

View File

@ -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,

View File

@ -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))

View File

@ -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,
})

View File

@ -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 = []

View File

@ -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'
)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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, "'", '"')

View File

@ -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

View File

@ -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({

View File

@ -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')"),
)

View File

@ -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>
&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
{% if obj %}
&rsaquo; <a href="{% url opts|admin_urlname:'change' obj.pk %}">{{ obj }}</a>
&rsaquo; {{ action_name }}
{% elif add %}
&rsaquo; <a href="../">{% trans "Add" %} {{ opts.verbose_name }}</a>
&rsaquo; {{ action_name }}
{% else %}
&rsaquo; {{ 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" %}&nbsp;{% 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" %}&nbsp;{% 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" %}&nbsp;{% 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 %}

View File

@ -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,
})

View File

@ -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
)

View File

@ -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

View File

@ -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, '"', "'")

View File

@ -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.",
)

View File

@ -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