diff --git a/TODO.md b/TODO.md
index 6dbe10f0..70324e94 100644
--- a/TODO.md
+++ b/TODO.md
@@ -195,23 +195,20 @@ Php binaries should have this format: /usr/bin/php5.2-cgi
* Orchestra global search box on the header, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields
-* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading
+* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading machinary
* contact.alternative_phone on a phone.tooltip, email:to
* better validate options and directives (url locations, filesystem paths, etc..)
-* filter php deprecated options out based on version
* make sure that you understand the risks
* full support for deactivation of services/accounts
- * Display admin.is_active (disabled account/order by)
-
+ * Display admin.is_active (disabled account special icon and order by support)
* lock resource monitoring
-
* -EXecCGI in common CMS upload locations /wp-upload/upload/uploads
* cgi user / pervent shell access
@@ -219,14 +216,6 @@ Php binaries should have this format: /usr/bin/php5.2-cgi
* disable anonymized list options (mailman)
-* webapps directory protection and disable excecgi
-
-* php-fpm disable execCGI
-
-* SuexecUserGroup needs to be per app othewise wrapper/fpm user can't be correct
-
-* wprdess-mu saas app that create a Website object????
-
* tags = GenericRelation(TaggedItem, related_query_name='bookmarks')
* make home for all systemusers (/home/username) and fix monitors
@@ -243,23 +232,17 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
* normurlpath '' return '/'
-* rename webapps.type to something more generic
-
* initial configuration of multisite sas apps with password stored in DATA
* webapps installation complete, passowrd protected
* saas.initial_password autogenerated (ok because its random and not user provided) vs saas.password /change_Form provided + send email with initial_password
-* disable saas apps
-
* more robust backend error handling, continue executing but exit code > 0 if failure, replace exit_code=0; do_sometging || exit_code=1
* saas require unique emails? connect to backend server to find out because they change
* automaitcally set passwords and email users?
-* website directives uniquenes validation on serializers
-
-* gitlab store id, username changes
+* website directives uniquenes validation on serializers
diff --git a/orchestra/apps/saas/admin.py b/orchestra/apps/saas/admin.py
index ff09b933..d8670899 100644
--- a/orchestra/apps/saas/admin.py
+++ b/orchestra/apps/saas/admin.py
@@ -11,8 +11,8 @@ from .services import SoftwareService
class SaaSAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
- list_display = ('name', 'service', 'display_site_domain', 'account_link')
- list_filter = ('service',)
+ list_display = ('name', 'service', 'display_site_domain', 'account_link', 'is_active')
+ list_filter = ('service', 'is_active')
change_readonly_fields = ('service',)
plugin = SoftwareService
plugin_field = 'service'
diff --git a/orchestra/apps/saas/backends/gitlab.py b/orchestra/apps/saas/backends/gitlab.py
index ee7232b7..db101644 100644
--- a/orchestra/apps/saas/backends/gitlab.py
+++ b/orchestra/apps/saas/backends/gitlab.py
@@ -22,9 +22,10 @@ class GitLabSaaSBackend(ServiceController):
user_id = saas.data['user_id']
return self.get_base_url() + '/users/%i' % user_id
- def validate_response(self, response, status_codes):
+ def validate_response(self, response, *status_codes):
if response.status_code not in status_codes:
raise RuntimeError("[%i] %s" % (response.status_code, response.content))
+ return json.loads(response.content)
def authenticate(self):
login_url = self.get_base_url() + '/session'
@@ -33,8 +34,8 @@ class GitLabSaaSBackend(ServiceController):
'password': settings.SAAS_GITLAB_ROOT_PASSWORD,
}
response = requests.post(login_url, data=data)
- self.validate_response(response, [201])
- token = json.loads(response.content)['private_token']
+ session = self.validate_response(response, 201)
+ token = session['private_token']
self.headers = {
'PRIVATE-TOKEN': token,
}
@@ -49,9 +50,7 @@ class GitLabSaaSBackend(ServiceController):
'name': saas.account.get_full_name(),
}
response = requests.post(user_url, data=data, headers=self.headers)
- self.validate_response(response, [201])
- print response.content
- user = json.loads(response.content)
+ user = self.validate_response(response, 201)
saas.data['user_id'] = user['id']
# Using queryset update to avoid triggering backends with the post_save signal
type(saas).objects.filter(pk=saas.pk).update(data=saas.data)
@@ -60,19 +59,32 @@ class GitLabSaaSBackend(ServiceController):
def change_password(self, saas, server):
self.authenticate()
user_url = self.get_user_url(saas)
- data = {
- 'password': saas.password,
- }
- response = requests.patch(user_url, data=data, headers=self.headers)
- self.validate_response(response, [200])
- print json.dumps(json.loads(response.content), indent=4)
+ response = requests.get(user_url, headers=self.headers)
+ user = self.validate_response(response, 200)
+ user = json.loads(response.content)
+ user['password'] = saas.password
+ response = requests.put(user_url, data=user, headers=self.headers)
+ user = self.validate_response(response, 200)
+ print json.dumps(user, indent=4)
+
+ def set_state(self, saas, server):
+ # TODO http://feedback.gitlab.com/forums/176466-general/suggestions/4098632-add-administrative-api-call-to-block-users
+ return
+ self.authenticate()
+ user_url = self.get_user_url(saas)
+ response = requests.get(user_url, headers=self.headers)
+ user = self.validate_response(response, 200)
+ user['state'] = 'active' if saas.active else 'blocked',
+ response = requests.patch(user_url, data=user, headers=self.headers)
+ user = self.validate_response(response, 200)
+ print json.dumps(user, indent=4)
def delete_user(self, saas, server):
self.authenticate()
user_url = self.get_user_url(saas)
response = requests.delete(user_url, headers=self.headers)
- self.validate_response(response, [200, 404])
- print json.dumps(json.loads(response.content), indent=4)
+ user = self.validate_response(response, 200, 404)
+ print json.dumps(user, indent=4)
def _validate_creation(self, saas, server):
""" checks if a saas object is valid for creation on the server side """
@@ -96,6 +108,7 @@ class GitLabSaaSBackend(ServiceController):
self.append(self.change_password, saas)
else:
self.append(self.create_user, saas)
+ self.append(self.set_state, saas)
def delete(self, saas):
self.append(self.delete_user, saas)
diff --git a/orchestra/apps/saas/models.py b/orchestra/apps/saas/models.py
index 6b33b767..d58ee537 100644
--- a/orchestra/apps/saas/models.py
+++ b/orchestra/apps/saas/models.py
@@ -19,6 +19,8 @@ class SaaS(models.Model):
validators=[validators.validate_username])
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='saas')
+ is_active = models.BooleanField(_("active"), default=True,
+ help_text=_("Designates whether this service should be treated as active. "))
data = JSONField(_("data"), default={},
help_text=_("Extra information dependent of each service."))
@@ -41,6 +43,10 @@ class SaaS(models.Model):
""" Per request lived service_instance """
return self.service_class(self)
+ @cached_property
+ def active(self):
+ return self.is_active and self.account.is_active
+
def clean(self):
self.data = self.service_instance.clean_data()
diff --git a/orchestra/apps/webapps/options.py b/orchestra/apps/webapps/options.py
index 9a0ab84c..84b6f0ea 100644
--- a/orchestra/apps/webapps/options.py
+++ b/orchestra/apps/webapps/options.py
@@ -48,6 +48,20 @@ class AppOption(Plugin):
})
+class PHPAppOption(AppOption):
+ deprecated = None
+ group = AppOption.PHP
+
+ def validate(self):
+ super(PHPAppOption, self).validate()
+ if self.deprecated:
+ php_version = self.instance.webapp.type_instance.get_php_version()
+ if php_version and php_version > self.deprecated:
+ raise ValidationError(
+ _("This option is deprecated since PHP version %s.") % str(self.deprecated)
+ )
+
+
class PublicRoot(AppOption):
name = 'public-root'
verbose_name = _("Public root")
@@ -77,193 +91,171 @@ class Processes(AppOption):
group = AppOption.PROCESS
-class PHPEnabledFunctions(AppOption):
+class PHPEnabledFunctions(PHPAppOption):
name = 'enabled_functions'
verbose_name = _("Enabled functions")
help_text = ' '.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS)
regex = r'^[\w\.,-]+$'
- group = AppOption.PHP
-class PHPAllowURLInclude(AppOption):
+class PHPAllowURLInclude(PHPAppOption):
name = 'allow_url_include'
verbose_name = _("Allow URL include")
help_text = _("Allows the use of URL-aware fopen wrappers with include, include_once, require, "
"require_once (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPAllowURLFopen(AppOption):
+class PHPAllowURLFopen(PHPAppOption):
name = 'allow_url_fopen'
verbose_name = _("Allow URL fopen")
help_text = _("Enables the URL-aware fopen wrappers that enable accessing URL object like files (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPAutoAppendFile(AppOption):
+class PHPAutoAppendFile(PHPAppOption):
name = 'auto_append_file'
verbose_name = _("Auto append file")
help_text = _("Specifies the name of a file that is automatically parsed after the main file.")
regex = r'^[\w\.,-/]+$'
- group = AppOption.PHP
-class PHPAutoPrependFile(AppOption):
+class PHPAutoPrependFile(PHPAppOption):
name = 'auto_prepend_file'
verbose_name = _("Auto prepend file")
help_text = _("Specifies the name of a file that is automatically parsed before the main file.")
regex = r'^[\w\.,-/]+$'
- group = AppOption.PHP
-class PHPDateTimeZone(AppOption):
+class PHPDateTimeZone(PHPAppOption):
name = 'date.timezone'
verbose_name = _("date.timezone")
help_text = _("Sets the default timezone used by all date/time functions (Timezone string 'Europe/London').")
regex = r'^\w+/\w+$'
- group = AppOption.PHP
-class PHPDefaultSocketTimeout(AppOption):
+class PHPDefaultSocketTimeout(PHPAppOption):
name = 'default_socket_timeout'
verbose_name = _("Default socket timeout")
help_text = _("Number between 0 and 999.")
regex = r'^[0-9]{1,3}$'
- group = AppOption.PHP
-class PHPDisplayErrors(AppOption):
+class PHPDisplayErrors(PHPAppOption):
name = 'display_errors'
verbose_name = _("Display errors")
help_text = _("Determines whether errors should be printed to the screen as part of the output or "
"if they should be hidden from the user (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPExtension(AppOption):
+class PHPExtension(PHPAppOption):
name = 'extension'
verbose_name = _("Extension")
regex = r'^[^ ]+$'
- group = AppOption.PHP
-class PHPMagicQuotesGPC(AppOption):
+class PHPMagicQuotesGPC(PHPAppOption):
name = 'magic_quotes_gpc'
verbose_name = _("Magic quotes GPC")
help_text = _("Sets the magic_quotes state for GPC (Get/Post/Cookie) operations (On or Off) "
"DEPRECATED as of PHP 5.3.0.")
regex = r'^(On|Off|on|off)$'
- deprecated=5.3
- group = AppOption.PHP
+ deprecated = 5.3
-class PHPMagicQuotesRuntime(AppOption):
+class PHPMagicQuotesRuntime(PHPAppOption):
name = 'magic_quotes_runtime'
verbose_name = _("Magic quotes runtime")
help_text = _("Functions that return data from any sort of external source will have quotes escaped "
"with a backslash (On or Off) DEPRECATED as of PHP 5.3.0.")
regex = r'^(On|Off|on|off)$'
- deprecated=5.3
- group = AppOption.PHP
+ deprecated = 5.3
-class PHPMaginQuotesSybase(AppOption):
+class PHPMaginQuotesSybase(PHPAppOption):
name = 'magic_quotes_sybase'
verbose_name = _("Magic quotes sybase")
help_text = _("Single-quote is escaped with a single-quote instead of a backslash (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPMaxExecutonTime(AppOption):
+class PHPMaxExecutonTime(PHPAppOption):
name = 'max_execution_time'
verbose_name = _("Max execution time")
help_text = _("Maximum time in seconds a script is allowed to run before it is terminated by "
"the parser (Integer between 0 and 999).")
regex = r'^[0-9]{1,3}$'
- group = AppOption.PHP
-class PHPMaxInputTime(AppOption):
+class PHPMaxInputTime(PHPAppOption):
name = 'max_input_time'
verbose_name = _("Max input time")
help_text = _("Maximum time in seconds a script is allowed to parse input data, like POST and GET "
"(Integer between 0 and 999).")
regex = r'^[0-9]{1,3}$'
- group = AppOption.PHP
-class PHPMaxInputVars(AppOption):
+class PHPMaxInputVars(PHPAppOption):
name = 'max_input_vars'
verbose_name = _("Max input vars")
help_text = _("How many input variables may be accepted (limit is applied to $_GET, $_POST "
"and $_COOKIE superglobal separately) (Integer between 0 and 9999).")
regex = r'^[0-9]{1,4}$'
- group = AppOption.PHP
-class PHPMemoryLimit(AppOption):
+class PHPMemoryLimit(PHPAppOption):
name = 'memory_limit'
verbose_name = _("Memory limit")
help_text = _("This sets the maximum amount of memory in bytes that a script is allowed to allocate "
"(Value between 0M and 999M).")
regex = r'^[0-9]{1,3}M$'
- group = AppOption.PHP
-class PHPMySQLConnectTimeout(AppOption):
+class PHPMySQLConnectTimeout(PHPAppOption):
name = 'mysql.connect_timeout'
verbose_name = _("Mysql connect timeout")
help_text = _("Number between 0 and 999.")
regex = r'^([0-9]){1,3}$'
- group = AppOption.PHP
-class PHPOutputBuffering(AppOption):
+class PHPOutputBuffering(PHPAppOption):
name = 'output_buffering'
verbose_name = _("Output buffering")
help_text = _("Turn on output buffering (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPRegisterGlobals(AppOption):
+class PHPRegisterGlobals(PHPAppOption):
name = 'register_globals'
verbose_name = _("Register globals")
help_text = _("Whether or not to register the EGPCS (Environment, GET, POST, Cookie, Server) "
"variables as global variables (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPPostMaxSize(AppOption):
+class PHPPostMaxSize(PHPAppOption):
name = 'post_max_size'
verbose_name = _("Post max size")
help_text = _("Sets max size of post data allowed (Value between 0M and 999M).")
regex = r'^[0-9]{1,3}M$'
- group = AppOption.PHP
-class PHPSendmailPath(AppOption):
+class PHPSendmailPath(PHPAppOption):
name = 'sendmail_path'
verbose_name = _("sendmail_path")
help_text = _("Where the sendmail program can be found.")
regex = r'^[^ ]+$'
- group = AppOption.PHP
-class PHPSessionBugCompatWarn(AppOption):
+class PHPSessionBugCompatWarn(PHPAppOption):
name = 'session.bug_compat_warn'
verbose_name = _("session.bug_compat_warn")
help_text = _("Enables an PHP bug on session initialization for legacy behaviour (On or Off).")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPSessionAutoStart(AppOption):
+class PHPSessionAutoStart(PHPAppOption):
name = 'session.auto_start'
verbose_name = _("session.auto_start")
help_text = _("Specifies whether the session module starts a session automatically on request "
@@ -272,72 +264,63 @@ class PHPSessionAutoStart(AppOption):
group = AppOption.PHP
-class PHPSafeMode(AppOption):
+class PHPSafeMode(PHPAppOption):
name = 'safe_mode'
verbose_name = _("Safe mode")
help_text = _("Whether to enable PHP's safe mode (On or Off) DEPRECATED as of PHP 5.3.0")
regex = r'^(On|Off|on|off)$'
deprecated=5.3
- group = AppOption.PHP
-class PHPSuhosinPostMaxVars(AppOption):
+class PHPSuhosinPostMaxVars(PHPAppOption):
name = 'suhosin.post.max_vars'
verbose_name = _("Suhosin POST max vars")
help_text = _("Number between 0 and 9999.")
regex = r'^[0-9]{1,4}$'
- group = AppOption.PHP
-class PHPSuhosinGetMaxVars(AppOption):
+class PHPSuhosinGetMaxVars(PHPAppOption):
name = 'suhosin.get.max_vars'
verbose_name = _("Suhosin GET max vars")
help_text = _("Number between 0 and 9999.")
regex = r'^[0-9]{1,4}$'
- group = AppOption.PHP
-class PHPSuhosinRequestMaxVars(AppOption):
+class PHPSuhosinRequestMaxVars(PHPAppOption):
name = 'suhosin.request.max_vars'
verbose_name = _("Suhosin request max vars")
help_text = _("Number between 0 and 9999.")
regex = r'^[0-9]{1,4}$'
- group = AppOption.PHP
-class PHPSuhosinSessionEncrypt(AppOption):
+class PHPSuhosinSessionEncrypt(PHPAppOption):
name = 'suhosin.session.encrypt'
verbose_name = _("suhosin.session.encrypt")
help_text = _("On or Off")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPSuhosinSimulation(AppOption):
+class PHPSuhosinSimulation(PHPAppOption):
name = 'suhosin.simulation'
verbose_name = _("Suhosin simulation")
help_text = _("On or Off")
regex = r'^(On|Off|on|off)$'
- group = AppOption.PHP
-class PHPSuhosinExecutorIncludeWhitelist(AppOption):
+class PHPSuhosinExecutorIncludeWhitelist(PHPAppOption):
name = 'suhosin.executor.include.whitelist'
verbose_name = _("suhosin.executor.include.whitelist")
regex = r'.*$'
- group = AppOption.PHP
-class PHPUploadMaxFileSize(AppOption):
+class PHPUploadMaxFileSize(PHPAppOption):
name = 'upload_max_filesize'
verbose_name = _("upload_max_filesize")
help_text = _("Value between 0M and 999M.")
regex = r'^[0-9]{1,3}M$'
- group = AppOption.PHP
-class PHPZendExtension(AppOption):
+class PHPZendExtension(PHPAppOption):
name = 'zend_extension'
verbose_name = _("Zend extension")
regex = r'^[^ ]+$'
- group = AppOption.PHP
diff --git a/orchestra/apps/webapps/types/__init__.py b/orchestra/apps/webapps/types/__init__.py
index 3e179b7e..3b210320 100644
--- a/orchestra/apps/webapps/types/__init__.py
+++ b/orchestra/apps/webapps/types/__init__.py
@@ -36,14 +36,6 @@ class AppType(plugins.Plugin):
'name': _("A WordPress blog with this name already exists."),
})
- @classmethod
- @cached
- def get_php_options(cls):
- # TODO validate php options once a php version has been selected (deprecated directives)
- php_version = getattr(cls, 'php_version', 1)
- php_options = AppOption.get_option_groups()[AppOption.PHP]
- return [op for op in php_options if getattr(cls, 'deprecated', 99) > php_version]
-
@classmethod
@cached
def get_options(cls):
@@ -52,8 +44,6 @@ class AppType(plugins.Plugin):
options = []
for group in cls.option_groups:
group_options = groups[group]
- if group == AppOption.PHP:
- group_options = cls.get_php_options()
if group is None:
options.insert(0, (group, group_options))
else:
diff --git a/orchestra/apps/webapps/types/php.py b/orchestra/apps/webapps/types/php.py
index aed56a80..97760da9 100644
--- a/orchestra/apps/webapps/types/php.py
+++ b/orchestra/apps/webapps/types/php.py
@@ -7,8 +7,10 @@ from rest_framework import serializers
from orchestra.forms import widgets
from orchestra.plugins.forms import PluginDataForm
+from orchestra.utils.functional import cached
from .. import settings
+from ..options import AppOption
from . import AppType
@@ -57,6 +59,12 @@ class PHPApp(AppType):
def get_detail(self):
return self.instance.data.get('php_version', '')
+ @cached
+ def get_php_options(self):
+ php_version = self.get_php_version()
+ php_options = AppOption.get_option_groups()[AppOption.PHP]
+ return [op for op in php_options if getattr(self, 'deprecated', 999) > php_version]
+
def get_php_init_vars(self, merge=False):
"""
process php options for inclusion on php.ini
@@ -72,7 +80,7 @@ class PHPApp(AppType):
for webapp in webapps:
if webapp.type_instance.get_php_version == php_version:
options += list(webapp.options.all())
- php_options = [option.name for option in type(self).get_php_options()]
+ php_options = [option.name for option in self.get_php_options()]
enabled_functions = set()
for opt in options:
if opt.name in php_options:
diff --git a/orchestra/apps/websites/directives.py b/orchestra/apps/websites/directives.py
index da6273e6..38fd474a 100644
--- a/orchestra/apps/websites/directives.py
+++ b/orchestra/apps/websites/directives.py
@@ -10,7 +10,6 @@ from orchestra.utils.python import import_class
from . import settings
-# TODO multiple and unique validation support in the formset
class SiteDirective(Plugin):
HTTPD = 'HTTPD'
SEC = 'ModSecurity'
@@ -141,7 +140,6 @@ class WordPressSaaS(SiteDirective):
name = 'wordpress-saas'
verbose_name = "WordPress SaaS"
help_text = _("URL path for mounting wordpress multisite.")
-# fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True
@@ -151,7 +149,6 @@ class DokuWikiSaaS(SiteDirective):
name = 'dokuwiki-saas'
verbose_name = "DokuWiki SaaS"
help_text = _("URL path for mounting wordpress multisite.")
-# fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True
@@ -161,7 +158,6 @@ class DrupalSaaS(SiteDirective):
name = 'drupal-saas'
verbose_name = "Drupdal SaaS"
help_text = _("URL path for mounting wordpress multisite.")
-# fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
group = SiteDirective.SAAS
regex = r'^/[^ ]*$'
unique_value = True