dabo Commit
Revision 5681
Date: 2010-02-14 08:30:15 -0800 (Sun, 14 Feb 2010)
Author: Ed
Trac: http://trac.dabodev.com/changeset/5681

Changed:
U   trunk/dabo/dApp.py
U   trunk/dabo/db/dConnectInfo.py
U   trunk/dabo/lib/SimpleCrypt.py
U   trunk/dabo/lib/connParser.py
U   trunk/dabo/lib/utils.py
U   trunk/dabo/settings.py

Log:
Several major changes in this commit. Please test, and also note that I have 
once again made changes that could break your .cnxml files, due to a change in 
the way that encryption is handled.

The other big change is in the handling of pathing. I'm focusing my efforts on 
making the concept of an app's HomeDirectory much more important as the 
starting point for all path-related matters. So again, any relative pathing you 
may have in your code, especially in .cnxml and .cdxml files, may need to be 
updated to bring those paths in line with the current approach.

Recent changes to the SimpleCrypt script allowed you to set a dabo-level 
attribute in your settings_override script to your encryption key, and if that 
was available and you had the PyCrypto package installed, it would use 
DES-level encryption in your app.

This bothered me on several levels, but mostly because it required a plain text 
file that contained the key. In other words, while the encryption was better, 
it was no more secure.

Now there is a write-only property of dApp called 'CryptoKey': when you set 
that property, if you have PyCrypto installed, it will create a new SimpleCrypt 
instance that uses DES3 encryption. And to avoid having to pass a plain-text 
value to the app creation, you can set this property at any time, passing 
either a string or any callable. By passing a callable, you can more 
effectively hide the way your key is stored; the actual key never has to appear 
in your code at all.

All of the util functions to create and/or resolve relative pathing have been 
updated to default to the app's HomeDirectory instead of the current working 
directory. Also, many of the routines that call those methods have been udpated 
to work with the new relative pathing scheme.


Diff:
Modified: trunk/dabo/dApp.py
===================================================================
--- trunk/dabo/dApp.py  2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/dApp.py  2010-02-14 16:30:15 UTC (rev 5681)
@@ -173,12 +173,16 @@
        isDesigner = False
 
 
-       def __init__(self, selfStart=False, properties=None, *args, **kwargs):
+       def __init__(self, selfStart=False, ignoreScriptDir=False, 
properties=None, *args, **kwargs):
                if dabo.settings.loadUserLocale:
                        locale.setlocale(locale.LC_ALL, '')
 
                # Subdirectories that make up a standard Dabo app
                self._standardDirs = ("biz", "cache", "db", "lib", "reports", 
"resources", "test", "ui")
+               
+               # Some apps, such as the visual tools, are meant to be run from 
directories
+               # other than that where they are located. In those cases, use 
the current dir.
+               self._ignoreScriptDir = ignoreScriptDir
 
                self._uiAlreadySet = False
                dabo.dAppRef = self
@@ -907,7 +911,7 @@
                        if os.path.exists(dbDir) and os.path.isdir(dbDir):
                                files = glob.glob(os.path.join(dbDir, 
"*.cnxml"))
                                for f in files:
-                                       cn = importConnections(f)
+                                       cn = importConnections(f, 
useHomeDir=True)
                                        connDefs.update(cn)
                                        for kk in cn.keys():
                                                
self.dbConnectionNameToFiles[kk] = f
@@ -1042,7 +1046,7 @@
                                        connFile = sysFile
                                        break
                if os.path.exists(connFile):
-                       connDefs = importConnections(connFile)
+                       connDefs = importConnections(connFile, useHomeDir=True)
                        # For each connection definition, add an entry to
                        # self.dbConnectionDefs that contains a key on the
                        # name, and a value of a dConnectInfo object.
@@ -1322,6 +1326,10 @@
                self._cryptoProvider = val
 
 
+       def _setCryptoKey(self, val):
+               self._cryptoProvider = SimpleCrypt(key=val)
+
+
        def _getDatabaseActivityLog(self):
                return dabo.dbActivityLog.LogObject
 
@@ -1377,40 +1385,43 @@
                                hd = sys._daboRunHomeDir
                        except AttributeError:
                                calledScript = None
-                               try:
-                                       # Get the script name that launched the 
app. In case it was run
-                                       # as an executable, strip the leading 
'./'
-                                       calledScript = sys.argv[0]
-                               except IndexError:
-                                       # Give up... just assume the current 
directory.
+                               if self._ignoreScriptDir:
                                        hd = os.getcwd()
-                               if calledScript:
-                                       if calledScript.startswith("./"):
-                                               calledScript = 
calledScript.lstrip("./")
-                                       scriptDir = 
os.path.realpath(os.path.split(os.path.join(os.getcwd(), calledScript))[0])
-                                       appDir = 
os.path.realpath(os.path.split(inspect.getabsfile(self.__class__))[0])
-                                       def issubdir(d1, d2):
-                                               while True:
-                                                       if len(d1) < len(d2) or 
len(d1) <= 1:
-                                                               return False
-                                                       d1 = 
os.path.split(d1)[0]
-                                                       if d1 == d2:
-                                                               return True
+                               else:
+                                       try:
+                                               # Get the script name that 
launched the app. In case it was run
+                                               # as an executable, strip the 
leading './'
+                                               calledScript = sys.argv[0]
+                                       except IndexError:
+                                               # Give up... just assume the 
current directory.
+                                               hd = os.getcwd()
+                                       if calledScript:
+                                               if 
calledScript.startswith("./"):
+                                                       calledScript = 
calledScript.lstrip("./")
+                                               scriptDir = 
os.path.realpath(os.path.split(os.path.join(os.getcwd(), calledScript))[0])
+                                               appDir = 
os.path.realpath(os.path.split(inspect.getabsfile(self.__class__))[0])
+                                               def issubdir(d1, d2):
+                                                       while True:
+                                                               if len(d1) < 
len(d2) or len(d1) <= 1:
+                                                                       return 
False
+                                                               d1 = 
os.path.split(d1)[0]
+                                                               if d1 == d2:
+                                                                       return 
True
+       
+                                               if issubdir(scriptDir, appDir):
+                                                       # The directory where 
the main script is executing is a subdirectory of the
+                                                       # location of the 
application object in use. So we can safely make the app
+                                                       # directory the 
HomeDirectory.
+                                                       hd = appDir
+                                               else:
+                                                       # The directory where 
the main script is executing is *not* a subdirectory
+                                                       # of the location of 
the app object in use. The app object is likely an
+                                                       # instance of a raw 
dApp. So the only thing we can really do is make the
+                                                       # HomeDirectory the 
location of the main script, since we can't guess at
+                                                       # the application's 
directory structure.
+                                                       
dabo.infoLog.write("Can't deduce HomeDirectory:setting to the script 
directory.")
+                                                       hd = scriptDir
 
-                                       if issubdir(scriptDir, appDir):
-                                               # The directory where the main 
script is executing is a subdirectory of the
-                                               # location of the application 
object in use. So we can safely make the app
-                                               # directory the HomeDirectory.
-                                               hd = appDir
-                                       else:
-                                               # The directory where the main 
script is executing is *not* a subdirectory
-                                               # of the location of the app 
object in use. The app object is likely an
-                                               # instance of a raw dApp. So 
the only thing we can really do is make the
-                                               # HomeDirectory the location of 
the main script, since we can't guess at
-                                               # the application's directory 
structure.
-                                               dabo.infoLog.write("Can't 
deduce HomeDirectory:setting to the script directory.")
-                                               hd = scriptDir
-
                        if os.path.split(hd)[1][-4:].lower() in (".zip", 
".exe"):
                                # mangle HomeDirectory to not be the py2exe 
library.zip file,
                                # but the containing directory (the directory 
where the exe lives)
@@ -1641,6 +1652,11 @@
        Crypto = property(_getCrypto, _setCrypto, None,
                        _("Reference to the object that provides cryptographic 
services.  (varies)" ) )
 
+       CryptoKey = property(None, _setCryptoKey, None,
+                       _("""When set, creates a DES crypto object if PyCrypto 
is installed. Note that
+                       each time this property is set, a new PyCrypto instance 
is created, and 
+                       any previous crypto objects are released. Write-only.  
(varies)"""))
+
        DatabaseActivityLog = property(_getDatabaseActivityLog, 
_setDatabaseActivityLog, None,
                        _("""Path to the file (or file-like object) to be used 
for logging all database
                        activity. Default=None, which means no log is kept.   
(file or str)"""))

Modified: trunk/dabo/db/dConnectInfo.py
===================================================================
--- trunk/dabo/db/dConnectInfo.py       2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/db/dConnectInfo.py       2010-02-14 16:30:15 UTC (rev 5681)
@@ -136,6 +136,19 @@
                return self._backendObject
 
 
+       def _getCrypto(self):
+               try:
+                       return self.Application.Crypto
+               except AttributeError:
+                       if not getattr(self, "_cryptoProvider"):
+                               # Use the default crypto
+                               self._cryptoProvider = SimpleCrypt()
+                       return self._cryptoProvider
+
+       def _setCrypto(self, val):
+               self._cryptoProvider = val
+
+
        def _getDbType(self): 
                try:
                        return self._dbType
@@ -256,6 +269,10 @@
 
 
 
+       Crypto = property(_getCrypto, _setCrypto, None,
+                       _("""Reference to the object that provides 
cryptographic services if run
+                       outside of an application.  (varies)"""))
+
        DbType = property(_getDbType, _setDbType, None,
                        _("Name of the backend database type.  (str)"))
 

Modified: trunk/dabo/lib/SimpleCrypt.py
===================================================================
--- trunk/dabo/lib/SimpleCrypt.py       2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/lib/SimpleCrypt.py       2010-02-14 16:30:15 UTC (rev 5681)
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import random
+import string
 import warnings
 import base64
 import dabo
@@ -11,6 +12,11 @@
        security. Since this class is provided to all Dabo users, anyone with
        a copy of Dabo can decrypt your encrypted values.
        
+       You can make your application more secure by making sure that the
+       PyCrypto package is installed, and then setting the application's 
+       'CryptoKey' property to a string that is not publicly discoverable. This
+       will provide security as strong as the secrecy of this key.
+       
        For real-world applications, you should provide your own security
        class, and then set the Application's 'Crypto' property to an instance
        of that class. That class must provide the following interface:
@@ -21,24 +27,27 @@
        Thanks to Raymond Hettinger for the default (non-DES) code, originally 
found on
        http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266586
        """
-       def __init__(self):
+       def __init__(self, key=None):
                super(SimpleCrypt, self).__init__()
+               if callable(key):
+                       # Providing a callable is probably more secure than 
storing the key
+                       # directly in your code
+                       self.__key = key()
+               else:
+                       self.__key = key
                self._cryptoProvider = None
                # If the Crypto package is available, use it.
-               useDES = True
-               try:
-                       from Crypto.Cipher import DES
-               except ImportError:
-                       useDES = False
-               try:
-                       ckey = dabo.cryptoKeyDES[:8].rjust(8, "@")
-               except TypeError:
-                       dabo.errorLog.write("The 'cryptoKey' value has not been 
configured in dabo")
-                       useDES = False
-               if useDES:
-                               self._cryptoProvider = DES.new(ckey, 
DES.MODE_ECB)
-                       
+               self._useDES3 = (self.__key is not None)
+               if self._useDES3:
+                       try:
+                               from Crypto.Cipher import DES3
+                       except ImportError:
+                               self._useDES3 = False
+               if self._useDES3:
+                       self.__key = self.__key[:16].rjust(16, "@")
+                       self._cryptoProvider = DES3.new(self.__key, 
DES3.MODE_CBC)
 
+
        def showWarning(self):
                warnings.warn("WARNING: SimpleCrypt is not secure. Please see 
http://wiki.dabodev.com/SimpleCrypt for more information")
 
@@ -46,21 +55,18 @@
        def encrypt(self, aString):
                if not aString:
                        return ""
-               try:
-                       # If we are not using 
-                       encMethod = self._cryptoProvider.encrypt
-                       # Strings must be multiples of 8 in length
-                       padlen = 0
-                       pad = ""
-                       diffToEight = len(aString) % 8
-                       if diffToEight:
-                               padlen = 8 - diffToEight
-                               pad = "@" * padlen
-                       padVal = "%s%s" % (aString, pad)
-                       ret = "%s%s" % (padlen, encMethod(padVal))
-                       ret = base64.b64encode(ret)
-                       return ret
-               except AttributeError:
+               if self._useDES3:
+                       # Strings must have an introductory 8 byte string
+                       initialPad = "".join(random.sample(string.printable, 8))
+                       # Strings must be multiples of 8 bytes
+                       strLen = len(aString)
+                       diffToEight = 8 - (strLen % 8)
+                       pad = "@" * diffToEight
+                       paddedText = "%s%s%s" % (initialPad, pad, aString)
+                       enc = self._cryptoProvider.encrypt(paddedText)
+                       retText = "%s%s" % (diffToEight, enc)
+                       return base64.b64encode(retText)
+               else:
                        self.showWarning()
                        tmpKey = self.generateKey(aString)
                        myRand = random.Random(tmpKey).randrange
@@ -68,21 +74,19 @@
                        hex = self.strToHex("".join(crypted))
                        ret = "".join([tmpKey[i/2]  + hex[i:i+2] for i in 
range(0, len(hex), 2)])
                        return ret
-               
 
+
        def decrypt(self, aString):
                if not aString:
                        return ""
-               try:
-                       decryptMethod = self._cryptoProvider.decrypt
+               if self._useDES3:
                        decString = base64.b64decode(aString)
-                       padlen = int(decString[0])
-                       encval = decString[1:]
-                       ret = decryptMethod(encval)
-                       if padlen:
-                               ret = ret[:-padlen]
-                       return ret
-               except (ValueError, AttributeError):
+                       # We need to chop off any padding, along with the first 
8 garbage bytes
+                       padlen = int(decString[0]) + 8
+                       decString = decString[1:]
+                       ret = self._cryptoProvider.decrypt(decString)
+                       return ret[padlen:]
+               else:
                        self.showWarning()
                        tmpKey = "".join([aString[i] for i in range(0, 
len(aString), 3)])
                        val = "".join([aString[i+1:i+3] for i in range(0, 
len(aString), 3)])
@@ -97,12 +101,13 @@
                for i in range(len(s)):
                        chars.append(chr(65 + random.randrange(26)))
                return "".join(chars)
-               
 
+
        def strToHex(self, aString):
                hexlist = ["%02X" % ord(x) for x in aString]
                return ''.join(hexlist)
-       
+
+
        def hexToStr(self, aString):
                # Break the string into 2-character chunks
                try:

Modified: trunk/dabo/lib/connParser.py
===================================================================
--- trunk/dabo/lib/connParser.py        2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/lib/connParser.py        2010-02-14 16:30:15 UTC (rev 5681)
@@ -62,13 +62,27 @@
                return self.connDict
        
 
-def importConnections(pth=None):
+def importConnections(pth=None, useHomeDir=False):
+       """Read the connection info in the file passed as 'pth', and return
+       a dict containing connection names as keys and connection info
+       dicts as the values.
+       
+       If 'useHomeDir' is True, any file-based database connections
+       will have their pathing resolved based upon the app's current 
+       HomeDirectory. Otherwise, the path will be resolved relative to
+       the connection file itself.
+       """
        if pth is None:
                return None
        f = fileRef(pth)
        ch = connHandler()
        xml.sax.parse(f, ch)
        ret = ch.getConnectionDict()
+       basePath = pth
+       if useHomeDir:
+               basePath = dabo.dAppRef.HomeDirectory
+       else:
+               basePath = pth
 
        for cxn, data in ret.items():
                dbtype = data.get("dbtype", "")
@@ -76,9 +90,9 @@
                        for key, val in data.items():
                                if key=="database":
                                        osp = os.path
-                                       relpath = utils.resolvePath(val, pth, 
abspath=False)
+                                       relpath = utils.resolvePath(val, 
basePath, abspath=False)
                                        pth = 
pth.decode(dabo.fileSystemEncoding)
-                                       abspath = 
osp.abspath(osp.join(osp.split(pth)[0], relpath))
+                                       abspath = 
osp.abspath(osp.join(osp.split(basePath)[0], relpath))
                                        if osp.exists(abspath):
                                                ret[cxn][key] = abspath 
        return ret

Modified: trunk/dabo/lib/utils.py
===================================================================
--- trunk/dabo/lib/utils.py     2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/lib/utils.py     2010-02-14 16:30:15 UTC (rev 5681)
@@ -147,13 +147,13 @@
        is assumed.
        """
        if fromLoc is None:
-               fromLoc = os.getcwd()
+               fromLoc = dabo.dAppRef.HomeDirectory
        if toLoc.startswith(".."):
                if osp.isdir(fromLoc):
                        toLoc = osp.join(fromLoc, toLoc)
                else:
                        toLoc = osp.join(osp.split(fromLoc)[0], toLoc)
-       toLoc = osp.abspath(toLoc)
+       toLoc = osp.abspath(osp.normpath(toLoc))
        if osp.isfile(toLoc):
                toDir, toFile = osp.split(toLoc)
        else:
@@ -192,7 +192,7 @@
        return "path://"
 
 
-def resolveAttributePathing(atts, pth=None):
+def resolveAttributePathing(atts, pth=None, abspath=False):
        """Dabo design files store their information in XML, which means
        when they are 'read' the values come back in a dictionary of 
        attributes, which are then used to restore the designed object to its
@@ -206,15 +206,21 @@
        those new values.
        """
        prfx = getPathAttributePrefix()
-       pathsToConvert = [(kk, vv) for kk, vv in atts.items()
-                       if isinstance(vv, basestring) and vv.startswith(prfx)]
+       pathsToConvert = ((kk, vv) for kk, vv in atts.items()
+                       if isinstance(vv, basestring) and vv.startswith(prfx))
        for convKey, convVal in pathsToConvert:
                # Strip the path designator
                convVal = convVal.replace(prfx, "")
-               # Convert to relative path
-               relPath = relativePath(convVal, pth)
+               if abspath:
+                       if pth:
+                               retPath = osp.normpath(osp.join(pth, convVal))
+                       else:
+                               retPath = resolvePath(convVal, pth, True)
+               else:
+                       # Convert to relative path
+                       retPath = relativePath(convVal, pth)
                # Update the atts
-               atts[convKey] = relPath
+               atts[convKey] = retPath
 
 
 def resolvePath(val, pth=None, abspath=False):

Modified: trunk/dabo/settings.py
===================================================================
--- trunk/dabo/settings.py      2010-02-13 20:03:02 UTC (rev 5680)
+++ trunk/dabo/settings.py      2010-02-14 16:30:15 UTC (rev 5681)
@@ -1,5 +1,8 @@
 # -*- coding: utf-8 -*-
 
+import os
+import sys
+
 # Dabo Global Settings
 
 # Do not modify this file directly. Instead, create a file called 
@@ -176,14 +179,12 @@
 # URL of the Web Update server
 webupdate_urlbase = "http://daboserver.com/webupdate";
 
-# Customized encryption key if using the DES cipher from the Crypto package.
-# If you are using that package, you need to override this in the 
settings_override.py
-# file to something unique for your application.
-cryptoKeyDES = None
 
-
 ### Settings - end
 
+# Make sure that the current directory is in the sys.path
+sys.path.append(os.getcwd())
+
 # Do not copy/paste anything below this line into settings_override.py.
 try:
        from settings_override import *



_______________________________________________
Post Messages to: [email protected]
Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev
Searchable Archives: http://leafe.com/archives/search/dabo-dev
This message: 
http://leafe.com/archives/byMID/[email protected]

Reply via email to