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]