Merge lp:~lifeless/launchpad/bug-830789 into lp:launchpad

Proposed by Robert Collins
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 13749
Proposed branch: lp:~lifeless/launchpad/bug-830789
Merge into: lp:launchpad
Diff against target: 858 lines (+2/-755)
7 files modified
lib/canonical/launchpad/interfaces/gpghandler.py (+0/-20)
lib/canonical/launchpad/scripts/ftests/test_keyringtrustanalyser.py (+0/-292)
lib/canonical/launchpad/utilities/ftests/test_gpghandler.py (+2/-52)
lib/canonical/launchpad/utilities/gpghandler.py (+0/-36)
lib/lp/registry/scripts/keyringtrustanalyser.py (+0/-183)
scripts/find-email-clusters.py (+0/-101)
scripts/merge-email-clusters.py (+0/-71)
To merge this branch: bzr merge lp:~lifeless/launchpad/bug-830789
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+72371@code.launchpad.net

Commit message

[r=wgrant][bug=830789] Delete unused code.

Description of the change

Unused code can go to code heaven.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

./lib/canonical/launchpad/utilities/gpghandler.py
      27: 'gpgme_editutil' imported but unused
./lib/canonical/launchpad/utilities/ftests/test_gpghandler.py
     191: local variable 'tac' is assigned to but never used
      13: 'gpgme' imported but unused

The first one is yours, the other two might not be.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/gpghandler.py'
2--- lib/canonical/launchpad/interfaces/gpghandler.py 2011-05-30 12:45:29 +0000
3+++ lib/canonical/launchpad/interfaces/gpghandler.py 2011-08-22 01:12:25 +0000
4@@ -199,14 +199,6 @@
5 :return: a `PymeKey` object for the just-generated secret key.
6 """
7
8- def importKeyringFile(filepath):
9- """Import the keyring filepath into the local key database.
10-
11- :param filepath: the path to a keyring to import.
12-
13- :returns: a list of the imported keys.
14- """
15-
16 def encryptContent(content, fingerprint):
17 """Encrypt the given content for the given fingerprint.
18
19@@ -271,15 +263,6 @@
20 :raise AssertionError: if the POST request doesn't succeed.
21 """
22
23- def checkTrustDb():
24- """Check whether the OpenPGP trust database is up to date.
25-
26- The method automatically rebuild the trust values if necessary.
27-
28- The results will be visible in any new retrieved key objects.
29- Existing key objects will not reflect the new trust value.
30- """
31-
32 def localKeys(filter=None, secret=False):
33 """Return an iterator of all keys locally known about.
34
35@@ -340,9 +323,6 @@
36 can_authenticate = Attribute(
37 "Whether the key can be used for authentication")
38
39- def setOwnerTrust(value):
40- """Set the owner_trust value for this key."""
41-
42 def export():
43 """Export the context key in ASCII-armored mode.
44
45
46=== removed file 'lib/canonical/launchpad/scripts/ftests/test_keyringtrustanalyser.py'
47--- lib/canonical/launchpad/scripts/ftests/test_keyringtrustanalyser.py 2011-08-12 11:19:40 +0000
48+++ lib/canonical/launchpad/scripts/ftests/test_keyringtrustanalyser.py 1970-01-01 00:00:00 +0000
49@@ -1,292 +0,0 @@
50-# Copyright 2009 Canonical Ltd. This software is licensed under the
51-# GNU Affero General Public License version 3 (see the file LICENSE).
52-
53-import logging
54-import unittest
55-
56-import gpgme
57-from zope.component import getUtility
58-
59-from canonical.launchpad.ftests import keys_for_tests
60-from canonical.launchpad.interfaces.emailaddress import (
61- EmailAddressStatus,
62- IEmailAddressSet,
63- )
64-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
65-from canonical.testing.layers import LaunchpadZopelessLayer
66-from lp.registry.interfaces.person import IPersonSet
67-from lp.registry.scripts.keyringtrustanalyser import (
68- addOtherKeyring,
69- addTrustedKeyring,
70- findEmailClusters,
71- getValidUids,
72- mergeClusters,
73- )
74-
75-
76-test_fpr = 'A419AE861E88BC9E04B9C26FBA2B9389DFD20543'
77-foobar_fpr = '340CA3BB270E2716C9EE0B768E7EB7086C64A8C5'
78-
79-
80-class LogCollector(logging.Handler):
81-
82- def __init__(self):
83- logging.Handler.__init__(self)
84- self.records = []
85-
86- def emit(self, record):
87- self.records.append(self.format(record))
88-
89-
90-def setupLogger(name='test_keyringtrustanalyser'):
91- """Set up the named logger to collect log messages.
92-
93- Returns (logger, handler)
94- """
95- logger = logging.getLogger(name)
96- for handler in logger.handlers[:]:
97- logger.removeHandler(handler)
98- handler.flush()
99- handler.close()
100- handler = LogCollector()
101- handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
102- logger.addHandler(handler)
103- return logger, handler
104-
105-
106-class TestKeyringTrustAnalyser(unittest.TestCase):
107- layer = LaunchpadZopelessLayer
108-
109- def setUp(self):
110- self.gpg_handler = getUtility(IGPGHandler)
111-
112- def tearDown(self):
113- # XXX stub 2005-10-27: this should be a zope test cleanup
114- # thing per SteveA.
115- self.gpg_handler.resetLocalState()
116-
117- def _addTrustedKeys(self):
118- # Add trusted key with ULTIMATE validity. This will mark UIDs as
119- # valid with a single signature, which is appropriate with the
120- # small amount of test data.
121- filename = keys_for_tests.test_pubkey_file_from_email(
122- 'test@canonical.com')
123- addTrustedKeyring(filename, gpgme.VALIDITY_ULTIMATE)
124-
125- def _addUntrustedKeys(self):
126- for ring in keys_for_tests.test_keyrings():
127- addOtherKeyring(ring)
128-
129- def testAddTrustedKeyring(self):
130- """Test addTrustedKeyring"""
131- self._addTrustedKeys()
132-
133- # get key from keyring
134- keys = [key for key in self.gpg_handler.localKeys()
135- if key.fingerprint == test_fpr]
136- self.assertEqual(len(keys), 1)
137- key = keys[0]
138- self.assertTrue('test@canonical.com' in key.emails)
139- self.assertEqual(key.owner_trust, gpgme.VALIDITY_ULTIMATE)
140-
141- def testAddOtherKeyring(self):
142- """Test addOtherKeyring"""
143- self._addUntrustedKeys()
144- fingerprints = set(key.fingerprint
145- for key in self.gpg_handler.localKeys())
146- self.assertTrue(test_fpr in fingerprints)
147- self.assertTrue(foobar_fpr in fingerprints)
148-
149- def testGetValidUids(self):
150- """Test getValidUids"""
151- self._addTrustedKeys()
152- self._addUntrustedKeys()
153-
154- # calculate valid UIDs
155- validuids = list(getValidUids())
156-
157- # test@canonical.com's non-revoked UIDs are valid
158- self.assertTrue((test_fpr, 'test@canonical.com') in validuids)
159- self.assertTrue((test_fpr,
160- 'sample.person@canonical.com') in validuids)
161- self.assertTrue((test_fpr, 'sample.revoked@canonical.com')
162- not in validuids)
163-
164- # foo.bar@canonical.com's non-revoked signed UIDs are valid
165- self.assertTrue((foobar_fpr, 'foo.bar@canonical.com') in validuids)
166- self.assertTrue((foobar_fpr,
167- 'revoked@canonical.com') not in validuids)
168- self.assertTrue((foobar_fpr, 'untrusted@canonical.com')
169- not in validuids)
170-
171- def testFindEmailClusters(self):
172- """Test findEmailClusters"""
173- self._addTrustedKeys()
174- self._addUntrustedKeys()
175-
176- clusters = list(findEmailClusters())
177-
178- # test@canonical.com is ultimately trusted, so its non-revoked keys
179- # form a cluster
180- self.assertTrue(set(['test@canonical.com',
181- 'sample.person@canonical.com']) in clusters)
182-
183- # foobar has only one signed, non-revoked key
184- self.assertTrue(set(['foo.bar@canonical.com']) in clusters)
185-
186-
187-class TestMergeClusters(unittest.TestCase):
188- """Tests of the mergeClusters() routine."""
189- layer = LaunchpadZopelessLayer
190-
191- def _getEmails(self, person):
192- emailset = getUtility(IEmailAddressSet)
193- return set(address.email for address in emailset.getByPerson(person))
194-
195- def testNullMerge(self):
196- """Test that a merge with an empty sequence of clusters works"""
197- mergeClusters([])
198-
199- def testMergeOneAccountNoNewEmails(self):
200- """Test that merging a single email address does not affect an
201- account.
202- """
203- person = getUtility(IPersonSet).getByEmail('test@canonical.com')
204- emails = self._getEmails(person)
205- self.assertTrue('test@canonical.com' in emails)
206- self.assertEqual(person.merged, None)
207-
208- mergeClusters([set(['test@canonical.com'])])
209- self.assertEqual(person.merged, None)
210- self.assertEqual(self._getEmails(person), emails)
211-
212- def testMergeOneAccountAddEmails(self):
213- """Test that merging a cluster containing new email addresses adds
214- those emails.
215- """
216- personset = getUtility(IPersonSet)
217- emailset = getUtility(IEmailAddressSet)
218-
219- person = personset.getByEmail('test@canonical.com')
220- self.assertEqual(person.merged, None)
221- # make sure newemail doesn't exist
222- self.assertEqual(personset.getByEmail('newemail@canonical.com'), None)
223-
224- mergeClusters([set(['test@canonical.com', 'newemail@canonical.com'])])
225- self.assertEqual(person.merged, None)
226- emails = self._getEmails(person)
227-
228- # both email addresses associated with account ...
229- self.assertTrue('test@canonical.com' in emails)
230- self.assertTrue('newemail@canonical.com' in emails)
231-
232- address = emailset.getByEmail('newemail@canonical.com')
233- self.assertEqual(address.email, 'newemail@canonical.com')
234- self.assertEqual(address.personID, person.id)
235- self.assertEqual(address.status, EmailAddressStatus.NEW)
236-
237- def testMergeUnvalidatedAccountWithValidated(self):
238- """Test merging an unvalidated account with a validated account."""
239- personset = getUtility(IPersonSet)
240-
241- validated_person = personset.getByEmail('test@canonical.com')
242- unvalidated_person = personset.getByEmail(
243- 'matsubara@async.com.br')
244-
245- allemails = self._getEmails(validated_person)
246- allemails.update(self._getEmails(unvalidated_person))
247-
248- self.assertNotEqual(validated_person, unvalidated_person)
249-
250- self.assertNotEqual(validated_person.preferredemail, None)
251- self.assertEqual(unvalidated_person.preferredemail, None)
252-
253- self.assertEqual(validated_person.merged, None)
254- self.assertEqual(unvalidated_person.merged, None)
255-
256- mergeClusters([set(['test@canonical.com',
257- 'matsubara@async.com.br'])])
258-
259- # unvalidated person has been merged into the validated person
260- self.assertEqual(validated_person.merged, None)
261- self.assertEqual(unvalidated_person.merged, validated_person)
262-
263- # all email addresses are now associated with the valid person
264- self.assertEqual(self._getEmails(validated_person), allemails)
265- self.assertEqual(self._getEmails(unvalidated_person), set())
266-
267- def testMergeTwoValidatedAccounts(self):
268- """Test merging of two validated accounts. This should do
269- nothing, since both accounts are in use.
270- """
271- personset = getUtility(IPersonSet)
272-
273- person1 = personset.getByEmail('test@canonical.com')
274- person2 = personset.getByEmail('foo.bar@canonical.com')
275- self.assertNotEqual(person1, person2)
276-
277- self.assertNotEqual(person1.preferredemail, None)
278- self.assertNotEqual(person2.preferredemail, None)
279-
280- self.assertEqual(person1.merged, None)
281- self.assertEqual(person2.merged, None)
282-
283- logger, collector = setupLogger()
284- mergeClusters([set(['test@canonical.com', 'foo.bar@canonical.com'])],
285- logger=logger)
286-
287- self.assertEqual(person1.merged, None)
288- self.assertEqual(person2.merged, None)
289-
290- messages = collector.records
291- self.assertNotEqual(messages, [])
292- self.assertTrue(messages[0].startswith('WARNING:Multiple validated '
293- 'user accounts'))
294-
295- def testMergeTwoUnvalidatedAccounts(self):
296- """Test merging of two unvalidated accounts. This will pick
297- one account and merge the others into it (since none of the
298- accounts have been used, there is no need to favour one over
299- the other).
300- """
301- personset = getUtility(IPersonSet)
302-
303- person1 = personset.getByEmail('matsubara@async.com.br')
304- person2 = personset.getByEmail('martin.pitt@canonical.com')
305-
306- allemails = self._getEmails(person1)
307- allemails.update(self._getEmails(person2))
308-
309- self.assertEqual(person1.preferredemail, None)
310- self.assertEqual(person2.preferredemail, None)
311-
312- self.assertEqual(person1.merged, None)
313- self.assertEqual(person2.merged, None)
314-
315- mergeClusters([set(['matsubara@async.com.br',
316- 'martin.pitt@canonical.com'])])
317-
318- # since we don't know which account will be merged, swap
319- # person1 and person2 if person1 was merged into person2.
320- if person1.merged is not None:
321- person1, person2 = person2, person1
322-
323- # one account is merged into the other
324- self.assertEqual(person1.merged, None)
325- self.assertEqual(person2.merged, person1)
326-
327- self.assertEqual(self._getEmails(person1), allemails)
328- self.assertEqual(self._getEmails(person2), set())
329-
330- def testMergeUnknownEmail(self):
331- """Merging a cluster of unknown emails creates an account."""
332- personset = getUtility(IPersonSet)
333-
334- self.assertEqual(personset.getByEmail('newemail@canonical.com'), None)
335-
336- mergeClusters([set(['newemail@canonical.com'])])
337-
338- person = personset.getByEmail('newemail@canonical.com')
339- self.assertNotEqual(person, None)
340- self.assertEqual(person.preferredemail, None)
341- self.assertTrue('newemail@canonical.com' in self._getEmails(person))
342
343=== modified file 'lib/canonical/launchpad/utilities/ftests/test_gpghandler.py'
344--- lib/canonical/launchpad/utilities/ftests/test_gpghandler.py 2011-08-16 20:35:11 +0000
345+++ lib/canonical/launchpad/utilities/ftests/test_gpghandler.py 2011-08-22 01:12:25 +0000
346@@ -149,54 +149,6 @@
347 """Do we have the expected test keyring files"""
348 self.assertEqual(len(list(keys_for_tests.test_keyrings())), 1)
349
350- def testImportKeyRing(self):
351- """Import a sample keyring and check its contents are available."""
352- self.testEmptyGetKeys()
353- importedkeys = set()
354- for ring in keys_for_tests.test_keyrings():
355- keys = self.gpg_handler.importKeyringFile(ring)
356- importedkeys.update(key.fingerprint for key in keys)
357-
358- # check that expected keys are in importedkeys set
359- self.assertTrue("340CA3BB270E2716C9EE0B768E7EB7086C64A8C5"
360- in importedkeys)
361- self.assertTrue("A419AE861E88BC9E04B9C26FBA2B9389DFD20543"
362- in importedkeys)
363-
364- # check that importedkeys are in key ring
365- keyring = set(key.fingerprint
366- for key in self.gpg_handler.localKeys())
367- self.assertNotEqual(len(keyring), 0)
368- self.assertTrue(importedkeys.issubset(keyring))
369-
370- def testSetOwnerTrust(self):
371- """Import a key and set the ownertrust."""
372- self.testEmptyGetKeys()
373- for email in keys_for_tests.iter_test_key_emails():
374- pubkey = keys_for_tests.test_pubkey_from_email(email)
375- self.gpg_handler.importPublicKey(pubkey)
376-
377- iterator = self.gpg_handler.localKeys()
378- key = iterator.next()
379- self.assertEqual(key.owner_trust, gpgme.VALIDITY_UNKNOWN)
380- key.setOwnerTrust(gpgme.VALIDITY_FULL)
381- self.assertEqual(key.owner_trust, gpgme.VALIDITY_FULL)
382- other_iterator = self.gpg_handler.localKeys()
383- other_key_instance = other_iterator.next()
384- self.assertEqual(key.owner_trust, other_key_instance.owner_trust)
385-
386- def testCheckTrustDb(self):
387- """Test IGPGHandler.checkTrustDb()"""
388- self.testEmptyGetKeys()
389-
390- # check trust DB with no keys succeeds
391- self.assertEqual(self.gpg_handler.checkTrustDb(), 0)
392-
393- # add some keys and check trust DB again
394- for ring in keys_for_tests.test_keyrings():
395- self.gpg_handler.importKeyringFile(ring)
396- self.assertEqual(self.gpg_handler.checkTrustDb(), 0)
397-
398 def testHomeDirectoryJob(self):
399 """Does the job to touch the home work."""
400 gpghandler = getUtility(IGPGHandler)
401@@ -224,9 +176,7 @@
402 def test_retrieveKey_raises_GPGKeyDoesNotExistOnServer(self):
403 # GPGHandler.retrieveKey() raises GPGKeyDoesNotExistOnServer
404 # when called for a key that does not exist on the key server.
405- tac = KeyServerTac()
406- tac.setUp()
407- self.addCleanup(tac.tearDown)
408+ self.useFixture(KeyServerTac())
409 gpghandler = getUtility(IGPGHandler)
410 self.assertRaises(
411 GPGKeyDoesNotExistOnServer, gpghandler.retrieveKey,
412@@ -236,7 +186,7 @@
413 self):
414 # If the keyserver responds too slowly, GPGHandler.retrieveKey()
415 # raises GPGKeyTemporarilyNotFoundError.
416- tac = self.useFixture(KeyServerTac())
417+ self.useFixture(KeyServerTac())
418 old_timeout_function = get_default_timeout_function()
419 set_default_timeout_function(lambda: 0.01)
420 try:
421
422=== modified file 'lib/canonical/launchpad/utilities/gpghandler.py'
423--- lib/canonical/launchpad/utilities/gpghandler.py 2011-06-01 13:32:02 +0000
424+++ lib/canonical/launchpad/utilities/gpghandler.py 2011-08-22 01:12:25 +0000
425@@ -24,7 +24,6 @@
426 import urllib2
427
428 import gpgme
429-from gpgme import editutil as gpgme_editutil
430 from zope.interface import implements
431
432 from canonical.config import config
433@@ -344,18 +343,6 @@
434
435 return key
436
437- def importKeyringFile(self, filepath):
438- """See IGPGHandler.importKeyringFile."""
439- ctx = gpgme.Context()
440- data = open(filepath, 'r')
441- result = ctx.import_(data)
442- # if not considered -> format wasn't recognized
443- # no key was imported
444- if result.considered == 0:
445- raise ValueError('Empty or invalid keyring')
446- return [PymeKey(fingerprint)
447- for (fingerprint, result, status) in result.imports]
448-
449 def encryptContent(self, content, fingerprint):
450 """See IGPGHandler."""
451 if isinstance(content, unicode):
452@@ -541,16 +528,6 @@
453 url = self.getURLForKeyInServer(fingerprint, action)
454 return urlfetch(url)
455
456- def checkTrustDb(self):
457- """See IGPGHandler"""
458- p = subprocess.Popen(['gpg', '--check-trustdb', '--batch', '--yes'],
459- close_fds=True,
460- stdin=subprocess.PIPE,
461- stdout=subprocess.PIPE,
462- stderr=subprocess.STDOUT)
463- p.communicate()
464- return p.returncode
465-
466
467 class PymeSignature(object):
468 """See IPymeSignature."""
469@@ -617,19 +594,6 @@
470 self.emails = [uid.email for uid in self.uids
471 if valid_email(uid.email) and not uid.revoked]
472
473- def setOwnerTrust(self, value):
474- """Set the ownertrust on the actual gpg key"""
475- if value not in (gpgme.VALIDITY_UNDEFINED, gpgme.VALIDITY_NEVER,
476- gpgme.VALIDITY_MARGINAL, gpgme.VALIDITY_FULL,
477- gpgme.VALIDITY_ULTIMATE):
478- raise ValueError("invalid owner trust level")
479- # edit the owner trust value on the key
480- ctx = gpgme.Context()
481- key = ctx.get_key(self.fingerprint.encode('ascii'), False)
482- gpgme_editutil.edit_trust(ctx, key, value)
483- # set the cached copy of owner_trust
484- self.owner_trust = value
485-
486 @property
487 def displayname(self):
488 return '%s%s/%s' % (self.keysize, self.algorithm, self.keyid)
489
490=== removed file 'lib/lp/registry/scripts/keyringtrustanalyser.py'
491--- lib/lp/registry/scripts/keyringtrustanalyser.py 2011-02-23 20:26:53 +0000
492+++ lib/lp/registry/scripts/keyringtrustanalyser.py 1970-01-01 00:00:00 +0000
493@@ -1,183 +0,0 @@
494-# Copyright 2009 Canonical Ltd. This software is licensed under the
495-# GNU Affero General Public License version 3 (see the file LICENSE).
496-
497-import gpgme
498-from zope.component import getUtility
499-
500-from canonical.database.sqlbase import flush_database_updates
501-from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet
502-from canonical.launchpad.interfaces.gpghandler import IGPGHandler
503-from lp.app.validators.email import valid_email
504-from lp.registry.interfaces.person import (
505- IPersonSet,
506- PersonCreationRationale,
507- )
508-
509-
510-__metaclass__ = type
511-
512-__all__ = [
513- 'addTrustedKeyring',
514- 'addOtherKeyring',
515- 'getValidUids',
516- 'findEmailClusters',
517- 'mergeClusters',
518- ]
519-
520-def addTrustedKeyring(filename, ownertrust=gpgme.VALIDITY_MARGINAL):
521- """Add a keyring of keys owned by people trusted to make good signatues.
522- """
523- gpg = getUtility(IGPGHandler)
524- keys = gpg.importKeyringFile(filename)
525- for key in keys:
526- key.setOwnerTrust(ownertrust)
527-
528-def addOtherKeyring(filename):
529- """Add a keyring of possibly suspect keys"""
530- gpg = getUtility(IGPGHandler)
531- gpg.importKeyringFile(filename)
532-
533-def getValidUids(minvalid=gpgme.VALIDITY_MARGINAL):
534- """Returns an iterator yielding (fingerprint, email) pairs.
535-
536- Only UIDs assigned a validity of at least 'minvalid' are returned.
537- """
538- gpg = getUtility(IGPGHandler)
539- gpg.checkTrustDb()
540- for key in gpg.localKeys():
541- for uid in key.uids:
542- if (not uid.revoked and valid_email(uid.email) and
543- uid.validity >= minvalid):
544- yield key.fingerprint, uid.email
545-
546-def findEmailClusters(minvalid=gpgme.VALIDITY_MARGINAL):
547- """Returns an iterator yielding sets of related email addresses.
548-
549- Two email addresses are considered to be related if they appear as
550- valid user IDs on a PGP key in the keyring.
551- """
552- emails = {} # fingerprint -> set(emails)
553- fingerprints = {} # email -> set(fingerprints)
554-
555- # get the valid UIDs
556- for fpr, email in getValidUids(minvalid):
557- fingerprints.setdefault(email, set()).add(fpr)
558- emails.setdefault(fpr, set()).add(email)
559-
560- # find clusters of keys based on the presence of shared valid UIDs
561- clusters = {} # fingerprint -> set(fingerprints)
562- for fprs in fingerprints.itervalues():
563- cluster = fprs.copy()
564- for fpr in fprs:
565- x = clusters.get(fpr)
566- if x is not None:
567- cluster.update(x)
568- for fpr in cluster:
569- clusters[fpr] = cluster
570-
571- # return email addresses belonging to each key cluster
572- for cluster in clusters.itervalues():
573- email_cluster = set()
574- for fpr in cluster:
575- email_cluster.update(emails[fpr])
576- yield email_cluster
577-
578-def _mergeOrAddEmails(personset, emailset, cluster, logger):
579- """Helper function for mergeClusters()
580-
581- The strategy for merging clusters is as follows:
582- 1. Find all Person objects attached to the given email addresses.
583- 2. If there is more than one Person object associated with the cluster,
584- merge them into one (merge into Person with preferred email).
585- 3. If there are no Person objects associated with the cluster, create
586- a new person.
587- 4. For each email address not associated with the person or awaiting
588- validation, add it to the person in state NEW (unvalidated).
589-
590- This algorithm does not handle the case where two accounts have a
591- preferred email. This situation would indicate that users have
592- logged in as both identities, and we don't want to kill accounts
593- for no reason.
594- """
595- # get a list of Person objects associated with this address cluster
596- people = set()
597- for email in cluster:
598- person = personset.getByEmail(email)
599- if person:
600- people.add(person)
601-
602- if len(people) > 1:
603- # more than one Person object => account merge.
604-
605- # Check if any of the accounts have been used.
606- # If one account has been used, we want to merge the others
607- # into that one.
608- # If more than one account has been used, bail.
609-
610- validpeople = set(person for person in people
611- if person.preferredemail is not None)
612- if len(validpeople) > 1:
613- if logger:
614- logger.warning('Multiple validated user accounts found for cluster %r: %s',
615- cluster,
616- ', '.join(['%s (%d)' % (person.name, person.id)
617- for person in validpeople]))
618- return None
619- elif len(validpeople) == 1:
620- person = validpeople.pop()
621- people.remove(person)
622- else:
623- # no validated accounts -- pick one at random
624- person = people.pop()
625-
626- # assign email addresses
627- from zope.security.proxy import removeSecurityProxy
628- for otherperson in people:
629- for email in emailset.getByPerson(otherperson):
630- # EmailAddress.person is a readonly field, so we need to
631- # remove the security proxy here.
632- removeSecurityProxy(email).personID = person.id
633-
634- # merge people
635- for otherperson in people:
636- if logger:
637- logger.info('Merging %s (%d) into %s (%d)',
638- otherperson.name, otherperson.id,
639- person.name, person.id)
640- personset.merge(otherperson, person)
641-
642- elif len(people) == 1:
643- # one person: use that
644- person = people.pop()
645- else:
646- # no person? create it.
647- # We should have the display name from a key here ...
648- person, email = personset.createPersonAndEmail(
649- cluster.pop(), PersonCreationRationale.KEYRINGTRUSTANALYZER)
650-
651- # We now have one person. Now add the missing addresses:
652- existing = set(email.email for email in emailset.getByPerson(person))
653- existing.update(person.unvalidatedemails)
654- for newemail in cluster.difference(existing):
655- if logger:
656- logger.info('Adding email %s to %s (%d)',
657- newemail, person.name, person.id)
658- emailset.new(email=newemail, person=person, account=person.account)
659-
660- flush_database_updates()
661-
662- return person
663-
664-def mergeClusters(clusters, ztm=None, logger=None):
665- """Merge accounts for clusters of addresses.
666-
667- The first argument is an iterator returning sets of email addresses.
668- """
669- personset = getUtility(IPersonSet)
670- emailset = getUtility(IEmailAddressSet)
671- for cluster in clusters:
672- if not cluster: continue
673-
674- if ztm: ztm.begin()
675- _mergeOrAddEmails(personset, emailset, cluster, logger)
676- if ztm: ztm.commit()
677
678=== removed file 'scripts/find-email-clusters.py'
679--- scripts/find-email-clusters.py 2010-04-27 19:48:39 +0000
680+++ scripts/find-email-clusters.py 1970-01-01 00:00:00 +0000
681@@ -1,101 +0,0 @@
682-#!/usr/bin/python -S
683-#
684-# Copyright 2009 Canonical Ltd. This software is licensed under the
685-# GNU Affero General Public License version 3 (see the file LICENSE).
686-
687-# pylint: disable-msg=W0403
688-import _pythonpath
689-
690-import sys
691-import logging
692-import optparse
693-
694-import gpgme
695-
696-from canonical.launchpad.scripts import (
697- execute_zcml_for_scripts, logger_options, logger as logger_from_options)
698-from lp.registry.scripts.keyringtrustanalyser import (
699- addOtherKeyring, addTrustedKeyring, findEmailClusters)
700-
701-
702-validity_map = {
703- 'UNDEFINED': gpgme.VALIDITY_UNDEFINED,
704- 'NEVER': gpgme.VALIDITY_NEVER,
705- 'MARGINAL': gpgme.VALIDITY_MARGINAL,
706- 'FULL': gpgme.VALIDITY_FULL,
707- 'ULTIMATE': gpgme.VALIDITY_ULTIMATE,
708- }
709-
710-
711-def main(argv):
712- parser = optparse.OptionParser(
713- usage="usage: %prog [options] keyrings ...",
714- description="This script inferrs clusters of "
715- "email addresses belonging to a single user "
716- "from the user IDs attached to PGP keys.")
717- parser.add_option('-o', '--output', metavar='FILE', action='store',
718- help='Output clusters to given file',
719- type='string', dest='output', default=None)
720- parser.add_option('--trust', metavar='KEYRING', action='append',
721- help='Trust the owners of keys on this keyring',
722- type='string', dest='trust', default=[])
723- parser.add_option('--owner-trust', metavar='TRUST', action='store',
724- help='What level of trust to assign to trusted keys',
725- type='string', dest='owner_trust', default='ULTIMATE')
726- parser.add_option('--min-valid', metavar='TRUST', action='store',
727- help='Minimum trust necessary for a user ID to '
728- 'be considered valid',
729- type='string', dest='minvalid', default='MARGINAL')
730-
731- logger_options(parser, logging.WARNING)
732-
733- options, args = parser.parse_args(argv[1:])
734-
735- # map validity options
736- if options.owner_trust.upper() not in validity_map:
737- sys.stderr.write('%s: unknown owner trust value %s'
738- % (argv[0], options.owner_trust))
739- return 1
740- options.owner_trust = validity_map[options.owner_trust.upper()]
741-
742- if options.minvalid.upper() not in validity_map:
743- sys.stderr.write('%s: unknown min valid value %s'
744- % (argv[0], options.minvalid))
745- return 1
746- options.minvalid = validity_map[options.minvalid.upper()]
747-
748- # get logger
749- logger = logger_from_options(options)
750-
751- if options.output is not None:
752- logger.debug('openning %s', options.output)
753- fp = open(options.output, 'w')
754- else:
755- fp = sys.stdout
756-
757- logger.info('Setting up utilities')
758- execute_zcml_for_scripts()
759-
760- logger.info('Loading trusted keyrings')
761- for keyring in options.trust:
762- logger.info('Loading %s', keyring)
763- addTrustedKeyring(keyring, options.owner_trust)
764-
765- logger.info('Loading other keyrings')
766- for keyring in args:
767- logger.info('Loading %s', keyring)
768- addOtherKeyring(keyring)
769-
770- logger.info('Computing address clusters')
771- for cluster in findEmailClusters(options.minvalid):
772- for email in cluster:
773- fp.write('%s\n' % email)
774- fp.write('\n')
775-
776- logger.info('Done')
777-
778- return 0
779-
780-
781-if __name__ == '__main__':
782- sys.exit(main(sys.argv))
783
784=== removed file 'scripts/merge-email-clusters.py'
785--- scripts/merge-email-clusters.py 2010-04-27 19:48:39 +0000
786+++ scripts/merge-email-clusters.py 1970-01-01 00:00:00 +0000
787@@ -1,71 +0,0 @@
788-#!/usr/bin/python -S
789-#
790-# Copyright 2009 Canonical Ltd. This software is licensed under the
791-# GNU Affero General Public License version 3 (see the file LICENSE).
792-
793-# pylint: disable-msg=W0403
794-import _pythonpath
795-
796-import sys
797-import logging
798-import optparse
799-
800-from canonical.lp import initZopeless
801-from canonical.launchpad.scripts import (
802- execute_zcml_for_scripts, logger_options, logger as logger_from_options)
803-from lp.registry.scripts.keyringtrustanalyser import mergeClusters
804-
805-
806-def readClusters(fp):
807- """Read clusters of email addresses from the file (separated by blank
808- lines), and yield them as sets."""
809- cluster = set()
810- for line in fp:
811- line = line.strip()
812- if line:
813- cluster.add(line)
814- elif cluster:
815- yield cluster
816- cluster = set()
817- if cluster:
818- yield cluster
819-
820-
821-def main(argv):
822- parser = optparse.OptionParser(
823- description="This script reads a list of email address clusters. "
824- "and updates the Launchpad database to match by adding email "
825- "addresses to existing accounts, merging accounts and "
826- "creating new accounts")
827- parser.add_option('-i', '--input', metavar='FILE', action='store',
828- help='Read clusters from the given file',
829- type='string', dest='input', default=None)
830-
831- logger_options(parser, logging.WARNING)
832-
833- options, args = parser.parse_args(argv[1:])
834-
835- # get logger
836- logger = logger_from_options(options)
837-
838- if options.input is not None:
839- logger.debug('openning %s', options.input)
840- fp = open(options.input, 'r')
841- else:
842- fp = sys.stdin
843-
844- logger.info('Setting up utilities')
845- execute_zcml_for_scripts()
846-
847- logger.info('Connecting to database')
848- ztm = initZopeless()
849-
850- mergeClusters(readClusters(fp), ztm, logger)
851-
852- logger.info('Done')
853-
854- return 0
855-
856-
857-if __name__ == '__main__':
858- sys.exit(main(sys.argv))