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/'), }, )