summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/dn.py262
1 files changed, 159 insertions, 103 deletions
diff --git a/ipalib/dn.py b/ipalib/dn.py
index 47c66198e..1311b6ae0 100644
--- a/ipalib/dn.py
+++ b/ipalib/dn.py
@@ -19,7 +19,6 @@
from ldap.dn import str2dn, dn2str
from ldap import DECODING_ERROR
-from copy import deepcopy
import sys
__all__ = ['AVA', 'RDN', 'DN']
@@ -392,7 +391,7 @@ if container_dn in dn:
# (e.g. cmp()). The general rule is for objects supporting multiple
# values first their lengths are compared, then if the lengths match
# the respective components of each are pair-wise compared until one
-# is discovered to be non-equal
+# is discovered to be non-equal. The comparision is case insensitive.
Concatenation and In-Place Addition:
@@ -412,13 +411,35 @@ manipulate these objects.
'''
+def _adjust_indices(start, end, length):
+ 'helper to fixup start/end slice values'
+
+ if end > length:
+ end = length
+ elif end < 0:
+ end += length
+ if end < 0:
+ end = 0
+
+ if start < 0:
+ start += length
+ if start < 0:
+ start = 0
+
+ return start, end
+
class AVA(object):
'''
+ AVA(arg0, ...)
+
An AVA is an LDAP Attribute Value Assertion. It is convenient to think of
AVA's as a <attr,value> pair. AVA's are members of RDN's (Relative
Distinguished Name).
- The AVA constructor may be invoked with any of the following methods:
+ The AVA constructor is passed a sequence of args and a set of
+ keyword parameters used for configuration.
+
+ The arg sequence may be:
1) With 2 string (or unicode) arguments, the first argument will be the
attr, the 2nd the value.
@@ -454,14 +475,16 @@ class AVA(object):
AVA objects support equality testing and comparsion (e.g. cmp()). When they
are compared the attr is compared first, if the 2 attr's are equal then the
- values are compared.
+ values are compared. The comparision is case insensitive (because attr's map
+ to numeric OID's and their values derive from from the 'name' atribute type
+ (OID 2.5.4.41) whose EQUALITY MATCH RULE is caseIgnoreMatch.
The str method of an AVA returns the string representation in RFC 4514 DN
syntax with proper escaping.
'''
flags = 0
- def __init__(self, *args):
+ def __init__(self, *args, **kwds):
if len(args) == 1:
arg = args[0]
if isinstance(arg, basestring):
@@ -496,42 +519,84 @@ class AVA(object):
if not isinstance(value, basestring):
raise TypeError("value must be basestring, got %s instead" % value.__class__.__name__)
- self.attr = attr.decode('utf-8')
- self.value = value.decode('utf-8')
+ attr = attr.decode('utf-8')
+ value = value.decode('utf-8')
+
+ self._attr = attr
+ self._value = value
+
+ def _get_attr(self):
+ return self._attr
+
+ def _set_attr(self, new_attr):
+ if not isinstance(new_attr, basestring):
+ raise TypeError("attr must be basestring, got %s instead" % new_attr.__class__.__name__)
+
+ self._attr = new_attr
+ attr = property(_get_attr, _set_attr)
+
+ def _get_value(self):
+ return self._value
+
+ def _set_value(self, new_value):
+ if not isinstance(new_value, basestring):
+ raise TypeError("value must be basestring, got %s instead" % new_value.__class__.__name__)
+
+ self._value = new_value
+
+ value = property(_get_value, _set_value)
def _to_openldap(self):
- return [[(self.attr.encode('utf-8'), self.value.encode('utf-8'), self.flags)]]
+ return [[(self._attr.encode('utf-8'), self._value.encode('utf-8'), self.flags)]]
def __str__(self):
return dn2str(self._to_openldap())
def __getitem__(self, key):
if isinstance(key, basestring):
- if key == self.attr:
- return self.value
+ if key == self._attr:
+ return self._value
raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
else:
raise TypeError("unsupported type for AVA indexing, must be basestring; not %s" % \
(key.__class__.__name__))
def __eq__(self, other):
+ '''
+ The attr comparison is case insensitive because attr is
+ really an LDAP attribute type which means it's specified with
+ an OID (dotted number) and not a string. Since OID's are
+ numeric the human readable name which maps to the OID is not
+ significant in case.
+
+ The value comparison is also case insensitive because the all
+ attribute types used in a DN are derived from the 'name'
+ atribute type (OID 2.5.4.41) whose EQUALITY MATCH RULE is
+ caseIgnoreMatch.
+ '''
if not isinstance(other, self.__class__):
raise TypeError("expected AVA but got %s" % (other.__class__.__name__))
- return self.attr == other.attr and self.value == other.value
+ return self._attr.lower() == other.attr.lower() and \
+ self._value.lower() == other.value.lower()
def __cmp__(self, other):
+ 'comparision is case insensitive, see __eq__ doc for explanation'
+
if not isinstance(other, self.__class__):
raise TypeError("expected AVA but got %s" % (other.__class__.__name__))
- result = cmp(self.attr, other.attr)
- if result != 0: return result
- result = cmp(self.value, other.value)
+ result = cmp(self._attr.lower(), other.attr.lower())
+ if result != 0:
+ return result
+ result = cmp(self._value.lower(), other.value.lower())
return result
class RDN(object):
'''
+ RDN(arg0, ..., first_key_match=True)
+
An RDN is a LDAP Relative Distinguished Name. RDN's are members of DN's
(Distinguished Name). An RDN contains 1 or more AVA's. If the RDN contains
more than one AVA it is said to be a multi-valued RDN. When an RDN is
@@ -542,13 +607,12 @@ class RDN(object):
within an RDN). Single valued RDN's are the norm and thus the RDN
constructor has simple syntax for them.
- The RDN constructor may be invoked in a variety of different ways.
+ The RDN constructor is passed a sequence of args and a set of
+ keyword parameters used for configuration.
+
+ The constructor iterates though the sequence and adds AVA's to the RDN.
- * When two adjacent string (or unicode) argument appear together in the
- argument list they are taken to be the <attr,value> pair of an AVA. An AVA
- object is constructed and inserted into the RDN. Multiple pairs of strings
- arguments may appear in the argument list, each pair adds one additional AVA
- to the RDN.
+ The arg sequence may be:
* A 2-valued tuple or list denotes the <attr,value> pair of an AVA. The
first member is the attr and the second member is the value, both members
@@ -562,28 +626,26 @@ class RDN(object):
more AVA <attr,value> pairs. The parsing recognizes the DN syntax escaping
rules.
- Note, a DN syntax argument is distguished from AVA string pairs by testing
- to see if two strings appear adjacent in the argument list, if so those two
- strings are interpretted as an <attr,value> AVA pair and consumed.
+ * A AVA object, the AVA will be copied into the new RDN respecting
+ the constructors keyword configuration parameters.
- * A AVA object. Each AVA object in the argument list will be added to the RDN.
+ * A RDN object, the AVA's in the RDN are copied into the new RDN
+ respecting the constructors keyword configuration parameters.
Single AVA Examples:
- RDN('cn', 'Bob') # 2 adjacent strings yield 1 AVA
RDN(('cn', 'Bob')) # tuple yields 1 AVA
RDN('cn=Bob') # DN syntax with 1 AVA
RDN(AVA('cn', 'Bob')) # AVA object adds 1 AVA
Multiple AVA Examples:
- RDN('cn', 'Bob', 'ou', 'people') # 2 strings pairs yield 2 AVA's
RDN(('cn', 'Bob'),('ou', 'people')) # 2 tuples yields 2 AVA's
RDN('cn=Bob+ou=people') # DN syntax with 2 AVA's
RDN(AVA('cn', 'Bob'),AVA('ou', 'people')) # 2 AVA objects adds 2 AVA's
- RDN('cn', 'Bob', "ou=people') # 3 strings, 1st two strings form 1 AVA
- # 3rd string DN syntax for 1 AVA,
- # adds 2 AVA's in total
+ RDN(('cn', 'Bob'), 'ou=people') # 2 args, 1st tuple forms 1 AVA,
+ # 2nd DN syntax string adds 1 AVA,
+ # 2 AVA's in total
Note: The RHS of a slice assignment is interpreted exactly in the
same manner as the constructor argument list (see above examples).
@@ -613,7 +675,7 @@ class RDN(object):
and value properties return the attr and value properties of the first AVA
in the RDN, for example:
- rdn = RDN('cn', 'Bob') # rdn has 1 AVA whose attr == 'cn' and value == 'Bob'
+ rdn = RDN(('cn', 'Bob')) # rdn has 1 AVA whose attr == 'cn' and value == 'Bob'
len(rdn) -> 1
rdn.attr -> u'cn' # exactly equivalent to rdn[0].attr
rdn.value -> u'Bob' # exactly equivalent to rdn[0].value
@@ -644,14 +706,22 @@ class RDN(object):
flags = 0
- def __init__(self, *args):
- self.first_key_match = True
+ def __init__(self, *args, **kwds):
+ self.first_key_match = kwds.get('first_key_match', True)
self.avas = self._avas_from_sequence(args)
self.avas.sort()
def _ava_from_value(self, value):
if isinstance(value, AVA):
- return deepcopy(value)
+ return AVA(value.attr, value.value)
+ elif isinstance(value, RDN):
+ avas = []
+ for ava in value.avas:
+ avas.append(AVA(ava.attr, ava.value))
+ if len(avas) == 1:
+ return avas[0]
+ else:
+ return avas
elif isinstance(value, basestring):
try:
rdns = str2dn(value.encode('utf-8'))
@@ -679,22 +749,12 @@ class RDN(object):
def _avas_from_sequence(self, seq):
avas = []
- i = 0
- while i < len(seq):
- if i+1 < len(seq) and \
- isinstance(seq[i], basestring) and \
- isinstance(seq[i+1], basestring):
- ava = AVA(seq[i], seq[i+1])
- avas.append(ava)
- i += 2
+ for item in seq:
+ ava = self._ava_from_value(item)
+ if isinstance(ava, list):
+ avas.extend(ava)
else:
- arg = seq[i]
- ava = self._ava_from_value(arg)
- if isinstance(ava, list):
- avas.extend(ava)
- else:
- avas.append(ava)
- i += 1
+ avas.append(ava)
return avas
def _to_openldap(self):
@@ -753,7 +813,8 @@ class RDN(object):
if key == self.avas[i].attr:
found = True
self.avas[i] = new_ava
- if self.first_key_match: break
+ if self.first_key_match:
+ break
i += 1
if not found:
raise KeyError("\"%s\" not found in %s" % (key, self.__str__()))
@@ -805,25 +866,27 @@ class RDN(object):
raise TypeError("expected RDN but got %s" % (other.__class__.__name__))
result = cmp(len(self), len(other))
- if result != 0: return result
+ if result != 0:
+ return result
i = 0
while i < len(self):
result = cmp(self[i], other[i])
- if result != 0: return result
+ if result != 0:
+ return result
i += 1
return 0
def __add__(self, other):
- result = deepcopy(self)
- if isinstance(other, self.__class__):
+ result = RDN(self, first_key_match=self.first_key_match)
+ if isinstance(other, RDN):
for ava in other.avas:
- result.avas.append(deepcopy(ava))
+ result.avas.append(AVA(ava.attr, ava.value))
elif isinstance(other, AVA):
- result.avas.append(deepcopy(other))
+ result.avas.append(AVA(other.attr, other.value))
elif isinstance(other, basestring):
rdn = RDN(other)
for ava in rdn.avas:
- result.avas.append(deepcopy(ava))
+ result.avas.append(AVA(ava.attr, ava.value))
else:
raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__))
@@ -831,15 +894,15 @@ class RDN(object):
return result
def __iadd__(self, other):
- if isinstance(other, self.__class__):
+ if isinstance(other, RDN):
for ava in other.avas:
- self.avas.append(deepcopy(ava))
+ self.avas.append(AVA(ava.attr, ava.value))
elif isinstance(other, AVA):
- self.avas.append(deepcopy(other))
+ self.avas.append(AVA(other.attr, other.value))
elif isinstance(other, basestring):
rdn = RDN(other)
for ava in rdn.avas:
- self.avas.append(deepcopy(ava))
+ self.avas.append(AVA(ava.attr, ava.value))
else:
raise TypeError("expected RDN, AVA or basestring but got %s" % (other.__class__.__name__))
@@ -848,11 +911,17 @@ class RDN(object):
class DN(object):
'''
+ DN(arg0, ..., first_key_match=True)
+
A DN is a LDAP Distinguished Name. A DN is an ordered sequence of RDN's.
- The DN constructor accepts a sequence. The constructor iterates
- through the sequence and adds the RDN's it finds in order to the
- DN object. Each item in the sequence may be:
+ The DN constructor is passed a sequence of args and a set of
+ keyword parameters used for configuration. normalize means the
+ attr and value will be converted to lower case.
+
+ The constructor iterates through the sequence and adds the RDN's
+ it finds in order to the DN object. Each item in the sequence may
+ be:
* A 2-valued tuple or list. The first member is the attr and the
second member is the value of an RDN, both members must be
@@ -866,11 +935,12 @@ class DN(object):
to yield one or more RDN's which will be appended in order to
the DN. The parsing recognizes the DN syntax escaping rules.
- * A RDN object. Each RDN object in the argument list will be
- appended to the DN in order.
+ * A RDN object, the RDN will copied respecting the constructors
+ keyword configuration parameters and appended in order.
- * A DN object. Each DN object in the argument list will append in order
- it's RDN's to the DN.
+ * A DN object, the RDN's in the DN are copied respecting the
+ constructors keyword configuration parameters and appended in
+ order.
Single DN Examples:
@@ -972,17 +1042,18 @@ class DN(object):
flags = 0
- def __init__(self, *args):
+ def __init__(self, *args, **kwds):
+ self.first_key_match = kwds.get('first_key_match', True)
self.first_key_match = True
self.rdns = self._rdns_from_sequence(args)
def _rdn_from_value(self, value):
if isinstance(value, RDN):
- return deepcopy(value)
+ return RDN(value, first_key_match=self.first_key_match)
elif isinstance(value, DN):
rdns = []
for rdn in value.rdns:
- rdns.append(deepcopy(rdn))
+ rdns.append(RDN(rdn, first_key_match=self.first_key_match))
if len(rdns) == 1:
return rdns[0]
else:
@@ -995,7 +1066,7 @@ class DN(object):
avas = []
for ava_tuple in rdn_list:
avas.append(AVA(ava_tuple[0], ava_tuple[1]))
- rdn = RDN(*avas)
+ rdn = RDN(*avas, first_key_match=self.first_key_match)
rdns.append(rdn)
except DECODING_ERROR:
raise ValueError("malformed RDN string = \"%s\"" % value)
@@ -1006,7 +1077,7 @@ class DN(object):
elif isinstance(value, (tuple, list)):
if len(value) != 2:
raise ValueError("tuple or list must be 2-valued, not \"%s\"" % (rdn))
- rdn = RDN(value)
+ rdn = RDN(value, first_key_match=self.first_key_match)
return rdn
else:
raise TypeError("must be str,unicode,tuple, or RDN, got %s instead" % \
@@ -1098,7 +1169,8 @@ class DN(object):
raise TypeError("expected DN but got %s" % (other.__class__.__name__))
result = cmp(len(self), len(other))
- if result != 0: return result
+ if result != 0:
+ return result
return self._cmp_sequence(other, 0, len(self))
def _cmp_sequence(self, pattern, self_start, pat_len):
@@ -1106,35 +1178,36 @@ class DN(object):
pat_idx = 0
while pat_idx < pat_len:
result = cmp(self[self_idx], pattern[pat_idx])
- if result != 0: return result
+ if result != 0:
+ return result
self_idx += 1
pat_idx += 1
return 0
def __add__(self, other):
- result = deepcopy(self)
+ result = DN(self, first_key_match=self.first_key_match)
if isinstance(other, self.__class__):
for rdn in other.rdns:
- result.rdns.append(deepcopy(rdn))
+ result.rdns.append(RDN(rdn, first_key_match=self.first_key_match))
elif isinstance(other, RDN):
- result.rdns.append(deepcopy(other))
+ result.rdns.append(RDN(other, first_key_match=self.first_key_match))
elif isinstance(other, basestring):
- dn = DN(other)
+ dn = DN(other, first_key_match=self.first_key_match)
for rdn in dn.rdns:
- result.rdns.append(deepcopy(rdn))
+ result.rdns.append(rdn)
else:
raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__))
return result
def __iadd__(self, other):
- if isinstance(other, self.__class__):
+ if isinstance(other, DN):
for rdn in other.rdns:
- self.rdns.append(deepcopy(rdn))
+ self.rdns.append(RDN(rdn, first_key_match=self.first_key_match))
elif isinstance(other, RDN):
- self.rdns.append(deepcopy(other))
+ self.rdns.append(RDN(other, first_key_match=self.first_key_match))
elif isinstance(other, basestring):
- dn = DN(other)
+ dn = DN(other, first_key_match=self.first_key_match)
self.__iadd__(dn)
else:
raise TypeError("expected DN, RDN or basestring but got %s" % (other.__class__.__name__))
@@ -1174,23 +1247,6 @@ class DN(object):
return self._tailmatch(suffix, start, end, +1)
- def _adjust_indices(self, start, end, length):
- 'helper to fixup start/end slice values'
-
- if end > length:
- end = length
- elif end < 0:
- end += length
- if end < 0:
- end = 0
-
- if start < 0:
- start += length
- if start < 0:
- start = 0
-
- return start, end
-
def _tailmatch(self, pattern, start, end, direction):
'''
Matches the end (direction >= 0) or start (direction < 0) of self
@@ -1207,11 +1263,11 @@ class DN(object):
self_len = len(self)
- start, end = self._adjust_indices(start, end, self_len)
+ start, end = _adjust_indices(start, end, self_len)
if direction < 0: # starswith
if start+pat_len > self_len:
- return 0;
+ return 0
else: # endswith
if end-start < pat_len or start > self_len:
return 0
@@ -1222,7 +1278,7 @@ class DN(object):
if isinstance(pattern, DN):
if end-start >= pat_len:
return not self._cmp_sequence(pattern, start, pat_len)
- return 0;
+ return 0
else:
return self.rdns[start] == pattern