summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2012-05-04 20:13:57 +0000
committerArthur de Jong <arthur@arthurdejong.org>2012-05-04 20:13:57 +0000
commitd8c5bb2778c0680d99b5eab0f6e6fa52ab1b6374 (patch)
treed411fad2eaf37ac1a76d63167d1577b807f81a43
parent85e30f2a743ab2ec2a2cccaa55c5cb9f692039b3 (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.am3
-rw-r--r--pynslcd/attmap.py144
-rw-r--r--pynslcd/cfg.py3
-rw-r--r--pynslcd/expr.py177
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))