Blame pynslcd/attmap.py

Packit 6bd9ab
Packit 6bd9ab
# attmap.py - attribute mapping class
Packit 6bd9ab
#
Packit 6bd9ab
# Copyright (C) 2011, 2012, 2013 Arthur de Jong
Packit 6bd9ab
#
Packit 6bd9ab
# This library is free software; you can redistribute it and/or
Packit 6bd9ab
# modify it under the terms of the GNU Lesser General Public
Packit 6bd9ab
# License as published by the Free Software Foundation; either
Packit 6bd9ab
# version 2.1 of the License, or (at your option) any later version.
Packit 6bd9ab
#
Packit 6bd9ab
# This library is distributed in the hope that it will be useful,
Packit 6bd9ab
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6bd9ab
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6bd9ab
# Lesser General Public License for more details.
Packit 6bd9ab
#
Packit 6bd9ab
# You should have received a copy of the GNU Lesser General Public
Packit 6bd9ab
# License along with this library; if not, write to the Free Software
Packit 6bd9ab
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6bd9ab
# 02110-1301 USA
Packit 6bd9ab
Packit 6bd9ab
"""Module for handling attribute mappings used for LDAP searches.
Packit 6bd9ab
Packit 6bd9ab
>>> attrs = Attributes(uid='uid',
Packit 6bd9ab
...                    userPassword='userPassword',
Packit 6bd9ab
...                    uidNumber='uidNumber',
Packit 6bd9ab
...                    gidNumber='gidNumber',
Packit 6bd9ab
...                    gecos='"${gecos:-$cn}"',
Packit 6bd9ab
...                    homeDirectory='homeDirectory',
Packit 6bd9ab
...                    loginShell='loginShell')
Packit 6bd9ab
>>> 'cn' in attrs.attributes()
Packit 6bd9ab
True
Packit 6bd9ab
>>> attrs.translate({'uid': ['UIDVALUE', '2nduidvalue'], 'cn': ['COMMON NAME', ]})
Packit 6bd9ab
{'uid': ['UIDVALUE', '2nduidvalue'], 'loginShell': [], 'userPassword': [], 'uidNumber': [], 'gidNumber': [], 'gecos': ['COMMON NAME'], 'homeDirectory': []}
Packit 6bd9ab
>>> attrs['uidNumber']  # a representation fit for logging and filters
Packit 6bd9ab
'uidNumber'
Packit 6bd9ab
>>> attrs['gecos']
Packit 6bd9ab
'"${gecos:-$cn}"'
Packit 6bd9ab
"""
Packit 6bd9ab
Packit 6bd9ab
import re
Packit 6bd9ab
Packit 6bd9ab
from ldap.filter import escape_filter_chars
Packit 6bd9ab
import ldap.dn
Packit 6bd9ab
Packit 6bd9ab
from expr import Expression
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# exported names
Packit 6bd9ab
__all__ = ('Attributes', )
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# TODO: support objectSid attributes
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# regular expression to match function attributes
Packit 6bd9ab
attribute_func_re = re.compile('^(?P<function>[a-z]+)\((?P<attribute>.*)\)$')
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class SimpleMapping(str):
Packit 6bd9ab
    """Simple mapping to another attribute name."""
Packit 6bd9ab
Packit 6bd9ab
    def attributes(self):
Packit 6bd9ab
        return [self]
Packit 6bd9ab
Packit 6bd9ab
    def mk_filter(self, value):
Packit 6bd9ab
        return '(%s=%s)' % (
Packit 6bd9ab
                self, escape_filter_chars(str(value))
Packit 6bd9ab
            )
Packit 6bd9ab
Packit 6bd9ab
    def values(self, variables):
Packit 6bd9ab
        """Expand the expression using the variables specified."""
Packit 6bd9ab
        return variables.get(self, [])
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class ExpressionMapping(str):
Packit 6bd9ab
    """Class for parsing and expanding an expression."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, value):
Packit 6bd9ab
        """Parse the expression as a string."""
Packit 6bd9ab
        self.expression = Expression(value[1:-1])
Packit 6bd9ab
        super(ExpressionMapping, self).__init__(value)
Packit 6bd9ab
Packit 6bd9ab
    def values(self, variables):
Packit 6bd9ab
        """Expand the expression using the variables specified."""
Packit 6bd9ab
        return [self.expression.value(variables)]
Packit 6bd9ab
Packit 6bd9ab
    def attributes(self):
Packit 6bd9ab
        """Return the attributes defined in the expression."""
Packit 6bd9ab
        return self.expression.variables()
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class FunctionMapping(str):
Packit 6bd9ab
    """Mapping to a function to another attribute."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, mapping):
Packit 6bd9ab
        self.mapping = mapping
Packit 6bd9ab
        m = attribute_func_re.match(mapping)
Packit 6bd9ab
        self.attribute = m.group('attribute')
Packit 6bd9ab
        self.function = getattr(self, m.group('function'))
Packit 6bd9ab
        super(FunctionMapping, self).__init__(mapping)
Packit 6bd9ab
Packit 6bd9ab
    def upper(self, value):
Packit 6bd9ab
        return value.upper()
Packit 6bd9ab
Packit 6bd9ab
    def lower(self, value):
Packit 6bd9ab
        return value.lower()
Packit 6bd9ab
Packit 6bd9ab
    def attributes(self):
Packit 6bd9ab
        return [self.attribute]
Packit 6bd9ab
Packit 6bd9ab
    def mk_filter(self, value):
Packit 6bd9ab
        return '(%s=%s)' % (
Packit 6bd9ab
                self.attribute, escape_filter_chars(value)
Packit 6bd9ab
            )
Packit 6bd9ab
Packit 6bd9ab
    def values(self, variables):
Packit 6bd9ab
        return [self.function(value)
Packit 6bd9ab
                for value in variables.get(self.attribute, [])]
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class Attributes(dict):
Packit 6bd9ab
    """Dictionary-like class for handling attribute mapping."""
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, *args, **kwargs):
Packit 6bd9ab
        self.update(*args, **kwargs)
Packit 6bd9ab
Packit 6bd9ab
    def __setitem__(self, attribute, mapping):
Packit 6bd9ab
        # translate the mapping into a mapping object
Packit 6bd9ab
        if mapping[0] == '"' and mapping[-1] == '"':
Packit 6bd9ab
            mapping = ExpressionMapping(mapping)
Packit 6bd9ab
        elif '(' in mapping:
Packit 6bd9ab
            mapping = FunctionMapping(mapping)
Packit 6bd9ab
        else:
Packit 6bd9ab
            mapping = SimpleMapping(mapping)
Packit 6bd9ab
        super(Attributes, self).__setitem__(attribute, mapping)
Packit 6bd9ab
Packit 6bd9ab
    def update(self, *args, **kwargs):
Packit 6bd9ab
        for arg in args:
Packit 6bd9ab
            other = dict(arg)
Packit 6bd9ab
            for key in other:
Packit 6bd9ab
                self[key] = other[key]
Packit 6bd9ab
        for key in kwargs:
Packit 6bd9ab
            self[key] = kwargs[key]
Packit 6bd9ab
Packit 6bd9ab
    def attributes(self):
Packit 6bd9ab
        """Return the list of attributes that are referenced in this
Packit 6bd9ab
        attribute mapping. These are the attributes that should be
Packit 6bd9ab
        requested in the search."""
Packit 6bd9ab
        attributes = set()
Packit 6bd9ab
        for mapping in self.itervalues():
Packit 6bd9ab
            attributes.update(mapping.attributes())
Packit 6bd9ab
        return list(attributes)
Packit 6bd9ab
Packit 6bd9ab
    def mk_filter(self, attribute, value):
Packit 6bd9ab
        """Construct a search filter for searching for the attribute value
Packit 6bd9ab
        combination."""
Packit 6bd9ab
        mapping = self.get(attribute, SimpleMapping(attribute))
Packit 6bd9ab
        return mapping.mk_filter(value)
Packit 6bd9ab
Packit 6bd9ab
    def translate(self, variables):
Packit 6bd9ab
        """Return a dictionary with every attribute mapped to their value from
Packit 6bd9ab
        the specified variables."""
Packit 6bd9ab
        results = dict()
Packit 6bd9ab
        for attribute, mapping in self.iteritems():
Packit 6bd9ab
            results[attribute] = mapping.values(variables)
Packit 6bd9ab
        return results
Packit 6bd9ab
Packit 6bd9ab
    def get_rdn_value(self, dn, attribute):
Packit 6bd9ab
        """Extract the attribute value from from DN if possible. Return None
Packit 6bd9ab
        otherwise."""
Packit 6bd9ab
        return self.translate(dict(
Packit 6bd9ab
                (x, [y])
Packit 6bd9ab
                for x, y, z in ldap.dn.str2dn(dn)[0]
Packit 6bd9ab
            ))[attribute][0]