Mini Shell
"""
schema.py - support for subSchemaSubEntry information
See https://www.python-ldap.org/ for details.
"""
import sys
import ldap.cidict
from ldap.compat import IterableUserDict
from ldap.schema.tokenizer import split_tokens,extract_tokens
NOT_HUMAN_READABLE_LDAP_SYNTAXES = {
'1.3.6.1.4.1.1466.115.121.1.4', # Audio
'1.3.6.1.4.1.1466.115.121.1.5', # Binary
'1.3.6.1.4.1.1466.115.121.1.8', # Certificate
'1.3.6.1.4.1.1466.115.121.1.9', # Certificate List
'1.3.6.1.4.1.1466.115.121.1.10', # Certificate Pair
'1.3.6.1.4.1.1466.115.121.1.23', # G3 FAX
'1.3.6.1.4.1.1466.115.121.1.28', # JPEG
'1.3.6.1.4.1.1466.115.121.1.40', # Octet String
'1.3.6.1.4.1.1466.115.121.1.49', # Supported Algorithm
}
class SchemaElement:
"""
Base class for all schema element classes. Not used directly!
Arguments:
schema_element_str
String which contains the schema element description to be parsed.
(Bytestrings are decoded using UTF-8)
Class attributes:
schema_attribute
LDAP attribute type containing a certain schema element description
token_defaults
Dictionary internally used by the schema element parser
containing the defaults for certain schema description key-words
"""
token_defaults = {
'DESC':(None,),
}
def __init__(self,schema_element_str=None):
if sys.version_info >= (3, 0) and isinstance(schema_element_str, bytes):
schema_element_str = schema_element_str.decode('utf-8')
if schema_element_str:
l = split_tokens(schema_element_str)
self.set_id(l[1])
d = extract_tokens(l,self.token_defaults)
self._set_attrs(l,d)
def _set_attrs(self,l,d):
self.desc = d['DESC'][0]
return
def set_id(self,element_id):
self.oid = element_id
def get_id(self):
return self.oid
def key_attr(self,key,value,quoted=0):
assert value is None or type(value)==str,TypeError("value has to be of str, was %r" % value)
if value:
if quoted:
return " %s '%s'" % (key,value.replace("'","\\'"))
else:
return " %s %s" % (key,value)
else:
return ""
def key_list(self,key,values,sep=' ',quoted=0):
assert type(values)==tuple,TypeError("values has to be a tuple, was %r" % values)
if not values:
return ''
if quoted:
quoted_values = [ "'%s'" % value.replace("'","\\'") for value in values ]
else:
quoted_values = values
if len(values)==1:
return ' %s %s' % (key,quoted_values[0])
else:
return ' %s ( %s )' % (key,sep.join(quoted_values))
def __str__(self):
result = [str(self.oid)]
result.append(self.key_attr('DESC',self.desc,quoted=1))
return '( %s )' % ''.join(result)
class ObjectClass(SchemaElement):
"""
Arguments:
schema_element_str
String containing an ObjectClassDescription
Class attributes:
oid
OID assigned to the object class
names
All NAMEs of the object class (tuple of strings)
desc
Description text (DESC) of the object class (string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the object class is marked
as OBSOLETE in the schema
must
NAMEs or OIDs of all attributes an entry of the object class must have
(tuple of strings)
may
NAMEs or OIDs of additional attributes an entry of the object class may
have (tuple of strings)
kind
Kind of an object class:
0 = STRUCTURAL,
1 = ABSTRACT,
2 = AUXILIARY
sup
NAMEs or OIDs of object classes this object class is derived from
(tuple of strings)
x_origin
Value of the X-ORIGIN extension flag (tuple of strings)
Although it's not official, X-ORIGIN is used in several LDAP server
implementations to indicate the source of the associated schema
element
"""
schema_attribute = u'objectClasses'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'SUP':(()),
'STRUCTURAL':None,
'AUXILIARY':None,
'ABSTRACT':None,
'MUST':(()),
'MAY':(),
'X-ORIGIN':()
}
def _set_attrs(self,l,d):
self.obsolete = d['OBSOLETE']!=None
self.names = d['NAME']
self.desc = d['DESC'][0]
self.must = d['MUST']
self.may = d['MAY']
self.x_origin = d['X-ORIGIN']
# Default is STRUCTURAL, see RFC2552 or draft-ietf-ldapbis-syntaxes
self.kind = 0
if d['ABSTRACT']!=None:
self.kind = 1
elif d['AUXILIARY']!=None:
self.kind = 2
if self.kind==0 and not d['SUP'] and self.oid!='2.5.6.0':
# STRUCTURAL object classes are sub-classes of 'top' by default
self.sup = ('top',)
else:
self.sup = d['SUP']
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append(self.key_list('SUP',self.sup,sep=' $ '))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append({0:' STRUCTURAL',1:' ABSTRACT',2:' AUXILIARY'}[self.kind])
result.append(self.key_list('MUST',self.must,sep=' $ '))
result.append(self.key_list('MAY',self.may,sep=' $ '))
result.append(self.key_list('X-ORIGIN',self.x_origin,quoted=1))
return '( %s )' % ''.join(result)
AttributeUsage = ldap.cidict.cidict({
'userApplication':0, # work-around for non-compliant schema
'userApplications':0,
'directoryOperation':1,
'distributedOperation':2,
'dSAOperation':3,
})
class AttributeType(SchemaElement):
"""
Arguments:
schema_element_str
String containing an AttributeTypeDescription
Class attributes:
oid
OID assigned to the attribute type (string)
names
All NAMEs of the attribute type (tuple of strings)
desc
Description text (DESC) of the attribute type (string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the attribute type is marked
as OBSOLETE in the schema
single_value
Integer flag (0 or 1) indicating whether the attribute must
have only one value
syntax
OID of the LDAP syntax assigned to the attribute type
no_user_mod
Integer flag (0 or 1) indicating whether the attribute is modifiable
by a client application
equality
NAME or OID of the matching rule used for checking whether attribute values
are equal (string, or None if missing)
substr
NAME or OID of the matching rule used for checking whether an attribute
value contains another value (string, or None if missing)
ordering
NAME or OID of the matching rule used for checking whether attribute values
are lesser-equal than (string, or None if missing)
usage
USAGE of an attribute type:
0 = userApplications
1 = directoryOperation,
2 = distributedOperation,
3 = dSAOperation
sup
NAMEs or OIDs of attribute types this attribute type is derived from
(tuple of strings)
x_origin
Value of the X-ORIGIN extension flag (tuple of strings).
Although it's not official, X-ORIGIN is used in several LDAP server
implementations to indicate the source of the associated schema
element
"""
schema_attribute = u'attributeTypes'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'SUP':(()),
'EQUALITY':(None,),
'ORDERING':(None,),
'SUBSTR':(None,),
'SYNTAX':(None,),
'SINGLE-VALUE':None,
'COLLECTIVE':None,
'NO-USER-MODIFICATION':None,
'USAGE':('userApplications',),
'X-ORIGIN':(),
'X-ORDERED':(None,),
}
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.sup = d['SUP']
self.equality = d['EQUALITY'][0]
self.ordering = d['ORDERING'][0]
self.substr = d['SUBSTR'][0]
self.x_origin = d['X-ORIGIN']
self.x_ordered = d['X-ORDERED'][0]
try:
syntax = d['SYNTAX'][0]
except IndexError:
self.syntax = None
self.syntax_len = None
else:
if syntax is None:
self.syntax = None
self.syntax_len = None
else:
try:
self.syntax,syntax_len = d['SYNTAX'][0].split("{")
except ValueError:
self.syntax = d['SYNTAX'][0]
self.syntax_len = None
for i in l:
if i.startswith("{") and i.endswith("}"):
self.syntax_len = int(i[1:-1])
else:
self.syntax_len = int(syntax_len[:-1])
self.single_value = d['SINGLE-VALUE']!=None
self.collective = d['COLLECTIVE']!=None
self.no_user_mod = d['NO-USER-MODIFICATION']!=None
self.usage = AttributeUsage.get(d['USAGE'][0],0)
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append(self.key_list('SUP',self.sup,sep=' $ '))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_attr('EQUALITY',self.equality))
result.append(self.key_attr('ORDERING',self.ordering))
result.append(self.key_attr('SUBSTR',self.substr))
result.append(self.key_attr('SYNTAX',self.syntax))
if self.syntax_len!=None:
result.append(('{%d}' % (self.syntax_len))*(self.syntax_len>0))
result.append({0:'',1:' SINGLE-VALUE'}[self.single_value])
result.append({0:'',1:' COLLECTIVE'}[self.collective])
result.append({0:'',1:' NO-USER-MODIFICATION'}[self.no_user_mod])
result.append(
{
0:"",
1:" USAGE directoryOperation",
2:" USAGE distributedOperation",
3:" USAGE dSAOperation",
}[self.usage]
)
result.append(self.key_list('X-ORIGIN',self.x_origin,quoted=1))
result.append(self.key_attr('X-ORDERED',self.x_ordered,quoted=1))
return '( %s )' % ''.join(result)
class LDAPSyntax(SchemaElement):
"""
SyntaxDescription
oid
OID assigned to the LDAP syntax
desc
Description text (DESC) of the LDAP syntax (string, or None if missing)
not_human_readable
Integer flag (0 or 1) indicating whether the attribute type is marked
as not human-readable (X-NOT-HUMAN-READABLE)
"""
schema_attribute = u'ldapSyntaxes'
token_defaults = {
'DESC':(None,),
'X-NOT-HUMAN-READABLE':(None,),
'X-BINARY-TRANSFER-REQUIRED':(None,),
'X-SUBST':(None,),
}
def _set_attrs(self,l,d):
self.desc = d['DESC'][0]
self.x_subst = d['X-SUBST'][0]
self.not_human_readable = \
self.oid in NOT_HUMAN_READABLE_LDAP_SYNTAXES or \
d['X-NOT-HUMAN-READABLE'][0]=='TRUE'
self.x_binary_transfer_required = d['X-BINARY-TRANSFER-REQUIRED'][0]=='TRUE'
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append(self.key_attr('X-SUBST',self.x_subst,quoted=1))
result.append(
{0:'',1:" X-NOT-HUMAN-READABLE 'TRUE'"}[self.not_human_readable]
)
return '( %s )' % ''.join(result)
class MatchingRule(SchemaElement):
"""
Arguments:
schema_element_str
String containing an MatchingRuleDescription
Class attributes:
oid
OID assigned to the matching rule
names
All NAMEs of the matching rule (tuple of strings)
desc
Description text (DESC) of the matching rule
obsolete
Integer flag (0 or 1) indicating whether the matching rule is marked
as OBSOLETE in the schema
syntax
OID of the LDAP syntax this matching rule is usable with
(string, or None if missing)
"""
schema_attribute = u'matchingRules'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'SYNTAX':(None,),
}
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.syntax = d['SYNTAX'][0]
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_attr('SYNTAX',self.syntax))
return '( %s )' % ''.join(result)
class MatchingRuleUse(SchemaElement):
"""
Arguments:
schema_element_str
String containing an MatchingRuleUseDescription
Class attributes:
oid
OID of the accompanying matching rule
names
All NAMEs of the matching rule (tuple of strings)
desc
Description text (DESC) of the matching rule (string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the matching rule is marked
as OBSOLETE in the schema
applies
NAMEs or OIDs of attribute types for which this matching rule is used
(tuple of strings)
"""
schema_attribute = u'matchingRuleUse'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'APPLIES':(()),
}
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.applies = d['APPLIES']
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_list('APPLIES',self.applies,sep=' $ '))
return '( %s )' % ''.join(result)
class DITContentRule(SchemaElement):
"""
Arguments:
schema_element_str
String containing an DITContentRuleDescription
Class attributes:
oid
OID of the accompanying structural object class
names
All NAMEs of the DIT content rule (tuple of strings)
desc
Description text (DESC) of the DIT content rule
(string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the DIT content rule is marked
as OBSOLETE in the schema
aux
NAMEs or OIDs of all auxiliary object classes usable in an entry of the
object class (tuple of strings)
must
NAMEs or OIDs of all attributes an entry of the object class must
have, which may extend the list of required attributes of the object
classes of an entry.
(tuple of strings)
may
NAMEs or OIDs of additional attributes an entry of the object class may
have. which may extend the list of optional attributes of the object
classes of an entry.
(tuple of strings)
nots
NAMEs or OIDs of attributes which may not be present in an entry of the
object class. (tuple of strings)
"""
schema_attribute = u'dITContentRules'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'AUX':(()),
'MUST':(()),
'MAY':(()),
'NOT':(()),
}
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.aux = d['AUX']
self.must = d['MUST']
self.may = d['MAY']
self.nots = d['NOT']
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_list('AUX',self.aux,sep=' $ '))
result.append(self.key_list('MUST',self.must,sep=' $ '))
result.append(self.key_list('MAY',self.may,sep=' $ '))
result.append(self.key_list('NOT',self.nots,sep=' $ '))
return '( %s )' % ''.join(result)
class DITStructureRule(SchemaElement):
"""
Arguments:
schema_element_str
String containing an DITStructureRuleDescription
Class attributes:
ruleid
rule ID of the DIT structure rule (only locally unique)
names
All NAMEs of the DIT structure rule (tuple of strings)
desc
Description text (DESC) of the DIT structure rule
(string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the DIT content rule is marked
as OBSOLETE in the schema
form
NAMEs or OIDs of associated name forms (tuple of strings)
sup
NAMEs or OIDs of allowed structural object classes
of superior entries in the DIT (tuple of strings)
"""
schema_attribute = u'dITStructureRules'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'FORM':(None,),
'SUP':(()),
}
def set_id(self,element_id):
self.ruleid = element_id
def get_id(self):
return self.ruleid
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.form = d['FORM'][0]
self.sup = d['SUP']
return
def __str__(self):
result = [str(self.ruleid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_attr('FORM',self.form,quoted=0))
result.append(self.key_list('SUP',self.sup,sep=' $ '))
return '( %s )' % ''.join(result)
class NameForm(SchemaElement):
"""
Arguments:
schema_element_str
String containing an NameFormDescription
Class attributes:
oid
OID of the name form
names
All NAMEs of the name form (tuple of strings)
desc
Description text (DESC) of the name form (string, or None if missing)
obsolete
Integer flag (0 or 1) indicating whether the name form is marked
as OBSOLETE in the schema
form
NAMEs or OIDs of associated name forms (tuple of strings)
oc
NAME or OID of structural object classes this name form
is usable with (string)
must
NAMEs or OIDs of all attributes an RDN must contain (tuple of strings)
may
NAMEs or OIDs of additional attributes an RDN may contain
(tuple of strings)
"""
schema_attribute = u'nameForms'
token_defaults = {
'NAME':(()),
'DESC':(None,),
'OBSOLETE':None,
'OC':(None,),
'MUST':(()),
'MAY':(()),
}
def _set_attrs(self,l,d):
self.names = d['NAME']
self.desc = d['DESC'][0]
self.obsolete = d['OBSOLETE']!=None
self.oc = d['OC'][0]
self.must = d['MUST']
self.may = d['MAY']
return
def __str__(self):
result = [str(self.oid)]
result.append(self.key_list('NAME',self.names,quoted=1))
result.append(self.key_attr('DESC',self.desc,quoted=1))
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
result.append(self.key_attr('OC',self.oc))
result.append(self.key_list('MUST',self.must,sep=' $ '))
result.append(self.key_list('MAY',self.may,sep=' $ '))
return '( %s )' % ''.join(result)
class Entry(IterableUserDict):
"""
Schema-aware implementation of an LDAP entry class.
Mainly it holds the attributes in a string-keyed dictionary with
the OID as key.
"""
def __init__(self,schema,dn,entry):
self._keytuple2attrtype = {}
self._attrtype2keytuple = {}
self._s = schema
self.dn = dn
IterableUserDict.IterableUserDict.__init__(self,{})
self.update(entry)
def _at2key(self,nameoroid):
"""
Return tuple of OID and all sub-types of attribute type specified
in nameoroid.
"""
try:
# Mapping already in cache
return self._attrtype2keytuple[nameoroid]
except KeyError:
# Mapping has to be constructed
oid = self._s.getoid(ldap.schema.AttributeType,nameoroid)
l = nameoroid.lower().split(';')
l[0] = oid
t = tuple(l)
self._attrtype2keytuple[nameoroid] = t
return t
def update(self,dict):
for key, value in dict.values():
self[key] = value
def __contains__(self,nameoroid):
return self._at2key(nameoroid) in self.data
def __getitem__(self,nameoroid):
return self.data[self._at2key(nameoroid)]
def __setitem__(self,nameoroid,attr_values):
k = self._at2key(nameoroid)
self._keytuple2attrtype[k] = nameoroid
self.data[k] = attr_values
def __delitem__(self,nameoroid):
k = self._at2key(nameoroid)
del self.data[k]
del self._attrtype2keytuple[nameoroid]
del self._keytuple2attrtype[k]
def has_key(self,nameoroid):
k = self._at2key(nameoroid)
return k in self.data
def keys(self):
return self._keytuple2attrtype.values()
def items(self):
return [
(k,self[k])
for k in self.keys()
]
def attribute_types(
self,attr_type_filter=None,raise_keyerror=1
):
"""
Convenience wrapper around SubSchema.attribute_types() which
passes object classes of this particular entry as argument to
SubSchema.attribute_types()
"""
return self._s.attribute_types(
self.get('objectClass',[]),attr_type_filter,raise_keyerror
)
Zerion Mini Shell 1.0