def _maybeNormalizeUUIDs(self): """ Normalize the UUIDs in the proxy database so they correspond to the normalized UUIDs in the main calendar database. """ alreadyDone = yield self._db_value_for_sql( "select VALUE from CALDAV where KEY = 'UUIDS_NORMALIZED'" ) if alreadyDone is None: for (groupname, member) in ( (yield self._db_all_values_for_sql( "select GROUPNAME, MEMBER from GROUPS")) ): grouplist = groupname.split("#") grouplist[0] = normalizeUUID(grouplist[0]) newGroupName = "#".join(grouplist) newMemberName = normalizeUUID(member) if newGroupName != groupname or newMemberName != member: yield self._db_execute(""" update GROUPS set GROUPNAME = :1, MEMBER = :2 where GROUPNAME = :3 and MEMBER = :4 """, [newGroupName, newMemberName, groupname, member]) yield self._db_execute( """ insert or ignore into CALDAV (KEY, VALUE) values ('UUIDS_NORMALIZED', 'YES') """ )
def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(), fullName=None, firstName=None, lastName=None, emailAddresses=set(), uid=None, password=None, **kwargs): """ Update the record matching guid. In this XML-based implementation, the xml accounts are read in and converted to elementtree elements. The account matching the given guid is replaced, then the document is serialized to disk. """ guid = normalizeUUID(guid) # Make sure latest XML records are read in accounts = self._forceReload() accountsElement = createElement("accounts", realm=self.realmName) for recType in self.recordTypes(): for xmlPrincipal in accounts[recType].itervalues(): if xmlPrincipal.guid == guid: # Replace this record xmlPrincipal.shortNames = shortNames xmlPrincipal.password = password xmlPrincipal.fullName = fullName xmlPrincipal.firstName = firstName xmlPrincipal.lastName = lastName xmlPrincipal.emailAddresses = emailAddresses xmlPrincipal.extras = kwargs self._addElement(accountsElement, xmlPrincipal) else: self._addElement(accountsElement, xmlPrincipal) self._persistRecords(accountsElement) self._forceReload() return self.recordWithGUID(guid)
def recordWithGUID(self, guid): guid = normalizeUUID(guid) for recordType in self.recordTypes(): record = self._lookupInIndex(recordType, self.INDEX_TYPE_GUID, guid) if record is not None: return record return None
def parseXML(self, node): for child in node.getchildren(): if child.tag == ELEMENT_SHORTNAME: self.shortNames.append(child.text.encode("utf-8")) elif child.tag == ELEMENT_GUID: self.guid = normalizeUUID(child.text.encode("utf-8")) if len(self.guid) < 4: self.guid += "?" * (4 - len(self.guid)) elif child.tag == ELEMENT_PASSWORD: self.password = child.text.encode("utf-8") elif child.tag == ELEMENT_NAME: self.fullName = child.text.encode("utf-8") elif child.tag == ELEMENT_FIRST_NAME: self.firstName = child.text.encode("utf-8") elif child.tag == ELEMENT_LAST_NAME: self.lastName = child.text.encode("utf-8") elif child.tag == ELEMENT_EMAIL_ADDRESS: self.emailAddresses.add(child.text.encode("utf-8").lower()) elif child.tag == ELEMENT_MEMBERS: self._parseMembers(child, self.members) elif child.tag == ELEMENT_EXTRAS: self._parseExtras(child, self.extras) else: raise RuntimeError("Unknown account attribute: %s" % (child.tag,)) if not self.shortNames: self.shortNames.append(self.guid)
def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(), fullName=None, firstName=None, lastName=None, emailAddresses=set(), uid=None, password=None, **kwargs): """ Create and persist a record using the provided information. In this XML-based implementation, the xml accounts are read in and converted to elementtree elements, a new element is added for the new record, and the document is serialized to disk. """ if guid is None: guid = str(uuid4()) guid = normalizeUUID(guid) if not shortNames: shortNames = (guid,) # Make sure latest XML records are read in accounts = self._forceReload() accountsElement = createElement("accounts", realm=self.realmName) for recType in self.recordTypes(): for xmlPrincipal in accounts[recType].itervalues(): if xmlPrincipal.guid == guid: raise DirectoryError("Duplicate guid: %s" % (guid,)) for shortName in shortNames: if shortName in xmlPrincipal.shortNames: raise DirectoryError("Duplicate shortName: %s" % (shortName,)) self._addElement(accountsElement, xmlPrincipal) xmlPrincipal = XMLAccountRecord(recordType) xmlPrincipal.shortNames = shortNames xmlPrincipal.guid = guid xmlPrincipal.password = password xmlPrincipal.fullName = fullName xmlPrincipal.firstName = firstName xmlPrincipal.lastName = lastName xmlPrincipal.emailAddresses = emailAddresses xmlPrincipal.extras = kwargs self._addElement(accountsElement, xmlPrincipal) self._persistRecords(accountsElement) self._forceReload() return self.recordWithGUID(guid)
def destroyRecord(self, recordType, guid=None): """ Remove the record matching guid. In this XML-based implementation, the xml accounts are read in and those not matching the given guid are converted to elementtree elements, then the document is serialized to disk. """ guid = normalizeUUID(guid) # Make sure latest XML records are read in accounts = self._forceReload() accountsElement = createElement("accounts", realm=self.realmName) for recType in self.recordTypes(): for xmlPrincipal in accounts[recType].itervalues(): if xmlPrincipal.guid != guid: self._addElement(accountsElement, xmlPrincipal) self._persistRecords(accountsElement) self._forceReload()
def normalizeUUIDs(self): """ Normalize (uppercase) all augment UIDs which are parseable as UUIDs. @return: a L{Deferred} that fires when all records have been normalized. """ remove = [] add = [] for uid in (yield self.getAllUIDs()): nuid = normalizeUUID(uid) if uid != nuid: old = yield self._lookupAugmentRecord(uid) new = copy.deepcopy(old) new.uid = uid.upper() remove.append(uid) add.append(new) try: yield self.removeAugmentRecords(remove) yield self.addAugmentRecords(add) except IOError: # It's OK if we can't re-write the file. pass
def test_normalizeUUID(self): # Ensure that record.guid automatically gets normalized to # uppercase+hyphenated form if the value is one that uuid.UUID( ) # recognizes. data = ( ( "0543A85A-D446-4CF6-80AE-6579FA60957F", "0543A85A-D446-4CF6-80AE-6579FA60957F" ), ( "0543a85a-d446-4cf6-80ae-6579fa60957f", "0543A85A-D446-4CF6-80AE-6579FA60957F" ), ( "0543A85AD4464CF680AE-6579FA60957F", "0543A85A-D446-4CF6-80AE-6579FA60957F" ), ( "0543a85ad4464cf680ae6579fa60957f", "0543A85A-D446-4CF6-80AE-6579FA60957F" ), ( "foo", "foo" ), ( None, None ), ) for original, expected in data: self.assertEquals(expected, normalizeUUID(original)) record = DirectoryRecord(self.service, "users", original, shortNames=("testing",)) self.assertEquals(expected, record.guid)
def recordWithGUID(self, guid): guid = normalizeUUID(guid) return self._lookupRecord(None, CachingDirectoryService.INDEX_TYPE_GUID, guid)
def repeat(self, ctr): """ Create another object like this but with all text items having % substitution done on them with the numeric value provided. @param ctr: an integer to substitute into text. """ # Regular expression which matches ~ followed by a number matchNumber = re.compile(r"(~\d+)") def expand(text, ctr): """ Returns a string where ~<number> is replaced by the first <number> characters from the md5 hexdigest of str(ctr), e.g.:: expand("~9 foo", 1) returns:: "c4ca4238a foo" ...since "c4ca4238a" is the first 9 characters of:: hashlib.md5(str(1)).hexdigest() If <number> is larger than 32, the hash will repeat as needed. """ if text: m = matchNumber.search(text) if m: length = int(m.group(0)[1:]) hash = hashlib.md5(str(ctr)).hexdigest() string = (hash*((length/32)+1))[:-(32-(length%32))] return text.replace(m.group(0), string) return text shortNames = [] for shortName in self.shortNames: if shortName.find("%") != -1: shortNames.append(shortName % ctr) else: shortNames.append(shortName) if self.guid and self.guid.find("%") != -1: guid = self.guid % ctr else: guid = self.guid if self.password.find("%") != -1: password = self.password % ctr else: password = self.password if self.fullName.find("%") != -1: fullName = self.fullName % ctr else: fullName = self.fullName fullName = expand(fullName, ctr) if self.firstName and self.firstName.find("%") != -1: firstName = self.firstName % ctr else: firstName = self.firstName firstName = expand(firstName, ctr) if self.lastName and self.lastName.find("%") != -1: lastName = self.lastName % ctr else: lastName = self.lastName lastName = expand(lastName, ctr) emailAddresses = set() for emailAddr in self.emailAddresses: emailAddr = expand(emailAddr, ctr) if emailAddr.find("%") != -1: emailAddresses.add(emailAddr % ctr) else: emailAddresses.add(emailAddr) result = XMLAccountRecord(self.recordType) result.shortNames = shortNames result.guid = normalizeUUID(guid) result.password = password result.fullName = fullName result.firstName = firstName result.lastName = lastName result.emailAddresses = emailAddresses result.members = self.members result.extras = self.extras return result