summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-08-12 18:31:10 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-08-12 18:31:10 -0400
commit4cb8a444b8566c273632a07e04eb44e6b928b5e7 (patch)
treeaf3bdba812a82e6e8d73eb872cd4f08396d19ff1
parenteef4a5ea1027db49e49b52e3034dd5611b937a63 (diff)
downloadpasslib-4cb8a444b8566c273632a07e04eb44e6b928b5e7.tar.gz
GAE compatibility fix: changed apache UTs to not rely on filesystem.
* added private constructor Htpasswd._from_string(), will clean it up and make public in future * most apache tests now use _from_string() so they don't have to write to fs * tests which still require writing to fs (mainly the autoload tests) now skipped for GAE
-rw-r--r--CHANGES8
-rw-r--r--passlib/apache.py39
-rw-r--r--passlib/tests/test_apache.py135
3 files changed, 109 insertions, 73 deletions
diff --git a/CHANGES b/CHANGES
index 75af3b9..cb99162 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,13 +9,13 @@ Release History
* Google App Engine compatibility:
- bugfix: make ``passlib.hash.__loader__`` attribute writable -
- needed by Google App Engine [issue 19]
+ needed by Google App Engine [issue 19].
- - provide fallback for loading ``passlib/default.cfg``
+ - bugfix: provide fallback for loading ``passlib/default.cfg``
if :mod:`pkg_resources` is not present.
- - under GAE, disable CryptContext unittests which
- require writing to filesystem
+ - under GAE, disable all unittests which
+ require writing to filesystem.
* bugfix: fixed error thrown by CryptContext.verify
when issuing min_verify_time warning [issue 17]
diff --git a/passlib/apache.py b/passlib/apache.py
index 01c9e8f..deadff0 100644
--- a/passlib/apache.py
+++ b/passlib/apache.py
@@ -27,6 +27,15 @@ DEFAULT_ENCODING = "utf-8" if sys.version_info >= (3,0) else None
class _CommonFile(object):
"helper for HtpasswdFile / HtdigestFile"
+ #XXX: would like to add 'path' keyword to load() / save(),
+ # but that makes .mtime somewhat meaningless.
+ # to simplify things, should probably deprecate mtime & force=False
+ # options.
+ #XXX: would also like to make _load_string available via public interface,
+ # such as via 'content' keyword in load() method.
+ # in short, need to clean up the htpasswd api a little bit in 1.6.
+ # keeping _load_string private for now, cause just using it for UTing.
+
#NOTE: 'path' is a property instead of attr,
# so that .mtime is wiped whenever path is changed.
_path = None
@@ -38,6 +47,13 @@ class _CommonFile(object):
self._path = path
path = property(_get_path, _set_path)
+ @classmethod
+ def _from_string(cls, content, **kwds):
+ #NOTE: not public yet, just using it for unit tests.
+ self = cls(**kwds)
+ self._load_string(content)
+ return self
+
def __init__(self, path=None, autoload=True,
encoding=DEFAULT_ENCODING,
):
@@ -56,7 +72,24 @@ class _CommonFile(object):
self._entry_order = []
self._entry_map = {}
- #TODO: load_string ?
+ def _load_string(self, content):
+ """UT helper for loading from string
+
+ to be improved/made public in later release.
+
+
+ :param content:
+ if specified, should be a bytes object.
+ passwords will be loaded directly from this string,
+ and any files will be ignored.
+ """
+ if isinstance(content, unicode):
+ content = content.encode(self.encoding or 'utf-8')
+ self.mtime = 0
+ #XXX: replace this with iterator?
+ lines = content.splitlines()
+ self._load_lines(lines)
+ return True
def load(self, force=True):
"""load entries from file
@@ -74,7 +107,7 @@ class _CommonFile(object):
raise RuntimeError("no load path specified")
if not force and self.mtime and self.mtime == os.path.getmtime(path):
return False
- with open(path, "rbU") as fh:
+ with open(path, "rb") as fh:
self.mtime = os.path.getmtime(path)
self._load_lines(fh)
return True
@@ -103,7 +136,7 @@ class _CommonFile(object):
entry_map = self._entry_map
assert len(entry_order) == len(entry_map), "internal error in entry list"
return (rl(key, entry_map[key]) for key in entry_order)
-
+
def save(self):
"save entries to file"
if not self.path:
diff --git a/passlib/tests/test_apache.py b/passlib/tests/test_apache.py
index 7d0b3a7..253487e 100644
--- a/passlib/tests/test_apache.py
+++ b/passlib/tests/test_apache.py
@@ -12,7 +12,7 @@ import time
#pkg
from passlib import apache
from passlib.utils import b, native_str, bytes
-from passlib.tests.utils import TestCase, mktemp
+from passlib.tests.utils import TestCase, mktemp, gae_env
#module
log = getLogger(__name__)
@@ -50,8 +50,11 @@ class HtpasswdFileTest(TestCase):
sample_dup = b('user1:pass1\nuser1:pass2\n')
- def test_00_constructor(self):
- "test constructor & to_string()"
+ def test_00_constructor_autoload(self):
+ "test constructor autoload"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
+
#check with existing file
path = mktemp()
set_file(path, self.sample_01)
@@ -66,17 +69,11 @@ class HtpasswdFileTest(TestCase):
os.remove(path)
self.assertRaises(IOError, apache.HtpasswdFile, path)
- #check no path
- ht = apache.HtpasswdFile()
- self.assertEqual(ht.to_string(), b(""))
-
#NOTE: "default" option checked via update() test, among others
def test_01_delete(self):
"test delete()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtpasswdFile(path)
+ ht = apache.HtpasswdFile._from_string(self.sample_01)
self.assertTrue(ht.delete("user1"))
self.assertTrue(ht.delete("user2"))
self.assertTrue(not ht.delete("user5"))
@@ -86,9 +83,8 @@ class HtpasswdFileTest(TestCase):
def test_02_update(self):
"test update()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtpasswdFile(path, default="plaintext")
+ ht = apache.HtpasswdFile._from_string(
+ self.sample_01, default="plaintext")
self.assertTrue(ht.update("user2", "pass2x"))
self.assertTrue(not ht.update("user5", "pass5"))
self.assertEqual(ht.to_string(), self.sample_03)
@@ -97,9 +93,7 @@ class HtpasswdFileTest(TestCase):
def test_03_users(self):
"test users()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtpasswdFile(path)
+ ht = apache.HtpasswdFile._from_string(self.sample_01)
ht.update("user5", "pass5")
ht.delete("user3")
ht.update("user3", "pass3")
@@ -107,9 +101,7 @@ class HtpasswdFileTest(TestCase):
def test_04_verify(self):
"test verify()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtpasswdFile(path)
+ ht = apache.HtpasswdFile._from_string(self.sample_01)
self.assertTrue(ht.verify("user5","pass5") is None)
for i in xrange(1,5):
i = str(i)
@@ -120,6 +112,8 @@ class HtpasswdFileTest(TestCase):
def test_05_load(self):
"test load()"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
#setup empty file
path = mktemp()
@@ -155,6 +149,9 @@ class HtpasswdFileTest(TestCase):
def test_06_save(self):
"test save()"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
+
#load from file
path = mktemp()
set_file(path, self.sample_01)
@@ -172,35 +169,42 @@ class HtpasswdFileTest(TestCase):
self.assertRaises(RuntimeError, hb.save)
def test_07_encodings(self):
- "test encoding parameter"
- path = mktemp()
- set_file(path, self.sample_01)
-
+ "test encoding parameter behavior"
#test bad encodings cause failure in constructor
- self.assertRaises(ValueError, apache.HtpasswdFile, path, encoding="utf-16")
+ self.assertRaises(ValueError, apache.HtpasswdFile, encoding="utf-16")
#check users() returns native string by default
- ht = apache.HtpasswdFile(path)
+ ht = apache.HtpasswdFile._from_string(self.sample_01)
self.assertIsInstance(ht.users()[0], native_str)
#check returns unicode if encoding explicitly set
- ht = apache.HtpasswdFile(path, encoding="utf-8")
+ ht = apache.HtpasswdFile._from_string(self.sample_01, encoding="utf-8")
self.assertIsInstance(ht.users()[0], unicode)
#check returns bytes if encoding explicitly disabled
- ht = apache.HtpasswdFile(path, encoding=None)
+ ht = apache.HtpasswdFile._from_string(self.sample_01, encoding=None)
self.assertIsInstance(ht.users()[0], bytes)
#check sample utf-8
- set_file(path, self.sample_04_utf8)
- ht = apache.HtpasswdFile(path, encoding="utf-8")
+ ht = apache.HtpasswdFile._from_string(self.sample_04_utf8, encoding="utf-8")
self.assertEqual(ht.users(), [ u"user\u00e6" ])
#check sample latin-1
- set_file(path, self.sample_04_latin1)
- ht = apache.HtpasswdFile(path, encoding="latin-1")
+ ht = apache.HtpasswdFile._from_string(self.sample_04_latin1,
+ encoding="latin-1")
self.assertEqual(ht.users(), [ u"user\u00e6" ])
+ def test_08_to_string(self):
+ "test to_string"
+
+ #check with known sample
+ ht = apache.HtpasswdFile._from_string(self.sample_01)
+ self.assertEqual(ht.to_string(), self.sample_01)
+
+ #test blank
+ ht = apache.HtpasswdFile()
+ self.assertEqual(ht.to_string(), b(""))
+
#=========================================================
#eoc
#=========================================================
@@ -219,8 +223,11 @@ class HtdigestFileTest(TestCase):
sample_04_utf8 = b('user\xc3\xa6:realm\xc3\xa6:549d2a5f4659ab39a80dac99e159ab19\n')
sample_04_latin1 = b('user\xe6:realm\xe6:549d2a5f4659ab39a80dac99e159ab19\n')
- def test_00_constructor(self):
- "test constructor & to_string()"
+ def test_00_constructor_autoload(self):
+ "test constructor autoload"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
+
#check with existing file
path = mktemp()
set_file(path, self.sample_01)
@@ -235,15 +242,9 @@ class HtdigestFileTest(TestCase):
os.remove(path)
self.assertRaises(IOError, apache.HtdigestFile, path)
- #check no path
- ht = apache.HtdigestFile()
- self.assertEqual(ht.to_string(), b(""))
-
def test_01_delete(self):
"test delete()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertTrue(ht.delete("user1", "realm"))
self.assertTrue(ht.delete("user2", "realm"))
self.assertTrue(not ht.delete("user5", "realm"))
@@ -253,9 +254,7 @@ class HtdigestFileTest(TestCase):
def test_02_update(self):
"test update()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertTrue(ht.update("user2", "realm", "pass2x"))
self.assertTrue(not ht.update("user5", "realm", "pass5"))
self.assertEqual(ht.to_string(), self.sample_03)
@@ -268,9 +267,7 @@ class HtdigestFileTest(TestCase):
def test_03_users(self):
"test users()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
ht.update("user5", "realm", "pass5")
ht.delete("user3", "realm")
ht.update("user3", "realm", "pass3")
@@ -278,9 +275,7 @@ class HtdigestFileTest(TestCase):
def test_04_verify(self):
"test verify()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertTrue(ht.verify("user5", "realm","pass5") is None)
for i in xrange(1,5):
i = str(i)
@@ -291,6 +286,8 @@ class HtdigestFileTest(TestCase):
def test_05_load(self):
"test load()"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
#setup empty file
path = mktemp()
@@ -321,6 +318,9 @@ class HtdigestFileTest(TestCase):
def test_06_save(self):
"test save()"
+ if gae_env:
+ return self.skipTest("GAE doesn't offer read/write filesystem access")
+
#load from file
path = mktemp()
set_file(path, self.sample_01)
@@ -339,9 +339,7 @@ class HtdigestFileTest(TestCase):
def test_07_realms(self):
"test realms() & delete_realm()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertEqual(ht.delete_realm("x"), 0)
self.assertEqual(ht.realms(), ['realm'])
@@ -352,48 +350,53 @@ class HtdigestFileTest(TestCase):
def test_08_find(self):
"test find()"
- path = mktemp()
- set_file(path, self.sample_01)
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertEqual(ht.find("user3", "realm"), "a500bb8c02f6a9170ae46af10c898744")
self.assertEqual(ht.find("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519")
self.assertEqual(ht.find("user5", "realm"), None)
def test_09_encodings(self):
"test encoding parameter"
- path = mktemp()
- set_file(path, self.sample_01)
-
#test bad encodings cause failure in constructor
- self.assertRaises(ValueError, apache.HtdigestFile, path, encoding="utf-16")
+ self.assertRaises(ValueError, apache.HtdigestFile, encoding="utf-16")
#check users() returns native string by default
- ht = apache.HtdigestFile(path)
+ ht = apache.HtdigestFile._from_string(self.sample_01)
self.assertIsInstance(ht.realms()[0], native_str)
self.assertIsInstance(ht.users("realm")[0], native_str)
#check returns unicode if encoding explicitly set
- ht = apache.HtdigestFile(path, encoding="utf-8")
+ ht = apache.HtdigestFile._from_string(self.sample_01, encoding="utf-8")
self.assertIsInstance(ht.realms()[0], unicode)
self.assertIsInstance(ht.users(u"realm")[0], unicode)
#check returns bytes if encoding explicitly disabled
- ht = apache.HtdigestFile(path, encoding=None)
+ ht = apache.HtdigestFile._from_string(self.sample_01, encoding=None)
self.assertIsInstance(ht.realms()[0], bytes)
self.assertIsInstance(ht.users(b("realm"))[0], bytes)
#check sample utf-8
- set_file(path, self.sample_04_utf8)
- ht = apache.HtdigestFile(path, encoding="utf-8")
+ ht = apache.HtdigestFile._from_string(self.sample_04_utf8, encoding="utf-8")
self.assertEqual(ht.realms(), [ u"realm\u00e6" ])
self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ])
#check sample latin-1
- set_file(path, self.sample_04_latin1)
- ht = apache.HtdigestFile(path, encoding="latin-1")
+ ht = apache.HtdigestFile._from_string(self.sample_04_latin1, encoding="latin-1")
self.assertEqual(ht.realms(), [ u"realm\u00e6" ])
self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ])
+
+ def test_10_to_string(self):
+ "test to_string()"
+
+ #check sample
+ ht = apache.HtdigestFile._from_string(self.sample_01)
+ self.assertEqual(ht.to_string(), self.sample_01)
+
+ #check blank
+ ht = apache.HtdigestFile()
+ self.assertEqual(ht.to_string(), b(""))
+
#=========================================================
#eoc
#=========================================================