diff --git a/TODO.md b/TODO.md
index f0854829..12634b9c 100644
--- a/TODO.md
+++ b/TODO.md
@@ -384,3 +384,6 @@ Case
# Deprecate orchestra start/stop/restart services management commands?
# Enable/disable ignore period orders list filter
+
+
+# Modsecurity rules template by cms (wordpress, joomla, dokuwiki (973337 973338 973347 958057), ...
diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py
index 21486fc7..e9721ef3 100644
--- a/orchestra/contrib/bills/actions.py
+++ b/orchestra/contrib/bills/actions.py
@@ -37,13 +37,13 @@ view_bill.url_name = 'view'
@transaction.atomic
def close_bills(modeladmin, request, queryset, action='close_bills'):
- queryset = queryset.filter(is_open=True)
- if not queryset:
- messages.warning(request, _("Selected bills should be in open state"))
- return
+ # Validate bills
for bill in queryset:
if not validate_contact(request, bill):
- return
+ return False
+ if not bill.is_open:
+ messages.warning(request, _("Selected bills should be in open state"))
+ return False
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
formset = SelectSourceFormSet(queryset=queryset)
if request.POST.get('post') == 'generic_confirmation':
@@ -143,9 +143,13 @@ download_bills.url_name = 'download'
def close_send_download_bills(modeladmin, request, queryset):
response = close_bills(modeladmin, request, queryset, action='close_send_download_bills')
+ if response is False:
+ # Not a valid contact or closed bill
+ return
if request.POST.get('post') == 'generic_confirmation':
response = send_bills_action(modeladmin, request, queryset)
if response is False:
+ # Not a valid contact
return
return download_bills(modeladmin, request, queryset)
return response
diff --git a/orchestra/contrib/mailer/admin.py b/orchestra/contrib/mailer/admin.py
index ca6c948d..e74ecf76 100644
--- a/orchestra/contrib/mailer/admin.py
+++ b/orchestra/contrib/mailer/admin.py
@@ -80,15 +80,20 @@ class MessageAdmin(admin.ModelAdmin):
part = email.message_from_string(instance.content)
payload = part.get_payload()
if isinstance(payload, list):
- for part in payload:
- payload = part.get_payload()
- if part.get_content_type() == 'text/html':
- payload = '
%s
' % payload
- # prioritize HTML
- break
+ for cpart in payload:
+ cpayload = cpart.get_payload()
+ if cpart.get_content_type().startswith('text/'):
+ part = cpart
+ payload = cpayload
+ if cpart.get_content_type() == 'text/html':
+ payload = '%s
' % payload
+ # prioritize HTML
+ break
if part.get('Content-Transfer-Encoding') == 'base64':
payload = base64.b64decode(payload)
- payload = payload.decode(part.get_charsets()[0])
+ charset = part.get_charsets()[0]
+ if charset:
+ payload = payload.decode(charset)
if part.get_content_type() == 'text/plain':
payload = payload.replace('\n', '
').replace(' ', ' ')
return payload
diff --git a/orchestra/contrib/orders/admin.py b/orchestra/contrib/orders/admin.py
index baa0dfe8..89b8d001 100644
--- a/orchestra/contrib/orders/admin.py
+++ b/orchestra/contrib/orders/admin.py
@@ -39,11 +39,11 @@ class MetricStorageInline(admin.TabularInline):
if change_view:
qs = qs.order_by('-id')
try:
- tenth_id = qs.values_list('id', flat=True)[9]
+ tenth_id = qs.filter(order_id=self.parent_object.pk).values_list('id', flat=True)[9]
except IndexError:
pass
else:
- return qs.filter(pk__lte=tenth_id)
+ return qs.filter(pk__gte=tenth_id)
return qs
diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py
index db3a2ff8..cb2614b6 100644
--- a/orchestra/contrib/orders/models.py
+++ b/orchestra/contrib/orders/models.py
@@ -181,7 +181,7 @@ class Order(models.Model):
return import_class(settings.ORDERS_BILLING_BACKEND)()
def clean(self):
- if self.billed_on < self.registered_on:
+ if self.billed_on and self.billed_on < self.registered_on:
raise ValidationError(_("Billed date can not be earlier than registered on."))
if self.billed_until and not self.billed_on:
raise ValidationError(_("Billed on is missing while billed until is being provided."))
diff --git a/orchestra/contrib/resources/helpers.py b/orchestra/contrib/resources/helpers.py
index da7cd447..d0d49873 100644
--- a/orchestra/contrib/resources/helpers.py
+++ b/orchestra/contrib/resources/helpers.py
@@ -24,7 +24,8 @@ def get_history_data(queryset):
'objects': [],
}
resources[resource] = (options, aggregation)
-
+ if aggregation.aggregated_history:
+ needs_aggregation = True
monitors = []
scale = options['scale']
all_dates = options['dates']
@@ -32,7 +33,6 @@ def get_history_data(queryset):
datasets = {}
for content_object, datas in aggregation.aggregate_history(dataset):
if aggregation.aggregated_history:
- needs_aggregation = True
serie = {}
for data in datas:
value = round(float(data.value)/scale, 3) if data.value is not None else None
@@ -54,9 +54,9 @@ def get_history_data(queryset):
})
options['objects'].append({
'object_name': rdata.content_object_repr,
- 'current': round(float(rdata.used), 3),
+ 'current': round(float(rdata.used or 0), 3),
'allocated': float(rdata.allocated) if rdata.allocated is not None else None,
- 'updated_at': rdata.updated_at.isoformat(),
+ 'updated_at': rdata.updated_at.isoformat() if rdata.updated_at else None,
'monitors': monitors,
})
if needs_aggregation:
diff --git a/orchestra/contrib/saas/services/phplist.py b/orchestra/contrib/saas/services/phplist.py
index a97721e0..0c15a459 100644
--- a/orchestra/contrib/saas/services/phplist.py
+++ b/orchestra/contrib/saas/services/phplist.py
@@ -18,11 +18,11 @@ class PHPListForm(SaaSPasswordForm):
admin_username = forms.CharField(label=_("Admin username"), required=False,
widget=SpanWidget(display='admin'))
database = forms.CharField(label=_("Database"), required=False,
- help_text=_("Database used for this instance."),
+ help_text=_("Database dedicated to this phpList instance."),
widget=SpanWidget(display=settings.SAAS_PHPLIST_DB_NAME.replace(
'%(', '<').replace(')s', '>')))
mailbox = forms.CharField(label=_("Bounces mailbox"), required=False,
- help_text=_("Mailbox used for reciving bounces."),
+ help_text=_("Dedicated mailbox used for reciving bounces."),
widget=SpanWidget(display=settings.SAAS_PHPLIST_BOUNCES_MAILBOX_NAME.replace(
'%(', '<').replace(')s', '>')))
diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py
index d36c881e..9be6de23 100644
--- a/orchestra/contrib/services/handlers.py
+++ b/orchestra/contrib/services/handlers.py
@@ -374,7 +374,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
priced[order][0] += price
priced[order][1] += cprice
else:
- priced[order] = (price, cprice)
+ priced[order] = [price, cprice]
lines = []
for order, prices in priced.items():
if hasattr(order, 'new_billed_until'):
diff --git a/orchestra/contrib/systemusers/settings.py b/orchestra/contrib/systemusers/settings.py
index 33efff1c..f0138d23 100644
--- a/orchestra/contrib/systemusers/settings.py
+++ b/orchestra/contrib/systemusers/settings.py
@@ -11,6 +11,7 @@ SYSTEMUSERS_SHELLS = Setting('SYSTEMUSERS_SHELLS',
(
('/dev/null', _("No shell, FTP only")),
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
+ ('/usr/bin/git-shell', _("No shell, GIT only")),
('/bin/bash', "/bin/bash"),
('/bin/sh', "/bin/sh"),
),
@@ -28,6 +29,7 @@ SYSTEMUSERS_DISABLED_SHELLS = Setting('SYSTEMUSERS_DISABLED_SHELLS',
default=(
'/dev/null',
'/bin/rssh',
+ '/usr/bin/git-shell',
),
)
diff --git a/orchestra/contrib/webapps/options.py b/orchestra/contrib/webapps/options.py
index f4b463be..6ff1976a 100644
--- a/orchestra/contrib/webapps/options.py
+++ b/orchestra/contrib/webapps/options.py
@@ -102,6 +102,11 @@ class PHPEnableFunctions(PHPAppOption):
])
regex = r'^[\w\.,-]+$'
comma_separated = True
+
+ def validate(self):
+ # Clean value removing spaces
+ self.instance.value = self.instance.value.replace(' ', '')
+ super(PHPEnableFunctions, self).validate()
class PHPAllowURLInclude(PHPAppOption):
diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py
index e019a203..301013fc 100644
--- a/orchestra/contrib/websites/backends/apache.py
+++ b/orchestra/contrib/websites/backends/apache.py
@@ -38,9 +38,7 @@ class Apache2Backend(ServiceController):
'WEBSITES_SAAS_DIRECTIVES',
))
- def render_virtual_host(self, site, context, ssl=False):
- context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
- context['vhost_set_fcgid'] = False
+ def get_extra_conf(self, site, context, ssl=False):
extra_conf = self.get_content_directives(site, context)
directives = site.get_directives()
if ssl:
@@ -54,7 +52,12 @@ class Apache2Backend(ServiceController):
extra_conf.append((location, directive % settings_context))
# Order extra conf directives based on directives (longer first)
extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True)
- context['extra_conf'] = '\n'.join([conf for location, conf in extra_conf])
+ return '\n'.join([conf for location, conf in extra_conf])
+
+ def render_virtual_host(self, site, context, ssl=False):
+ context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
+ context['vhost_set_fcgid'] = False
+ context['extra_conf'] = self.get_extra_conf(site, context, ssl)
return Template(textwrap.dedent("""\
IncludeOptional /etc/apache2/site[s]-override/{{ site_unique_name }}.con[f]
diff --git a/orchestra/contrib/websites/directives.py b/orchestra/contrib/websites/directives.py
index 65922d91..a017f251 100644
--- a/orchestra/contrib/websites/directives.py
+++ b/orchestra/contrib/websites/directives.py
@@ -169,6 +169,7 @@ class SecEngine(SecRuleRemove):
verbose_name = _("SecRuleEngine Off")
help_text = _("URL path with disabled modsecurity engine.")
regex = r'^/[^ ]*$'
+ unique_location = False
class WordPressSaaS(SiteDirective):
diff --git a/orchestra/contrib/websites/settings.py b/orchestra/contrib/websites/settings.py
index 91927e0c..022d36fa 100644
--- a/orchestra/contrib/websites/settings.py
+++ b/orchestra/contrib/websites/settings.py
@@ -109,8 +109,8 @@ WEBSITES_TRAFFIC_IGNORE_HOSTS = Setting('WEBSITES_TRAFFIC_IGNORE_HOSTS',
WEBSITES_SAAS_DIRECTIVES = Setting('WEBSITES_SAAS_DIRECTIVES',
{
'wordpress-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock', '/home/httpd/wordpress-mu/'),
- 'drupal-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock','/home/httpd/drupal-mu/'),
- 'dokuwiki-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock','/home/httpd/moodle-mu/'),
+ 'drupal-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock', '/home/httpd/drupal-mu/'),
+ 'dokuwiki-saas': ('fpm', '/var/run/fpm/pangea-5.4-fpm.sock', '/home/httpd/moodle-mu/'),
},
)