summaryrefslogtreecommitdiff
path: root/pynslcd/attmap.py
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2011-05-01 19:08:39 +0000
committerArthur de Jong <arthur@arthurdejong.org>2011-05-01 19:08:39 +0000
commit4c19151250e318fa38dac33e5db1397b9d95a43e (patch)
treef177c2225f49ff5bb1b126663b3c734f63d7773e /pynslcd/attmap.py
parentb4fa5371a770e8ba90a8d0ccd62ddabea0124fd5 (diff)
implement attribute mapping functionality and do some refactoring
git-svn-id: http://arthurdejong.org/svn/nss-pam-ldapd/nss-pam-ldapd@1454 ef36b2f9-881f-0410-afb5-c4e39611909c
Diffstat (limited to 'pynslcd/attmap.py')
-rw-r--r--pynslcd/attmap.py194
1 files changed, 194 insertions, 0 deletions
diff --git a/pynslcd/attmap.py b/pynslcd/attmap.py
new file mode 100644
index 0000000..f72fa62
--- /dev/null
+++ b/pynslcd/attmap.py
@@ -0,0 +1,194 @@
+
+# attributes.py - attribute mapping functions
+#
+# Copyright (C) 2011 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Module for handling attribute mappings used for LDAP searches.
+
+>>> attrs = Attributes(uid='uid',
+... userPassword='userPassword',
+... uidNumber='uidNumber',
+... gidNumber='gidNumber',
+... gecos='"${gecos:-$cn}"',
+... homeDirectory='homeDirectory',
+... loginShell='loginShell')
+>>> attrs.attributes()
+('uid', 'userPassword', 'uidNumber', 'gidNumber', 'gecos', 'cn', 'homeDirectory', 'loginShell')
+>>> attrs.value('gecos', {'cn': 'test'})
+['test']
+>>> attrs.search('uidNumber', 100)
+'(uidNumber=100)'
+>>> attrs['foo'] = '\"bar\"'
+>>> attrs.get('foo', {})
+['bar']
+"""
+
+# exported names
+__all__ = ( 'Attributes', )
+
+
+# FIXME: support multiple attribute values
+# TODO: support objectSid attributes
+# TODO: do more expression validity checking
+# TODO: handle userPassword specially to do filtering of results
+
+
+class MyIter(object):
+ """Custom iterator-like class with a back() method."""
+
+ def __init__(self, value):
+ self.value = value
+ self.pos = 0
+
+ def next(self):
+ self.pos += 1
+ return self.value[self.pos-1]
+
+ def back(self):
+ self.pos -= 1
+
+ def __iter__(self):
+ return self
+
+
+class DollarExpression(object):
+ """Class for handling a variable $xxx ${xxx}, ${xxx:-yyy} or ${xxx:+yyy}
+ expression."""
+
+ def _parse_varname(self, value):
+ """Read a variable name from the value iterator."""
+ name = ''
+ for c in value:
+ if not c.isalnum():
+ value.back()
+ return name
+ name += c
+
+ def __init__(self, value):
+ """Parse the expression as the start of a $-expression."""
+ self.op = None
+ self.expr = None
+ c = value.next()
+ if c == '{':
+ self.name = self._parse_varname(value)
+ c = value.next()
+ if c == '}':
+ return
+ self.op = c + value.next()
+ self.expr = Expression(value, endat='}')
+ else:
+ value.back()
+ self.name = self._parse_varname(value)
+
+ def value(self, variables):
+ """Expand the expression using the variables specified."""
+ value = variables.get(self.name, [''])[0]
+ # FIXME: expand list
+ if self.op == ':-':
+ return value if value else self.expr.value(variables)
+ elif self.op == ':+':
+ return self.expr.value(variables) if value else ''
+ return value
+
+ def variables(self, results):
+ """Add the variables used in the expression to results."""
+ results.add(self.name)
+ if self.expr:
+ self.expr.variables(results)
+
+
+class Expression(object):
+ """Class for parsing and expanding an expression."""
+
+ def __init__(self, value, endat=None):
+ """Parse the expression as a string."""
+ if not isinstance(value, MyIter):
+ value = MyIter(value)
+ if not endat:
+ endat = value.next() # skip opening quote
+ expr = []
+ literal = ''
+ c = value.next()
+ while c != endat:
+ if c == '$':
+ if literal:
+ expr.append(literal)
+ expr.append(DollarExpression(value))
+ literal = ''
+ elif c == '\\':
+ literal += value.next()
+ else:
+ literal += c
+ c = value.next()
+ if literal:
+ expr.append(literal)
+ self.expr = expr
+
+ def value(self, variables):
+ """Expand the expression using the variables specified."""
+ res = ''
+ for x in self.expr:
+ if hasattr(x, 'value'):
+ res += x.value(variables)
+ else:
+ res += x
+ return res
+
+ def variables(self, results=None):
+ """Return the variables defined in the expression."""
+ if not results:
+ results = set()
+ for x in self.expr:
+ if hasattr(x, 'variables'):
+ x.variables(results)
+ return results
+
+
+class Attributes(dict):
+ """Dictionary-like class for handling a list of attributes."""
+
+ def _prepare(self):
+ """Go over all values to parse any expressions."""
+ updates = dict()
+ for k, v in self.iteritems():
+ if isinstance(v, basestring) and v[0] == '"':
+ updates[k] = Expression(v)
+ self.update(updates)
+
+ def attributes(self):
+ """Return a set of attributes that are referenced in this attribute
+ mapping."""
+ self._prepare()
+ results = set()
+ for value in self.itervalues():
+ if hasattr(value, 'variables'):
+ results.update(value.variables())
+ else:
+ results.add(value)
+ return list(results)
+
+ def mapped(self, variables):
+ """Return a dictionary with every attribute mapped to their value from
+ the specified variables."""
+ results = dict()
+ for k, v in self.iteritems():
+ if hasattr(v, 'value'):
+ results[k] = [v.value(variables)]
+ else:
+ results[k] = variables.get(v, [])
+ return results