From f4512ef27dbb096051b6232a9ad8a70daa1ff523 Mon Sep 17 00:00:00 2001
From: Rafael Monnerat <rafael@nexedi.com>
Date: Fri, 7 Aug 2009 13:18:00 +0000
Subject: [PATCH] Implement methods for get and modify configuration into
 zope.conf and zopectl. This allow portal_introspections modify products
 folders, python or zope home during one upgrade.

For this is required the file /etc/erp5.cfg with some allowed folders
definition. This is file can be modified to follow some better name
convension.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@28311 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Tool/IntrospectionTool.py | 153 +++++++++++++++++++++++--
 1 file changed, 141 insertions(+), 12 deletions(-)

diff --git a/product/ERP5/Tool/IntrospectionTool.py b/product/ERP5/Tool/IntrospectionTool.py
index 55057a8860..dbbb563945 100644
--- a/product/ERP5/Tool/IntrospectionTool.py
+++ b/product/ERP5/Tool/IntrospectionTool.py
@@ -39,6 +39,8 @@ from Products.ERP5 import _dtmldir
 from Products.ERP5.Tool.LogMixin import LogMixin
 from Products.ERP5Type.Utils import _setSuperSecurityManager
 from App.config import getConfiguration
+from AccessControl import Unauthorized
+from Products.ERP5Type.Cache import CachingMethod
 import tarfile
 
 _MARKER = []
@@ -196,6 +198,40 @@ class IntrospectionTool(LogMixin, BaseTool):
   #
   #   Instance variable definition access
   #
+  security.declareProtected(Permissions.ManagePortal, '_loadExternalConfig')
+  def _loadExternalConfig(self):
+    """
+      Load configuration from one external file, this configuration 
+      should be set for security reasons to prevent people access 
+      forbidden areas in the system.
+    """
+    def cached_loadExternalConfig():
+      import ConfigParser
+      config = ConfigParser.ConfigParser()
+      config.readfp(open('/etc/erp5.cfg'))
+      return config     
+
+    cached_loadExternalConfig = CachingMethod(cached_loadExternalConfig,
+                                id='IntrospectionTool__loadExternalConfig',
+                                cache_factory='erp5_content_long')
+    return  cached_loadExternalConfig()
+
+  security.declareProtected(Permissions.ManagePortal, '_getZopeConfigurationFile')
+  def _getZopeConfigurationFile(self, relative_path="", mode="r"):
+    """
+     Get a configuration file from the instance using relative path
+    """
+    if ".." in relative_path or relative_path.startswith("/"):
+      raise Unauthorized("In Relative Path, you cannot use .. or startwith / for security reason.")
+
+    instance_home = getConfiguration().instancehome
+    file_path = os.path.join(instance_home, relative_path)
+    if not os.path.exists(file_path):
+      raise IOError, 'The file: %s does not exist.' % file_path
+
+    return open(file_path, mode)
+    
+
   security.declareProtected(Permissions.ManagePortal, 'getSoftwareHome')
   def getSoftwareHome(self):
     """
@@ -204,6 +240,7 @@ class IntrospectionTool(LogMixin, BaseTool):
       Get the value of SOFTWARE_HOME for zopectl startup script
       or from zope.conf (whichever is most relevant)
     """
+    return getConfiguration().softwarehome
 
   security.declareProtected(Permissions.ManagePortal, 'setSoftwareHome')
   def setSoftwareHome(self, path):
@@ -219,6 +256,29 @@ class IntrospectionTool(LogMixin, BaseTool):
       WARNING: the list of possible path should be protected 
       if possible (ex. /etc/erp5/software_home)
     """
+    config = self._loadExternalConfig()
+    allowed_path_list = config.get("main", "zopehome").split("\n")
+  
+    if path not in allowed_path_list:
+      raise Unauthorized("You are setting one Unauthorized path as Zope Home.")
+
+    config_file = self._getZopeConfigurationFile("bin/zopectl")
+    new_file_list = []
+    for line in config_file:
+      if line.startswith("SOFTWARE_HOME="):
+        # Only comment the line, so it can easily reverted 
+        new_file_list.append("#%s" % (line))
+        new_file_list.append('SOFTWARE_HOME="%s"\n' % (path))
+      else:
+        new_file_list.append(line)
+
+    config_file.close()
+
+    # reopen file for write
+    config_file = self._getZopeConfigurationFile("bin/zopectl", "w")
+    config_file.write("".join(new_file_list))
+    config_file.close()
+    return 
 
   security.declareProtected(Permissions.ManagePortal, 'getPythonExecutable')
   def getPythonExecutable(self):
@@ -226,7 +286,15 @@ class IntrospectionTool(LogMixin, BaseTool):
       Get the value of PYTHON for zopectl startup script
       or from zope.conf (whichever is most relevant)
     """
-
+    config_file = self._getZopeConfigurationFile("bin/zopectl")
+    new_file_list = []
+    for line in config_file:
+      if line.startswith("PYTHON="):
+        return line.replace("PYTHON=","")
+
+    # Not possible get configuration from the zopecl
+    return None
+    
   security.declareProtected(Permissions.ManagePortal, 'setPythonExecutable')
   def setPythonExecutable(self, path):
     """
@@ -238,16 +306,39 @@ class IntrospectionTool(LogMixin, BaseTool):
       WARNING: the list of possible path should be protected 
       if possible (ex. /etc/erp5/python)
     """
-
-  security.declareProtected(Permissions.ManagePortal, 'getProductPath')
-  def getProductPath(self):
+    config = self._loadExternalConfig()
+    allowed_path_list = config.get("main", "python").split("\n")
+
+    if path not in allowed_path_list:
+      raise Unauthorized("You are setting one Unauthorized path as Python.")
+
+    config_file = self._getZopeConfigurationFile("bin/zopectl")
+    new_file_list = []
+    for line in config_file:
+      if line.startswith("PYTHON="):
+        # Only comment the line, so it can easily reverted 
+        new_file_list.append("#%s" % (line))
+        new_file_list.append('PYTHON="%s"\n' % (path))
+      else:
+        new_file_list.append(line)
+
+    config_file.close()    
+    # reopen file for write
+    config_file = self._getZopeConfigurationFile("bin/zopectl", "w")
+    config_file.write("".join(new_file_list))
+    config_file.close()
+    return 
+
+  security.declareProtected(Permissions.ManagePortal, 'getProductPathList')
+  def getProductPathList(self):
     """
       Get the value of SOFTWARE_HOME for zopectl startup script
       or from zope.conf (whichever is most relevant)
     """
+    return getConfiguration().products
 
-  security.declareProtected(Permissions.ManagePortal, 'setProductPath')
-  def setProductPath(self, path):
+  security.declareProtected(Permissions.ManagePortal, 'setProductPathList')
+  def setProductPathList(self, path_list):
     """
       Set the value of SOFTWARE_HOME for zopectl startup script
       or from zope.conf (whichever is most relevant)
@@ -256,8 +347,32 @@ class IntrospectionTool(LogMixin, BaseTool):
       on the same system
 
       WARNING: the list of possible path should be protected 
-      if possible (ex. /etc/erp5/python)
+      if possible (ex. /etc/erp5/product)
     """
+    config = self._loadExternalConfig()
+    allowed_path_list = config.get("main", "products").split("\n")
+
+    for path in path_list:
+       if path not in allowed_path_list:
+         raise Unauthorized("You are setting one Unauthorized path as Product Path.")
+
+    config_file = self._getZopeConfigurationFile("etc/zope.conf")
+    new_file_list = []
+    for line in config_file:
+      new_line = line
+      if line.strip(" ").startswith("products"):
+        # Only comment the line, so it can easily reverted 
+        new_line = "#%s" % ( line)
+      new_file_list.append(new_line)
+    for path in path_list:
+      new_file_list.append("products %s\n" % (path))
+    config_file.close()    
+
+    # reopen file for write
+    config_file = self._getZopeConfigurationFile("etc/zope.conf", "w")
+    config_file.write("".join(new_file_list))
+    config_file.close()
+    return 
 
   security.declareProtected(Permissions.ManagePortal, 'updateSVNProductList')
   def updateSVNProductList(self, path_list, revision=None):
@@ -270,12 +385,12 @@ class IntrospectionTool(LogMixin, BaseTool):
       buildout installer (server level) and build a complex custom
       software home or product home
     """
-    pass
-
+    raise NotImplemented 
 
   #
   #   Library signature
   #
+  # XXX this function can be cached to prevent save disk access.
   security.declareProtected(Permissions.ManagePortal, 'getSystemSignatureDict')
   def getSystemSignatureDict(self):
     """
@@ -284,11 +399,25 @@ class IntrospectionTool(LogMixin, BaseTool):
       {
          'python': '2.4.3'
        , 'pysvn': '1.2.3'
-    
-     
+      }
       NOTE: consider using autoconf / automake tools ?
     """
+    def tuple_to_format_str(t):
+       return '.'.join([str(i) for i in t])
 
-
+    from sys import version_info
+    # Get only x.x.x numbers.
+    py_version = tuple_to_format_str(version_info)
+    try:
+      import pysvn
+      # Convert tuple to x.x.x format
+      pysvn_version =  tuple_to_format_str(pysvn.version)
+    except:
+      pysvn_version = None
+    
+    return {
+            "python" : py_version, 
+            "pysvn"  : pysvn_version
+           }
 
 InitializeClass(IntrospectionTool)
-- 
2.30.9