diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2012-05-04 20:13:57 +0000 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2012-05-04 20:13:57 +0000 |
commit | d8c5bb2778c0680d99b5eab0f6e6fa52ab1b6374 (patch) | |
tree | d411fad2eaf37ac1a76d63167d1577b807f81a43 | |
parent | 85e30f2a743ab2ec2a2cccaa55c5cb9f692039b3 (diff) |
move expression handling to own module
git-svn-id: http://arthurdejong.org/svn/nss-pam-ldapd/nss-pam-ldapd@1682 ef36b2f9-881f-0410-afb5-c4e39611909c
-rw-r--r-- | pynslcd/Makefile.am | 3 | ||||
-rw-r--r-- | pynslcd/attmap.py | 144 | ||||
-rw-r--r-- | pynslcd/cfg.py | 3 | ||||
-rw-r--r-- | pynslcd/expr.py | 177 |
4 files changed, 187 insertions, 140 deletions
diff --git a/pynslcd/Makefile.am b/pynslcd/Makefile.am index 5bf4342..51a9429 100644 --- a/pynslcd/Makefile.am +++ b/pynslcd/Makefile.am @@ -1,6 +1,6 @@ # Makefile.am - use automake to generate Makefile.in # -# Copyright (C) 2010, 2011 Arthur de Jong +# Copyright (C) 2010, 2011, 2012 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 @@ -20,6 +20,7 @@ pynslcddir = $(datadir)/pynslcd pynslcd_PYTHON = pynslcd.py cfg.py common.py tio.py mypidfile.py attmap.py \ + expr.py \ alias.py ether.py group.py host.py netgroup.py network.py \ pam.py passwd.py protocol.py rpc.py service.py shadow.py nodist_pynslcd_PYTHON = constants.py config.py diff --git a/pynslcd/attmap.py b/pynslcd/attmap.py index 68d7e93..c35d56f 100644 --- a/pynslcd/attmap.py +++ b/pynslcd/attmap.py @@ -41,154 +41,20 @@ import ldap import re from ldap.filter import escape_filter_chars as escape +from expr import Expression + # exported names __all__ = ('Attributes', ) -# FIXME: support multiple attribute values # TODO: support objectSid attributes -# TODO: do more expression validity checking # regular expression to match function attributes attribute_func_re = re.compile('^(?P<function>[a-z]+)\((?P<attribute>.*)\)$') -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 - - def get_name(self): - """Read a variable name from the value iterator.""" - name = '' - for c in self: - if not c.isalnum(): - self.back() - return name - name += c - return name - - -class DollarExpression(object): - """Class for handling a variable $xxx ${xxx}, ${xxx:-yyy} or ${xxx:+yyy} - expression.""" - - 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 = value.get_name() - c = value.next() - if c == '}': - return - self.op = c + value.next() - self.expr = Expression(value, endat='}') - elif c == '(': - self.name = None - self.op = value.get_name() - c = value.next() - if c != '(': - raise ValueError("Expecting '('") - self.expr = Expression(value, endat=')') - c = value.next() - if c != ')': - raise ValueError("Expecting ')'") - else: - value.back() - self.name = value.get_name() - - 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 '' - elif self.op == 'lower': - return self.expr.value(variables).lower() - elif self.op == 'upper': - return self.expr.value(variables).upper() - return value - - def variables(self, results): - """Add the variables used in the expression to results.""" - if self.name: - 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): - self.expression = value - 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 - - def __str__(self): - return self.expression - - def __repr__(self): - return repr(str(self)) - - class SimpleMapping(str): """Simple mapping to another attribute name.""" @@ -209,6 +75,7 @@ class ExpressionMapping(str): def __init__(self, value): """Parse the expression as a string.""" self.expression = Expression(value) + super(ExpressionMapping, self).__init__(value) def values(self, variables): """Expand the expression using the variables specified.""" @@ -227,6 +94,7 @@ class FunctionMapping(str): m = attribute_func_re.match(mapping) self.attribute = m.group('attribute') self.function = getattr(self, m.group('function')) + super(FunctionMapping, self).__init__(mapping) def upper(self, value): return value.upper() @@ -253,8 +121,8 @@ class Attributes(dict): def __setitem__(self, attribute, mapping): # translate the mapping into a mapping object - if mapping[0] == '"': - mapping = ExpressionMapping(mapping) + if mapping[0] == '"' and mapping[-1] == '"': + mapping = ExpressionMapping(mapping[1:-1]) elif '(' in mapping: mapping = FunctionMapping(mapping) else: diff --git a/pynslcd/cfg.py b/pynslcd/cfg.py index 30d4b02..a5f776d 100644 --- a/pynslcd/cfg.py +++ b/pynslcd/cfg.py @@ -24,6 +24,8 @@ import sys import ldap +from expr import Expression + # the number of threads to start threads = 5 @@ -251,7 +253,6 @@ def read(filename): # pam_authz_search <FILTER> m = re.match('pam_authz_search\s+(?P<value>\S.*)', line, re.IGNORECASE) if m: - from attmap import Expression pam_authz_search.append(Expression(m.group('value'))) # TODO: check pam_authz_search expression to only contain # username, service, ruser, rhost, tty, hostname, fqdn, dn or diff --git a/pynslcd/expr.py b/pynslcd/expr.py new file mode 100644 index 0000000..0f2eb2e --- /dev/null +++ b/pynslcd/expr.py @@ -0,0 +1,177 @@ + +# expr.py - expression handling functions +# +# Copyright (C) 2011, 2012 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 expressions used for LDAP searches. + +>>> expr = Expression('foo=$foo') +>>> expr.value(dict(foo='XX')) +'foo=XX' +>>> expr = Expression('foo=${foo:-$bar}') +>>> expr.value(dict(foo='', bar='YY')) +'foo=YY' +>>> expr.value(dict(bar=['YY', 'ZZ'])) +'foo=YY' +""" + +# exported names +__all__ = ('Expression', ) + + +# TODO: do more expression validity checking + + +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 + try: + return self.value[self.pos - 1] + except IndexError: + return None + + def back(self): + self.pos -= 1 + + def __iter__(self): + return self + + def get_name(self): + """Read a variable name from the value iterator.""" + name = '' + for c in self: + if not c or not c.isalnum(): + self.back() + return name + name += c + return name + + +class DollarExpression(object): + """Class for handling a variable $xxx ${xxx}, ${xxx:-yyy} or ${xxx:+yyy} + expression.""" + + 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 = value.get_name() + c = value.next() + if c == '}': + return + self.op = c + value.next() + self.expr = Expression(value, endat='}') + elif c == '(': + self.name = None + self.op = value.get_name() + c = value.next() + if c != '(': + raise ValueError("Expecting '('") + self.expr = Expression(value, endat=')') + c = value.next() + if c != ')': + raise ValueError("Expecting ')'") + else: + value.back() + self.name = value.get_name() + + def value(self, variables): + """Expand the expression using the variables specified.""" + # lookup the value + value = variables.get(self.name, '') + if value in (None, [], ()): + value = '' + elif isinstance(value, (list, tuple)): + value = value[0] + # TODO: try to return multiple values, one for each value of the list + if self.op == ':-': + return value if value else self.expr.value(variables) + elif self.op == ':+': + return self.expr.value(variables) if value else '' + elif self.op == 'lower': + return self.expr.value(variables).lower() + elif self.op == 'upper': + return self.expr.value(variables).upper() + return value + + def variables(self, results): + """Add the variables used in the expression to results.""" + if self.name: + 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): + self.expression = value + value = MyIter(value) + 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 + + def __str__(self): + return self.expression + + def __repr__(self): + return repr(str(self)) |