summaryrefslogtreecommitdiff
path: root/passlib/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/base.py')
-rw-r--r--passlib/base.py221
1 files changed, 119 insertions, 102 deletions
diff --git a/passlib/base.py b/passlib/base.py
index 0aa4aab..d810cf5 100644
--- a/passlib/base.py
+++ b/passlib/base.py
@@ -221,11 +221,8 @@ def parse_policy_key(key):
cat, name, opt = key
else:
orig = key
- if '/' in key: #legacy format
- key = key.replace("/",".")
- elif '.' not in key and '__' in key: #lets user specifiy programmatically (since python doesn't allow '.')
+ if '.' not in key and '__' in key: #lets user specifiy programmatically (since python doesn't allow '.')
key = key.replace("__", ".")
- key = key.replace(" ","").replace("\t","") #strip out all whitespace from key
parts = key.split(".")
if len(parts) == 1:
cat = None
@@ -299,7 +296,7 @@ class CryptPolicy(object):
elif isinstance(source, (str,unicode)):
#FIXME: this autodetection makes me uncomfortable...
- if any(c in source for c in "\n\r\t"): #none of these chars should be in filepaths, but should be in config string
+ if any(c in source for c in "\n\r\t") or not source.strip(" \t./\;:"): #none of these chars should be in filepaths, but should be in config string
return cls.from_string(source)
else: #other strings should be filepath
@@ -323,7 +320,7 @@ class CryptPolicy(object):
kwds = {}
for source in sources:
policy = cls.from_source(source)
- kwds.update(policy.iteritems())
+ kwds.update(policy.iter_config(resolve=True))
#build new policy
return cls(**kwds)
@@ -345,8 +342,8 @@ class CryptPolicy(object):
#:list of all handlers, in order they will be checked when identifying (reverse of order specified)
_handlers = None #list of password hash handlers instances.
- #:dict mapping category -> fallback handler for that category
- _fallback = None
+ #:dict mapping category -> default handler for that category
+ _default = None
#:dict mapping category -> set of handler names which are deprecated for that category
_deprecated = None
@@ -421,37 +418,57 @@ class CryptPolicy(object):
handlers.append(handler)
#
- #build _deprecated & _fallback maps
+ #build _deprecated & _default maps
#
dmap = self._deprecated = {}
- fmap = self._fallback = {}
+ fmap = self._default = {}
mvmap = self._min_verify_time = {}
for cat, config in options.iteritems():
kwds = config.pop("context", None)
if not kwds:
continue
+
+ #list of deprecated schemes
deps = kwds.get("deprecated")
if deps:
- for scheme in deps:
- if scheme not in seen:
- raise ValueError, "unspecified scheme in deprecated list: %r" % (scheme,)
+ if handlers:
+ for scheme in deps:
+ if scheme not in seen:
+ raise ValueError, "unspecified scheme in deprecated list: %r" % (scheme,)
dmap[cat] = frozenset(deps)
- fb = kwds.get("fallback")
+
+ #default scheme
+ fb = kwds.get("default")
if fb:
- if fb not in seen:
- raise ValueError, "unspecified scheme set as fallback: %r" % (fb,)
- fmap[cat] = self.get_handler(fb, required=True)
+ if handlers:
+ if hasattr(fb, "name"):
+ fb = fb.name
+ if fb not in seen:
+ raise ValueError, "unspecified scheme set as default: %r" % (fb,)
+ fmap[cat] = self.get_handler(fb, required=True)
+ else:
+ fmap[cat] = fb
+
+ #min verify time
value = kwds.get("min_verify_time")
if value:
mvmap[cat] = value
+ #XXX: error or warning if unknown key found in kwds?
#NOTE: for dmap/fmap/mvmap -
- # if no cat=None value is specified, each has it's own fallbacks,
+ # if no cat=None value is specified, each has it's own defaults,
# (handlers[0] for fmap, set() for dmap, 0 for mvmap)
# but we don't store those in dict since it would complicate policy merge operation
#=========================================================
#public interface (used by CryptContext)
#=========================================================
+ def has_handlers(self):
+ return len(self._handlers) > 0
+
+ def iter_handlers(self):
+ "iterate through all loaded handlers in policy"
+ return iter(self._handlers)
+
def get_handler(self, name=None, category=None, required=False):
"""given an algorithm name, return algorithm handler which manages it.
@@ -469,7 +486,7 @@ class CryptPolicy(object):
if handler.name == name:
return handler
else:
- fmap = self._fallback
+ fmap = self._default
if category in fmap:
return fmap[category]
elif category and None in fmap:
@@ -492,11 +509,15 @@ class CryptPolicy(object):
options = self._options
#start with default values
- kwds = options[None].get("default") or {}
+ kwds = options[None].get("all")
+ if kwds is None:
+ kwds = {}
+ else:
+ kwds = kwds.copy()
#mix in category default values
if category and category in options:
- tmp = options[category].get("default")
+ tmp = options[category].get("all")
if tmp:
kwds.update(tmp)
@@ -513,7 +534,7 @@ class CryptPolicy(object):
return kwds
- def is_deprecated(self, name, category=None):
+ def handler_is_deprecated(self, name, category=None):
"check if algorithm is deprecated according to policy"
if hasattr(name, "name"):
name = name.name
@@ -538,68 +559,64 @@ class CryptPolicy(object):
#=========================================================
#serialization
#=========================================================
- def hashandlers(self):
- return self._handlers > 0
-
- def iterhandlers(self):
- "iterate through all loaded handlers in policy"
- return iter(self._handlers)
+ def iter_config(self, ini=False, resolve=False):
+ """iterate through key/value pairs of policy configuration
- def iteritems(self, format="python"):
- """iterate through keys in policy.
+ :param ini:
+ If ``True``, returns data formatted for insertion
+ into INI file. Keys use ``.`` separator instead of ``__``;
+ list of handlers returned as comma-separated strings.
- :param format:
- format results should be returned in.
- * ``python`` - returns keys with ``__`` separator, and raw values
- * ``tuple`` - returns keys as raw (cat,name,opt) tuple, and raw values
- * ``ini`` - returns keys with ``.`` separator, and strings instead of handler lists
+ :param resolve:
+ If ``True``, returns handler objects instead of handler
+ names where appropriate. Ignored if ``ini=True``.
:returns:
iterator which yeilds (key,value) pairs.
"""
- ini = False
- if format == "tuple":
- def format_key(cat, name, opt):
- return (cat, name, opt)
+ #
+ #prepare formatting functions
+ #
+ if ini:
+ fmt1 = "%s.%s.%s"
+ fmt2 = "%s.%s"
+ def encode_handler(h):
+ return h.name
+ def encode_hlist(hl):
+ return ", ".join(h.name for h in hl)
else:
- if format == "ini":
- fmt1 = "%s.%s.%s"
- fmt2 = "%s.%s"
-
- ini = True
- def h2n(handler):
- return handler.name
- def hlist(handlers):
- return ", ".join(map(h2n, handlers))
-
+ fmt1 = "%s__%s__%s"
+ fmt2 = "%s__%s"
+ if resolve:
+ def encode_handler(h):
+ return h
+ def encode_hlist(hl):
+ return list(hl)
else:
- assert format == "python"
- fmt1 = "%s__%s__%s"
- fmt2 = "%s__%s"
-
- def format_key(cat, name, opt):
- if cat:
- return fmt1 % (cat, name or "context", opt)
- if name:
- return fmt2 % (name, opt)
- return opt
+ def encode_handler(h):
+ return h.name
+ def encode_hlist(hl):
+ return [ h.name for h in hl ]
+
+ def format_key(cat, name, opt):
+ if cat:
+ return fmt1 % (cat, name or "context", opt)
+ if name:
+ return fmt2 % (name, opt)
+ return opt
+ #
+ #run through contents of internal configuration
+ #
value = self._handlers
if value:
- value = value[::-1]
- if ini:
- value = hlist(value)
- yield format_key(None, None, "schemes"), value
+ yield format_key(None, None, "schemes"), encode_hlist(reversed(value))
for cat, value in self._deprecated.iteritems():
- if ini:
- value = hlist(value)
- yield format_key(cat, None, "deprecated"), value
+ yield format_key(cat, None, "deprecated"), encode_hlist(value)
- for cat, value in self._fallback.iteritems():
- if ini:
- value = h2n(value)
- yield format_key(cat, None, "fallback"), value
+ for cat, value in self._default.iteritems():
+ yield format_key(cat, None, "default"), encode_handler(value)
for cat, value in self._min_verify_time.iteritems():
yield format_key(cat, None, "min_verify_time"), value
@@ -611,20 +628,20 @@ class CryptPolicy(object):
value = config[opt]
yield format_key(cat, name, opt), value
- def to_dict(self, format="python"):
+ def to_dict(self, resolve=False):
"return as dictionary of keywords"
- return dict(self.iteritems(format))
+ return dict(self.iter_config(resolve=resolve))
def _write_to_parser(self, parser, section):
"helper for to_string / to_file"
- p.add_section(section)
- for k,v in self.iteritems("ini"):
- p.set(section, k,v)
+ parser.add_section(section)
+ for k,v in self.iter_config(ini=True):
+ parser.set(section, k,v)
def to_string(self, section="passlib"):
"render to INI string"
p = ConfigParser()
- self._write_to_parser(p)
+ self._write_to_parser(p, section)
b = StringIO()
p.write(b)
return b.getvalue()
@@ -636,7 +653,7 @@ class CryptPolicy(object):
if not p.read([path]):
raise EnvironmentError, "failed to read existing file"
p.remove_section(section)
- self._write_to_parser(p)
+ self._write_to_parser(p, section)
fh = file(path, "w")
p.write(fh)
fh.close()
@@ -729,29 +746,29 @@ class CryptContext(object):
if kwds:
policy.append(kwds)
policy = CryptPolicy.from_sources(policy)
- if not policy.hashandlers():
+ if not policy.has_handlers():
raise ValueError, "at least one scheme must be specified"
self.policy = policy
def __repr__(self):
#XXX: *could* have proper repr(), but would have to render policy object options, and it'd be *really* long
- names = [ handler.name for handler in self.policy.iterhandlers() ]
+ names = [ handler.name for handler in self.policy.iter_handlers() ]
return "<CryptContext %0xd schemes=%r>" % (id(self), names)
- def replace(self, *args, **kwds):
- "return CryptContext with new policy which has specified values replaced"
- return CryptContext(policy=self.policy.replace(*args,**kwds))
+ ##def replace(self, *args, **kwds):
+ ## "return CryptContext with new policy which has specified values replaced"
+ ## return CryptContext(policy=self.policy.replace(*args,**kwds))
#===================================================================
#policy adaptation
#===================================================================
- def get_handler(self, name=None, category=None, required=False):
- """given an algorithm name, return CryptHandler instance which manages it.
- if no match is found, returns None.
-
- if name is None, will return default algorithm
- """
- return self.policy.get_handler(name, category, required)
+ ##def get_handler(self, name=None, category=None, required=False):
+ ## """given an algorithm name, return CryptHandler instance which manages it.
+ ## if no match is found, returns None.
+ ##
+ ## if name is None, will return default algorithm
+ ## """
+ ## return self.policy.get_handler(name, category, required)
def norm_handler_settings(self, handler, category=None, **settings):
"normalize settings for handler according to context configuration"
@@ -795,13 +812,13 @@ class CryptContext(object):
return settings
- def is_compliant(self, hash, category=None):
+ def hash_is_compliant(self, hash, category=None):
"""check if hash is allowed by current policy, or if secret should be re-encrypted"""
- handler = self.identify(hash, rethandler=True, required=True)
+ handler = self.identify(hash, resolve=True, required=True)
policy = self.policy
#check if handler has been deprecated
- if policy.is_deprecated(handler, category):
+ if policy.handler_is_deprecated(handler, category):
return True
#get options, and call compliance helper (check things such as rounds, etc)
@@ -829,7 +846,7 @@ class CryptContext(object):
#===================================================================
def genconfig(self, scheme=None, category=None, **settings):
"""Call genconfig() for specified handler"""
- handler = self.get_handler(scheme, category, required=True)
+ handler = self.policy.get_handler(scheme, category, required=True)
settings = self.norm_handler_settings(handler, category, **settings)
return handler.genconfig(**settings)
@@ -837,19 +854,19 @@ class CryptContext(object):
"""Call genhash() for specified handler"""
#NOTE: this doesn't use category in any way, but accepts it for consistency
if scheme:
- handler = self.get_handler(scheme, required=True)
+ handler = self.policy.get_handler(scheme, required=True)
else:
- handler = self.identify(config, rethandler=True, required=True)
+ handler = self.identify(config, resolve=True, required=True)
#XXX: could insert normalization to preferred unicode encoding here
return handler.genhash(secret, config, **context)
- def identify(self, hash, category=None, rethandler=False, required=False):
+ def identify(self, hash, category=None, resolve=False, required=False):
"""Attempt to identify which algorithm hash belongs to w/in this context.
:arg hash:
The hash string to test.
- :param rethandler:
+ :param resolve:
If ``True``, returns the handler itself,
instead of the name of the handler.
@@ -865,9 +882,9 @@ class CryptContext(object):
if required:
raise ValueError, "no hash specified"
return None
- for handler in self.policy.iterhandlers():
+ for handler in self.policy.iter_handlers():
if handler.identify(hash):
- if rethandler:
+ if resolve:
return handler
else:
return handler.name
@@ -896,7 +913,7 @@ class CryptContext(object):
"""
if not self:
raise ValueError, "no algorithms registered"
- handler = self.get_handler(scheme, category, required=True)
+ handler = self.policy.get_handler(scheme, category, required=True)
kwds = self.norm_handler_settings(handler, category, **kwds)
#XXX: could insert normalization to preferred unicode encoding here
return handler.encrypt(secret, **kwds)
@@ -923,9 +940,9 @@ class CryptContext(object):
#locate handler
if scheme:
- handler = self.get_handler(scheme, required=True)
+ handler = self.policy.get_handler(scheme, required=True)
else:
- handler = self.identify(hash, rethandler=True, required=True)
+ handler = self.identify(hash, resolve=True, required=True)
#strip context kwds if scheme doesn't use them
##for k in context.keys():