Thursday, 7 January 2010

A configuration file with an encrypted password in Python

I need to set configuration by environment in quite a few Python utils. I'd like to create an object with a filename as a constructor variable, and then get a dict of the values. One issue is the password -  better not to keep it in plain text. So I need to make them "encrypted", or a better phrase might be "non-human readable".

import ConfigParser
import base64
import unittest
import os
import optparse
class SettingsTest(unittest.TestCase):
"""Tests for the Settings class"""
def setUp(self):
self.testfile = "test.ini"
# values
self.unenckey1 = "something"
self.unencval1 = "18"
self.unenckey2 = "somethingelse"
self.unencval2 = "woop;"
self.enckey = "password"
self.encval = "abc123"
# cleanup
if os.path.exists(self.testfile):
os.remove(self.testfile)
def testCanCreateObject(self):
self.settings = Settings(self.testfile)
self.failUnless(self.settings)
def testCanAddSomeValues(self):
self.testCanCreateObject()
self.settings.add(self.unenckey1, self.unencval1)
self.settings.add(self.unenckey2, self.unencval2)
def testCanAddEncryptedValue(self):
self.testCanAddSomeValues()
self.settings.addenc(self.enckey, self.encval)
def testFileIsCreated(self):
self.testCanAddEncryptedValue()
self.settings.write()
self.failUnless(os.path.exists(self.testfile))
def testCanRetrieveSomething(self):
self.testFileIsCreated()
self.settings2 = Settings(self.testfile)
self.result = self.settings.get()
self.failUnless(self.result)
def testCanRetrieveADict(self):
self.testCanRetrieveSomething()
self.failUnless(type(self.result).__name__=='dict')
def testItContainsCorrectUnencryptedValues(self):
self.testCanRetrieveADict()
self.failUnless(self.result[self.unenckey1] == self.unencval1)
self.failUnless(self.result[self.unenckey2] == self.unencval2)
def testItContainsCorrectEncryptedValues(self):
self.testCanRetrieveADict()
self.failUnless(self.result[self.enckey] == self.encval)
class Settings():
""" Class which writes out and reads in from an ini type file """
def __init__(self, filename):
self.filename = filename
self.parser = ConfigParser.RawConfigParser()
if os.path.exists(self.filename):
self.parser.read(self.filename)
else:
self.parser.add_section('Cheese')
self.parser.add_section('Ham')
def add(self, key, value):
self.parser.set('Ham', key, value)
def addenc(self, key, value):
self.parser.set('Cheese', key, base64.b64encode(value))
def write(self):
with open(self.filename, 'wb') as configfile:
self.parser.write(configfile)
def get(self):
result = {}
result["settings-filename"] = self.filename
result.update(self.getUnEnc())
result.update(self.getEnc())
return result
def getUnEnc(self):
result = {}
for i in self.parser.items("Ham"):
result[i[0]] = i[1]
return result
def getEnc(self):
result = {}
for i in self.parser.items("Cheese"):
result[i[0]] = base64.b64decode(i[1])
return result
if __name__ == "__main__":
parser = optparse.OptionParser("usage: %prog [options] - runs tests without options")
parser.set_defaults(encrypted=False)
parser.add_option( "-f",
dest="filename",
default="settings.ini",
type="string",
help="specify filename to write settings to (default=settings.ini)")
parser.add_option("-k",
dest="key",
type="string",
help="key to write into settings")
parser.add_option("-v",
dest="value",
type="string",
help="value to write into settings")
parser.add_option("-e",
dest="encrypted",
action="store_true",
help="flag to encrypt value")
(options, args) = parser.parse_args()
if options.key or options.value:
status = "[added " + options.key + ":" + options.value + " "
settings = Settings(options.filename)
if options.encrypted:
settings.addenc(options.key, options.value)
status += "encrypted "
else:
settings.add(options.key, options.value)
settings.write()
status+= "to " + options.filename + "]"
print status
else:
print "Running Tests"
unittest.main()
view raw settings.py hosted with ❤ by GitHub


http://gist.github.com/273393

I got my head round the python unittest framework when writing this, and also got my head round the ConfigParser and optparse libraries.

1 comment: