diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2011-05-01 19:08:39 +0000 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2011-05-01 19:08:39 +0000 |
commit | 4c19151250e318fa38dac33e5db1397b9d95a43e (patch) | |
tree | f177c2225f49ff5bb1b126663b3c734f63d7773e /pynslcd/attmap.py | |
parent | b4fa5371a770e8ba90a8d0ccd62ddabea0124fd5 (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.py | 194 |
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 |