Fixes for Sphinx doc generator
This commit is contained in:
469
Lib/ldap/syncrepl.py
Normal file
469
Lib/ldap/syncrepl.py
Normal file
@@ -0,0 +1,469 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.syncrepl - for implementing syncrepl consumer (see RFC 4533)
|
||||
|
||||
See http://www.python-ldap.org/ for project details.
|
||||
|
||||
$Id: syncrepl.py,v 1.3 2012/08/09 07:18:31 stroeder Exp $
|
||||
"""
|
||||
|
||||
#__all__ = [
|
||||
# '',
|
||||
# '',
|
||||
#]
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
# Imports from python-ldap 2.4+
|
||||
import ldap.ldapobject
|
||||
from ldap.controls import RequestControl,ResponseControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import tag,namedtype,namedval,univ,constraint
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
|
||||
__all__ = [ 'SyncreplConsumer' ]
|
||||
|
||||
# RFC 4533:
|
||||
#
|
||||
# syncUUID ::= OCTET STRING (SIZE(16))
|
||||
# syncCookie ::= OCTET STRING
|
||||
|
||||
class syncUUID(univ.OctetString):
|
||||
subtypeSpec = constraint.ValueSizeConstraint(16,16)
|
||||
|
||||
class syncCookie(univ.OctetString):
|
||||
pass
|
||||
|
||||
# 2.2. Sync Request Control
|
||||
#
|
||||
# The Sync Request Control is an LDAP Control [RFC4511] where the
|
||||
# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.1 and the
|
||||
# controlValue, an OCTET STRING, contains a BER-encoded
|
||||
# syncRequestValue. The criticality field is either TRUE or FALSE.
|
||||
#
|
||||
# syncRequestValue ::= SEQUENCE {
|
||||
# mode ENUMERATED {
|
||||
# -- 0 unused
|
||||
# refreshOnly (1),
|
||||
# -- 2 reserved
|
||||
# refreshAndPersist (3)
|
||||
# },
|
||||
# cookie syncCookie OPTIONAL,
|
||||
# reloadHint BOOLEAN DEFAULT FALSE
|
||||
# }
|
||||
#
|
||||
# The Sync Request Control is only applicable to the SearchRequest
|
||||
# Message.
|
||||
|
||||
class syncRequestMode(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
('refreshOnly', 1),
|
||||
('refreshAndPersist', 3)
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1,3)
|
||||
|
||||
class syncRequestValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('mode', syncRequestMode()),
|
||||
namedtype.OptionalNamedType('cookie', syncCookie()),
|
||||
namedtype.DefaultedNamedType('reloadHint', univ.Boolean(False))
|
||||
)
|
||||
|
||||
class SyncRequestControl(RequestControl):
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.1'
|
||||
|
||||
def __init__(self, criticality=1, cookie=None, mode='refreshOnly', reloadHint=False):
|
||||
self.criticality = criticality
|
||||
self.cookie = cookie
|
||||
self.mode = mode
|
||||
self.reloadHint = reloadHint
|
||||
|
||||
def encodeControlValue(self):
|
||||
r = syncRequestValue()
|
||||
r.setComponentByName('mode', syncRequestMode(self.mode))
|
||||
if self.cookie is not None:
|
||||
r.setComponentByName('cookie', syncCookie(self.cookie))
|
||||
if self.reloadHint:
|
||||
r.setComponentbyName('reloadHint', univ.Boolean(self.reloadHint))
|
||||
return encoder.encode(r)
|
||||
|
||||
# 2.3. Sync State Control
|
||||
#
|
||||
# The Sync State Control is an LDAP Control [RFC4511] where the
|
||||
# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.2 and the
|
||||
# controlValue, an OCTET STRING, contains a BER-encoded syncStateValue.
|
||||
# The criticality is FALSE.
|
||||
#
|
||||
# syncStateValue ::= SEQUENCE {
|
||||
# state ENUMERATED {
|
||||
# present (0),
|
||||
# add (1),
|
||||
# modify (2),
|
||||
# delete (3)
|
||||
# },
|
||||
# entryUUID syncUUID,
|
||||
# cookie syncCookie OPTIONAL
|
||||
# }
|
||||
#
|
||||
# The Sync State Control is only applicable to SearchResultEntry and
|
||||
# SearchResultReference Messages.
|
||||
|
||||
class syncStateOp(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
('present', 0),
|
||||
('add', 1),
|
||||
('modify', 2),
|
||||
('delete', 3)
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3)
|
||||
|
||||
class syncStateValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('state', syncStateOp()),
|
||||
namedtype.NamedType('entryUUID', syncUUID()),
|
||||
namedtype.OptionalNamedType('cookie', syncCookie())
|
||||
)
|
||||
|
||||
class SyncStateControl(ResponseControl):
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.2'
|
||||
opnames = ( 'present', 'add', 'modify', 'delete' )
|
||||
|
||||
def decodeControlValue(self, encodedControlValue):
|
||||
d = decoder.decode(encodedControlValue, asn1Spec = syncStateValue())
|
||||
state = d[0].getComponentByName('state')
|
||||
uuid = UUID(bytes=d[0].getComponentByName('entryUUID'))
|
||||
self.cookie = d[0].getComponentByName('cookie')
|
||||
self.state = self.__class__.opnames[int(state)]
|
||||
self.entryUUID = str(uuid)
|
||||
if self.cookie is not None:
|
||||
self.cookie = str(self.cookie)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SyncStateControl.controlType] = SyncStateControl
|
||||
|
||||
# 2.4. Sync Done Control
|
||||
#
|
||||
# The Sync Done Control is an LDAP Control [RFC4511] where the
|
||||
# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.3 and the
|
||||
# controlValue contains a BER-encoded syncDoneValue. The criticality
|
||||
# is FALSE (and hence absent).
|
||||
#
|
||||
# syncDoneValue ::= SEQUENCE {
|
||||
# cookie syncCookie OPTIONAL,
|
||||
# refreshDeletes BOOLEAN DEFAULT FALSE
|
||||
# }
|
||||
#
|
||||
# The Sync Done Control is only applicable to the SearchResultDone
|
||||
# Message.
|
||||
|
||||
class syncDoneValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', syncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False))
|
||||
)
|
||||
|
||||
class SyncDoneControl(ResponseControl):
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.3'
|
||||
|
||||
def decodeControlValue(self, encodedControlValue):
|
||||
d = decoder.decode(encodedControlValue, asn1Spec = syncDoneValue())
|
||||
self.cookie = d[0].getComponentByName('cookie')
|
||||
self.refreshDeletes = d[0].getComponentByName('refreshDeletes')
|
||||
if self.cookie is not None:
|
||||
self.cookie = str(self.cookie)
|
||||
if self.refreshDeletes is not None:
|
||||
self.refreshDeletes = bool(self.refreshDeletes)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SyncDoneControl.controlType] = SyncDoneControl
|
||||
|
||||
|
||||
# 2.5. Sync Info Message
|
||||
#
|
||||
# The Sync Info Message is an LDAP Intermediate Response Message
|
||||
# [RFC4511] where responseName is the object identifier
|
||||
# 1.3.6.1.4.1.4203.1.9.1.4 and responseValue contains a BER-encoded
|
||||
# syncInfoValue. The criticality is FALSE (and hence absent).
|
||||
#
|
||||
# syncInfoValue ::= CHOICE {
|
||||
# newcookie [0] syncCookie,
|
||||
# refreshDelete [1] SEQUENCE {
|
||||
# cookie syncCookie OPTIONAL,
|
||||
# refreshDone BOOLEAN DEFAULT TRUE
|
||||
# },
|
||||
# refreshPresent [2] SEQUENCE {
|
||||
# cookie syncCookie OPTIONAL,
|
||||
# refreshDone BOOLEAN DEFAULT TRUE
|
||||
# },
|
||||
# syncIdSet [3] SEQUENCE {
|
||||
# cookie syncCookie OPTIONAL,
|
||||
# refreshDeletes BOOLEAN DEFAULT FALSE,
|
||||
# syncUUIDs SET OF syncUUID
|
||||
# }
|
||||
# }
|
||||
#
|
||||
|
||||
class refreshDelete(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', syncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True))
|
||||
)
|
||||
|
||||
class refreshPresent(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', syncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True))
|
||||
)
|
||||
|
||||
class syncUUIDs(univ.SetOf):
|
||||
componentType = syncUUID()
|
||||
|
||||
class syncIdSet(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', syncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False)),
|
||||
namedtype.NamedType('syncUUIDs', syncUUIDs())
|
||||
)
|
||||
|
||||
class syncInfoValue(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'newcookie',
|
||||
syncCookie().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'refreshDelete',
|
||||
refreshDelete().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'refreshPresent',
|
||||
refreshPresent().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'syncIdSet',
|
||||
syncIdSet().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
class SyncInfoMessage:
|
||||
responseName = '1.3.6.1.4.1.4203.1.9.1.4'
|
||||
|
||||
def __init__(self, encodedMessage):
|
||||
d = decoder.decode(encodedMessage, asn1Spec = syncInfoValue())
|
||||
self.newcookie = None
|
||||
self.refreshDelete = None
|
||||
self.refreshPresent = None
|
||||
self.syncIdSet = None
|
||||
|
||||
for attr in [ 'newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']:
|
||||
comp = d[0].getComponentByName(attr)
|
||||
|
||||
if comp is not None:
|
||||
|
||||
if attr == 'newcookie':
|
||||
self.newcookie = str(comp)
|
||||
return
|
||||
|
||||
val = dict()
|
||||
|
||||
cookie = comp.getComponentByName('cookie')
|
||||
if cookie is not None:
|
||||
val['cookie'] = str(cookie)
|
||||
|
||||
if attr.startswith('refresh'):
|
||||
val['refreshDone'] = bool(comp.getComponentByName('refreshDone'))
|
||||
elif attr == 'syncIdSet':
|
||||
uuids = []
|
||||
ids = comp.getComponentByName('syncUUIDs')
|
||||
for i in range(len(ids)):
|
||||
uuid = UUID(bytes=str(ids.getComponentByPosition(i)))
|
||||
uuids.append(str(uuid))
|
||||
val['syncUUIDs'] = uuids
|
||||
val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes'))
|
||||
|
||||
setattr(self,attr,val)
|
||||
return
|
||||
|
||||
|
||||
class SyncreplConsumer:
|
||||
"""
|
||||
SyncreplConsumer - LDAP syncrepl consumer object.
|
||||
"""
|
||||
|
||||
def syncrepl_search(self, base, scope, mode='refreshOnly', cookie=None, **search_args):
|
||||
"""
|
||||
Starts syncrepl search operation.
|
||||
|
||||
base, scope, and search_args are passed along to
|
||||
self.search_ext unmodified (aside from adding a Sync
|
||||
Request control to any serverctrls provided).
|
||||
|
||||
mode provides syncrepl mode. Can be 'refreshOnly'
|
||||
to finish after synchronization, or
|
||||
'refreshAndPersist' to persist (continue to
|
||||
receive updates) after synchronization.
|
||||
|
||||
cookie: an opaque value representing the replication
|
||||
state of the client. Subclasses should override
|
||||
the syncrepl_set_cookie() and syncrepl_get_cookie()
|
||||
methods to store the cookie appropriately, rather than
|
||||
passing it.
|
||||
|
||||
"""
|
||||
if cookie is None:
|
||||
cookie = self.syncrepl_get_cookie()
|
||||
|
||||
syncreq = SyncRequestControl(cookie=cookie, mode=mode)
|
||||
|
||||
if 'serverctrls' in search_args:
|
||||
search_args['serverctrls'] += [syncreq]
|
||||
else:
|
||||
search_args['serverctrls'] = [syncreq]
|
||||
|
||||
self.__refreshDone = False
|
||||
return self.search_ext(base, scope, **search_args)
|
||||
|
||||
|
||||
def syncrepl_poll(self, msgid=-1, timeout=None, all=0):
|
||||
"""
|
||||
polls for and processes responses to the syncrepl_search() operation.
|
||||
Returns False when operation finishes, True if it is in progress, or
|
||||
raises an exception on error.
|
||||
|
||||
If timeout is specified, raises ldap.TIMEOUT in the event of a timeout.
|
||||
|
||||
If all is set to a nonzero value, poll() will return only when finished
|
||||
or when an exception is raised.
|
||||
|
||||
"""
|
||||
while True:
|
||||
type, msg, mid, ctrls, n, v = self.result4(
|
||||
msgid=msgid, timeout=timeout,
|
||||
add_intermediates=1, add_ctrls=1, all = 0
|
||||
)
|
||||
|
||||
if type == 101:
|
||||
# search result. This marks the end of a refreshOnly session.
|
||||
# look for a SyncDone control, save the cookie, and if necessary
|
||||
# delete non-present entries.
|
||||
for c in ctrls:
|
||||
if c.__class__.__name__ != 'SyncDoneControl':
|
||||
continue
|
||||
self.syncrepl_present(None,refreshDeletes=c.refreshDeletes)
|
||||
if c.cookie is not None:
|
||||
self.syncrepl_set_cookie(c.cookie)
|
||||
|
||||
return False
|
||||
|
||||
elif type == 100:
|
||||
# search entry with associated SyncState control
|
||||
for m in msg:
|
||||
dn, attrs, ctrls = m
|
||||
for c in ctrls:
|
||||
if c.__class__.__name__ != 'SyncStateControl':
|
||||
continue
|
||||
if c.state == 'present':
|
||||
self.syncrepl_present([c.entryUUID])
|
||||
elif c.state == 'delete':
|
||||
self.syncrepl_delete([c.entryUUID])
|
||||
else:
|
||||
self.syncrepl_entry(dn, attrs, c.entryUUID)
|
||||
if self.__refreshDone is False:
|
||||
self.syncrepl_present([c.entryUUID])
|
||||
if c.cookie is not None:
|
||||
self.syncrepl_set_cookie(c.cookie)
|
||||
break
|
||||
|
||||
elif type == 121:
|
||||
# Intermediate message. If it is a SyncInfoMessage, parse it
|
||||
for m in msg:
|
||||
rname, resp, ctrls = m
|
||||
if rname != SyncInfoMessage.responseName:
|
||||
continue
|
||||
sim = SyncInfoMessage(resp)
|
||||
if sim.newcookie is not None:
|
||||
self.syncrepl_set_cookie(sim.newcookie)
|
||||
elif sim.refreshPresent is not None:
|
||||
self.syncrepl_present(None, refreshDeletes=False)
|
||||
if 'cookie' in sim.refreshPresent:
|
||||
self.syncrepl_set_cookie(sim.refreshPresent['cookie'])
|
||||
self.__refreshDone=sim.refreshPresent['refreshDone']
|
||||
elif sim.refreshDelete is not None:
|
||||
self.syncrepl_present(None, refreshDeletes=True)
|
||||
if 'cookie' in sim.refreshDelete:
|
||||
self.syncrepl_set_cookie(sim.refreshDelete['cookie'])
|
||||
self.__refreshDone=sim.refreshDelete['refreshDone']
|
||||
elif sim.syncIdSet is not None:
|
||||
if sim.syncIdSet['refreshDeletes'] is True:
|
||||
self.syncrepl_delete(sim.syncIdSet['syncUUIDs'])
|
||||
else:
|
||||
self.syncrepl_present(sim.syncIdSet['syncUUIDs'])
|
||||
if 'cookie' in sim.syncIdSet:
|
||||
self.syncrepl_set_cookie(sim.syncIdSet['cookie'])
|
||||
pass
|
||||
|
||||
if all == 0:
|
||||
return True
|
||||
|
||||
|
||||
# virtual methods -- subclass must override these to do useful work
|
||||
|
||||
def syncrepl_set_cookie(self, cookie):
|
||||
"""
|
||||
Called by syncrepl_poll() to store a new cookie provided by the server.
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_get_cookie(self):
|
||||
"""
|
||||
Called by syncrepl_search() to retreive the cookie stored by syncrepl_set_cookie()
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_present(self, uuids, refreshDeletes=False):
|
||||
"""
|
||||
Called by syncrepl_poll() whenever entry UUIDs are presented to the client.
|
||||
syncrepl_present() is given a list of entry UUIDs (uuids) and a flag
|
||||
(refreshDeletes) which indicates whether the server explicitly deleted
|
||||
non-present entries during the refresh operation.
|
||||
|
||||
If called with a list of uuids, the syncrepl_present() implementation
|
||||
should record those uuids as present in the directory.
|
||||
|
||||
If called with uuids set to None and refreshDeletes set to False,
|
||||
syncrepl_present() should delete all non-present entries from the local
|
||||
mirror, and reset the list of recorded uuids.
|
||||
|
||||
If called with uuids set to None and refreshDeletes set to True,
|
||||
syncrepl_present() should reset the list of recorded uuids, without
|
||||
deleting any entries.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_delete(self, uuids):
|
||||
"""
|
||||
Called by syncrepl_poll() to delete entries. A list
|
||||
of UUIDs of the entries to be deleted is given in the
|
||||
uuids parameter.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_entry(self, dn, attrs, uuid):
|
||||
"""
|
||||
Called by syncrepl_poll() for any added or modified entries.
|
||||
|
||||
The provided uuid is used to identify the provided entry in
|
||||
any future modification (including dn modification), deletion,
|
||||
and presentation operations.
|
||||
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user