Merge lp:~wgrant/launchpad/devel-add-read-ff into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 17956
Proposed branch: lp:~wgrant/launchpad/devel-add-read-ff
Merge into: lp:launchpad
Diff against target: 1185 lines (+420/-236)
20 files modified
database/schema/security.cfg (+8/-0)
lib/lp/registry/browser/tests/test_gpgkey.py (+112/-8)
lib/lp/registry/configure.zcml (+5/-0)
lib/lp/registry/doc/gpgkey.txt (+0/-33)
lib/lp/registry/interfaces/gpg.py (+3/-1)
lib/lp/registry/interfaces/person.py (+2/-2)
lib/lp/registry/model/gpgkey.py (+153/-27)
lib/lp/registry/stories/gpg-coc/gpg-with-gpgservice-ff.txt (+6/-2)
lib/lp/services/gpg/configure.zcml (+1/-1)
lib/lp/services/gpg/handler.py (+6/-131)
lib/lp/services/gpg/interfaces.py (+10/-9)
lib/lp/services/gpg/tests/test_gpghandler.py (+81/-9)
lib/lp/services/verification/browser/tests/test_logintoken.py (+6/-4)
lib/lp/soyuz/tests/test_archive_subscriptions.py (+5/-2)
lib/lp/testing/factory.py (+8/-0)
lib/lp/testing/gpgkeys/__init__.py (+1/-0)
lib/lp/testing/gpgservice/_fixture.py (+7/-4)
lib/lp/testing/gpgservice/tests/test_fixture.py (+4/-3)
setup.py (+1/-0)
versions.cfg (+1/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/devel-add-read-ff
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+289603@code.launchpad.net

Commit message

Add feature flag to read GPG keys from gpgservice.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2016-03-14 19:40:14 +0000
3+++ database/schema/security.cfg 2016-03-21 05:51:58 +0000
4@@ -975,6 +975,7 @@
5 public.livefs = SELECT
6 public.livefsbuild = SELECT, UPDATE
7 public.livefsfile = SELECT
8+public.openididentifier = SELECT
9 public.packageset = SELECT
10 public.packagesetgroup = SELECT
11 public.packagesetinclusion = SELECT
12@@ -1056,6 +1057,7 @@
13 public.job = SELECT, INSERT, UPDATE
14 public.libraryfilealias = SELECT
15 public.libraryfilecontent = SELECT
16+public.openididentifier = SELECT
17 public.packagecopyjob = SELECT
18 public.packageset = SELECT, INSERT
19 public.packagesetgroup = SELECT, INSERT
20@@ -1113,6 +1115,7 @@
21 public.libraryfilecontent = SELECT, INSERT
22 public.message = SELECT, INSERT, UPDATE
23 public.messagechunk = SELECT, INSERT, UPDATE
24+public.openididentifier = SELECT
25 public.packagecopyjob = SELECT, UPDATE
26 public.packagediff = SELECT, INSERT
27 public.packageset = SELECT, INSERT
28@@ -1216,6 +1219,7 @@
29 public.logintoken = SELECT, INSERT, UPDATE
30 public.message = SELECT, INSERT, UPDATE
31 public.milestone = SELECT, INSERT, UPDATE
32+public.openididentifier = SELECT
33 public.packageupload = SELECT, INSERT, UPDATE
34 public.packageuploadbuild = SELECT, INSERT, UPDATE
35 public.packageuploadcustom = SELECT, INSERT, UPDATE
36@@ -1390,6 +1394,7 @@
37 public.messagechunk = SELECT, INSERT
38 public.milestone = SELECT
39 public.milestonetag = SELECT
40+public.openididentifier = SELECT
41 public.packagecopyjob = SELECT, INSERT
42 public.packagediff = SELECT, INSERT, UPDATE, DELETE
43 public.packageset = SELECT
44@@ -1502,6 +1507,7 @@
45 public.messagechunk = SELECT, INSERT
46 public.milestone = SELECT
47 public.milestonetag = SELECT
48+public.openididentifier = SELECT
49 public.packagecopyjob = SELECT, INSERT, UPDATE
50 public.packagediff = SELECT, UPDATE
51 public.packageset = SELECT
52@@ -1804,6 +1810,7 @@
53 public.messagechunk = SELECT, INSERT
54 public.milestone = SELECT
55 public.milestonetag = SELECT, INSERT, DELETE
56+public.openididentifier = SELECT
57 public.packageset = SELECT
58 public.packagesetgroup = SELECT
59 public.packagesetinclusion = SELECT
60@@ -1922,6 +1929,7 @@
61 public.libraryfilecontent = SELECT, INSERT
62 public.message = SELECT, INSERT
63 public.messagechunk = SELECT, INSERT
64+public.openididentifier = SELECT
65 public.person = SELECT
66 public.product = SELECT
67 public.productseries = SELECT
68
69=== modified file 'lib/lp/registry/browser/tests/test_gpgkey.py'
70--- lib/lp/registry/browser/tests/test_gpgkey.py 2016-02-10 00:51:55 +0000
71+++ lib/lp/registry/browser/tests/test_gpgkey.py 2016-03-21 05:51:58 +0000
72@@ -6,28 +6,39 @@
73 __metaclass__ = type
74
75 from testtools.matchers import (
76+ Contains,
77+ Equals,
78+ HasLength,
79 Not,
80 Raises,
81 raises,
82 )
83+from zope.component import getUtility
84
85+from lp.registry.interfaces.person import IPersonSet
86+from lp.registry.interfaces.gpg import IGPGKeySet
87 from lp.services.features.testing import FeatureFixture
88 from lp.services.gpg.interfaces import (
89+ GPG_DATABASE_READONLY_FEATURE_FLAG,
90+ GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG,
91+ GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG,
92+ GPGKeyAlgorithm,
93 GPGReadOnly,
94- GPG_DATABASE_READONLY_FEATURE_FLAG,
95 )
96+from lp.services.verification.interfaces.authtoken import LoginTokenType
97+from lp.services.verification.interfaces.logintoken import ILoginTokenSet
98 from lp.services.webapp import canonical_url
99 from lp.testing import (
100 login_person,
101 TestCaseWithFactory,
102 )
103-from lp.testing.layers import DatabaseFunctionalLayer
104+from lp.testing.layers import LaunchpadFunctionalLayer
105 from lp.testing.views import create_initialized_view
106
107
108 class TestCanonicalUrl(TestCaseWithFactory):
109
110- layer = DatabaseFunctionalLayer
111+ layer = LaunchpadFunctionalLayer
112
113 def test_canonical_url(self):
114 # The canonical URL of a GPG key is ???
115@@ -41,7 +52,7 @@
116
117 class TestPersonGPGView(TestCaseWithFactory):
118
119- layer = DatabaseFunctionalLayer
120+ layer = LaunchpadFunctionalLayer
121
122 def test_edit_pgp_keys_login_redirect(self):
123 """+editpgpkeys should redirect to force you to re-authenticate."""
124@@ -60,8 +71,9 @@
125 }))
126 person = self.factory.makePerson()
127 login_person(person)
128- view = create_initialized_view(person, "+editpgpkeys", principal=person,
129- method='POST', have_fresh_login=True)
130+ view = create_initialized_view(
131+ person, "+editpgpkeys", principal=person, method='POST',
132+ have_fresh_login=True)
133 self.assertThat(view.render, raises(GPGReadOnly))
134
135 def test_gpgkeys_GET_readonly_with_feature_flag_set(self):
136@@ -70,6 +82,98 @@
137 }))
138 person = self.factory.makePerson()
139 login_person(person)
140- view = create_initialized_view(person, "+editpgpkeys", principal=person,
141- method='GET', have_fresh_login=True)
142+ view = create_initialized_view(
143+ person, "+editpgpkeys", principal=person, method='GET',
144+ have_fresh_login=True)
145 self.assertThat(view.render, Not(Raises()))
146+
147+
148+class GPGKeySetTests(TestCaseWithFactory):
149+
150+ layer = LaunchpadFunctionalLayer
151+
152+ def test_can_add_keys_for_test(self):
153+ # IGPGKeySet.new _only_ writes to the launchpad database, so this test
154+ # only works if the read_from_gpgservice FF is *not* set. Once this is
155+ # the default codepath this test should be deleted.
156+ self.useFixture(FeatureFixture({
157+ GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG: None,
158+ }))
159+ keyset = getUtility(IGPGKeySet)
160+ person = self.factory.makePerson()
161+ fingerprint = "DEADBEEF12345678DEADBEEF12345678DEADBEEF"
162+ keyset.new(person.id, "F0A432C2", fingerprint, 4096,
163+ GPGKeyAlgorithm.R, True, True)
164+
165+ keys = keyset.getGPGKeysForPerson(person)
166+
167+ self.assertThat(keys, HasLength(1))
168+ self.assertThat(keys[0].fingerprint, Equals(fingerprint))
169+
170+ def test_sampledata_contains_gpgkeys(self):
171+ keyset = getUtility(IGPGKeySet)
172+ personset = getUtility(IPersonSet)
173+ foobar = personset.getByName('name16')
174+ keys = keyset.getGPGKeysForPerson(foobar)
175+
176+ self.assertThat(keys, HasLength(1))
177+ self.assertThat(keys[0].keyid, Equals('12345678'))
178+ self.assertThat(keys[0].fingerprint,
179+ Equals('ABCDEF0123456789ABCDDCBA0000111112345678'))
180+
181+ def test_can_retrieve_keys_by_fingerprint(self):
182+ keyset = getUtility(IGPGKeySet)
183+ person = self.factory.makePerson()
184+ key = self.factory.makeGPGKey(person)
185+
186+ retrieved_key = keyset.getByFingerprint(key.fingerprint)
187+
188+ self.assertThat(retrieved_key.owner.name, Equals(person.name))
189+ self.assertThat(retrieved_key.fingerprint, Equals(key.fingerprint))
190+
191+ def test_getGPGKeysForPerson_retrieves_active_keys(self):
192+ keyset = getUtility(IGPGKeySet)
193+ person = self.factory.makePerson()
194+ key = self.factory.makeGPGKey(person)
195+
196+ keys = keyset.getGPGKeysForPerson(person)
197+
198+ self.assertThat(keys, HasLength(1))
199+ self.assertThat(keys, Contains(key))
200+
201+ def test_getGPGKeysForPerson_retrieves_inactive_keys(self):
202+ keyset = getUtility(IGPGKeySet)
203+ person = self.factory.makePerson()
204+ key = self.factory.makeGPGKey(person)
205+ keyset.deactivate(key)
206+
207+ active_keys = keyset.getGPGKeysForPerson(person)
208+ inactive_keys = keyset.getGPGKeysForPerson(person, active=False)
209+
210+ self.assertThat(active_keys, HasLength(0))
211+ self.assertThat(inactive_keys, HasLength(1))
212+ self.assertThat(inactive_keys, Contains(key))
213+
214+ def test_getGPGKeysForPerson_excludes_keys_with_logintoken(self):
215+ keyset = getUtility(IGPGKeySet)
216+ email = 'foo@bar.com'
217+ person = self.factory.makePerson(email)
218+ key = self.factory.makeGPGKey(person)
219+ keyset.deactivate(key)
220+ getUtility(ILoginTokenSet).new(
221+ person, email, email, LoginTokenType.VALIDATEGPG, key.fingerprint)
222+
223+ inactive_keys = keyset.getGPGKeysForPerson(person, active=False)
224+ self.assertThat(inactive_keys, HasLength(0))
225+
226+
227+class GPGKeySetWithGPGServiceTests(GPGKeySetTests):
228+
229+ """A copy of GPGKeySetTests, but with gpgservice used."""
230+
231+ def setUp(self):
232+ super(GPGKeySetWithGPGServiceTests, self).setUp()
233+ self.useFixture(FeatureFixture({
234+ GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG: True,
235+ GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG: True,
236+ }))
237
238=== modified file 'lib/lp/registry/configure.zcml'
239--- lib/lp/registry/configure.zcml 2016-02-28 19:13:17 +0000
240+++ lib/lp/registry/configure.zcml 2016-03-21 05:51:58 +0000
241@@ -1284,6 +1284,11 @@
242 permission="launchpad.Edit"
243 set_attributes="active can_encrypt"/>
244 </class>
245+ <class
246+ class="lp.registry.model.gpgkey.GPGServiceKey">
247+ <allow
248+ interface="lp.registry.interfaces.gpg.IGPGKey"/>
249+ </class>
250
251 <!-- GPGKeySet -->
252
253
254=== removed file 'lib/lp/registry/doc/gpgkey.txt'
255--- lib/lp/registry/doc/gpgkey.txt 2016-03-01 12:46:32 +0000
256+++ lib/lp/registry/doc/gpgkey.txt 1970-01-01 00:00:00 +0000
257@@ -1,33 +0,0 @@
258-= GPG Keys =
259-
260-Launchpad models GPG keys in a GPGKey class.
261-
262- >>> from zope.component import getUtility
263- >>> from lp.registry.interfaces.gpg import IGPGKeySet
264- >>> from lp.registry.interfaces.person import IPersonSet
265- >>> from lp.services.gpg.interfaces import GPGKeyAlgorithm
266- >>> personset = getUtility(IPersonSet)
267- >>> foobar = personset.getByName('name16')
268- >>> gpgkeyset = getUtility(IGPGKeySet)
269- >>> keys = gpgkeyset.getGPGKeysForPerson(foobar)
270- >>> [key.keyid for key in keys]
271- [u'12345678']
272-
273-Adding new keys is pretty easy:
274-
275- >>> name12 = personset.getByName('name12')
276- >>> gpgkeyset.new(name12.id, u'DEADBEEF',
277- ... 'DEADBEEF12345678DEADBEEF12345678DEADBEEF', 1024,
278- ... GPGKeyAlgorithm.LITTLE_G)
279- <GPGKey...>
280- >>> gpgkeyset.new(name12.id, u'DEADBEED',
281- ... 'DEADBEED12345678DEADBEED12345678DEADBEED', 2048,
282- ... GPGKeyAlgorithm.G)
283- <GPGKey...>
284-
285-As is pulling it based on the key ID:
286-
287- >>> key = gpgkeyset.getByFingerprint(
288- ... 'DEADBEEF12345678DEADBEEF12345678DEADBEEF')
289- >>> key.owner.name
290- u'name12'
291
292=== modified file 'lib/lp/registry/interfaces/gpg.py'
293--- lib/lp/registry/interfaces/gpg.py 2016-03-14 19:40:14 +0000
294+++ lib/lp/registry/interfaces/gpg.py 2016-03-21 05:51:58 +0000
295@@ -39,7 +39,6 @@
296
297 export_as_webservice_entry('gpg_key')
298
299- id = Int(title=_("Database id"), required=True, readonly=True)
300 keysize = Int(title=_("Keysize"), required=True)
301 algorithm = Choice(title=_("Algorithm"), required=True,
302 vocabulary='GpgAlgorithm')
303@@ -84,6 +83,9 @@
304 inactive ones.
305 """
306
307+ def getOwnerIdForPerson(person):
308+ """return an owner id string suitable for sending to gpgservice."""
309+
310 def getByFingerprints(fingerprints):
311 """Get multiple OpenPGP keys by their fingerprints."""
312
313
314=== modified file 'lib/lp/registry/interfaces/person.py'
315--- lib/lp/registry/interfaces/person.py 2016-01-26 15:47:37 +0000
316+++ lib/lp/registry/interfaces/person.py 2016-03-21 05:51:58 +0000
317@@ -775,10 +775,10 @@
318 inactivesignatures = Attribute("Retrieve own Inactive CoC Signatures.")
319 signedcocs = Attribute("List of Signed Code Of Conduct")
320 gpg_keys = exported(
321- CollectionField(
322+ doNotSnapshot(CollectionField(
323 title=_("List of valid OpenPGP keys ordered by ID"),
324 readonly=False, required=False,
325- value_type=Reference(schema=IGPGKey)))
326+ value_type=Reference(schema=IGPGKey))))
327 pending_gpg_keys = CollectionField(
328 title=_("Set of fingerprints pending confirmation"),
329 readonly=False, required=False,
330
331=== modified file 'lib/lp/registry/model/gpgkey.py'
332--- lib/lp/registry/model/gpgkey.py 2016-03-16 00:32:05 +0000
333+++ lib/lp/registry/model/gpgkey.py 2016-03-21 05:51:58 +0000
334@@ -8,7 +8,6 @@
335 BoolCol,
336 ForeignKey,
337 IntCol,
338- SQLObjectNotFound,
339 StringCol,
340 )
341 from zope.component import getUtility
342@@ -18,6 +17,8 @@
343 IGPGKey,
344 IGPGKeySet,
345 )
346+from lp.registry.interfaces.person import IPersonSet
347+from lp.services.config import config
348 from lp.services.database.enumcol import EnumCol
349 from lp.services.database.interfaces import IStore
350 from lp.services.database.sqlbase import (
351@@ -27,12 +28,14 @@
352 from lp.services.features import getFeatureFlag
353 from lp.services.gpg.interfaces import (
354 GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG,
355+ GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG,
356 GPGKeyAlgorithm,
357 IGPGClient,
358 IGPGHandler,
359 )
360 from lp.services.openid.interfaces.openid import IOpenIDPersistentIdentity
361 from lp.services.openid.model.openididentifier import OpenIdIdentifier
362+from lp.services.verification.interfaces.logintoken import ILoginTokenSet
363
364
365 @implementer(IGPGKey)
366@@ -65,6 +68,58 @@
367 return '%s%s/%s' % (self.keysize, self.algorithm.title, self.keyid)
368
369
370+@implementer(IGPGKey)
371+class GPGServiceKey:
372+
373+ def __init__(self, key_data):
374+ self._key_data = key_data
375+
376+ @property
377+ def active(self):
378+ return self._key_data['enabled']
379+
380+ @property
381+ def keysize(self):
382+ return self._key_data['size']
383+
384+ @property
385+ def algorithm(self):
386+ return GPGKeyAlgorithm.items[self._key_data['algorithm']]
387+
388+ @property
389+ def keyid(self):
390+ return self._key_data['id']
391+
392+ @property
393+ def fingerprint(self):
394+ return self._key_data['fingerprint']
395+
396+ @property
397+ def displayname(self):
398+ return '%s%s/%s' % (self.keysize, self.algorithm.title, self.keyid)
399+
400+ @property
401+ def keyserverURL(self):
402+ return getUtility(
403+ IGPGHandler).getURLForKeyInServer(self.fingerprint, public=True)
404+
405+ @property
406+ def can_encrypt(self):
407+ return self._key_data['can_encrypt']
408+
409+ @property
410+ def owner(self):
411+ return getUtility(IPersonSet).getByOpenIDIdentifier(
412+ self._key_data['owner'])
413+
414+ @property
415+ def ownerID(self):
416+ return self.owner.id
417+
418+ def __eq__(self, other):
419+ return self.fingerprint == other.fingerprint
420+
421+
422 @implementer(IGPGKeySet)
423 class GPGKeySet:
424
425@@ -79,8 +134,13 @@
426 def activate(self, requester, key, can_encrypt):
427 """See `IGPGKeySet`."""
428 fingerprint = key.fingerprint
429- lp_key = self.getByFingerprint(fingerprint)
430+ # XXX: This is a little ugly - we can't use getByFingerprint
431+ # here since if the READ_FROM_GPGSERVICE FF is set we'll get a
432+ # GPGServiceKey object instead of a GPGKey object, and we need
433+ # to change the database representation in all cases.
434+ lp_key = GPGKey.selectOneBy(fingerprint=fingerprint)
435 if lp_key:
436+ assert lp_key.owner == requester
437 is_new = False
438 # Then the key already exists, so let's reactivate it.
439 lp_key.active = True
440@@ -95,50 +155,116 @@
441 ownerID, keyid, fingerprint, keysize, algorithm,
442 can_encrypt=can_encrypt)
443 if getFeatureFlag(GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG):
444+ # XXX: Further to the comment above, if READ_FROM_GPGSERVICE FF is
445+ # set then we need to duplicate the block above but reading from
446+ # the gpgservice instead of the database:
447 client = getUtility(IGPGClient)
448- openid_identifier = self.getOwnerIdForPerson(lp_key.owner)
449- client.addKeyForOwner(openid_identifier, key.fingerprint)
450+ owner_id = self.getOwnerIdForPerson(requester)
451+ # Users with more than one openid identifier may be re-activating
452+ # a key that was previously deactivated with their non-default
453+ # openid identifier. If that's the case, use the same openid
454+ # identifier rather than the default one - this happens even if the
455+ # read FF is not set:
456+ key_data = client.getKeyByFingerprint(fingerprint)
457+ if key_data:
458+ owner_id = key_data['owner']
459+ allowed_owner_ids = self._getAllOwnerIdsForPerson(requester)
460+ assert owner_id in allowed_owner_ids
461+ gpgservice_key = GPGServiceKey(
462+ client.addKeyForOwner(owner_id, key.fingerprint))
463+ if getFeatureFlag(GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG):
464+ is_new = key_data is None
465+ lp_key = gpgservice_key
466 return lp_key, is_new
467
468 def deactivate(self, key):
469- key.active = False
470+ # key could be a GPGServiceKey, which doesn't allow us to set it's
471+ # active attribute. Retrieve it by fingerprint:
472+ lp_key = GPGKey.selectOneBy(fingerprint=key.fingerprint)
473+ lp_key.active = False
474 if getFeatureFlag(GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG):
475+ # Users with more than one openid identifier may be
476+ # deactivating a key that is associated with their
477+ # non-default openid identifier. If that's the case, use
478+ # the same openid identifier rather than the default one:
479 client = getUtility(IGPGClient)
480- openid_identifier = self.getOwnerIdForPerson(key.owner)
481+ key_data = client.getKeyByFingerprint(key.fingerprint)
482+ if not key_data:
483+ # We get here if we're asked to deactivate a key that was never
484+ # activated. This should probably never happen.
485+ return
486+ openid_identifier = key_data['owner']
487 client.disableKeyForOwner(openid_identifier, key.fingerprint)
488
489 def getByFingerprint(self, fingerprint, default=None):
490 """See `IGPGKeySet`"""
491- result = GPGKey.selectOneBy(fingerprint=fingerprint)
492- if result is None:
493- return default
494- return result
495+ if getFeatureFlag(GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG):
496+ key_data = getUtility(IGPGClient).getKeyByFingerprint(fingerprint)
497+ return GPGServiceKey(key_data) if key_data else default
498+ else:
499+ result = GPGKey.selectOneBy(fingerprint=fingerprint)
500+ if result is None:
501+ return default
502+ return result
503
504 def getByFingerprints(self, fingerprints):
505 """See `IGPGKeySet`"""
506- return IStore(GPGKey).find(
507- GPGKey, GPGKey.fingerprint.is_in(fingerprints))
508+ fingerprints = list(fingerprints)
509+ if getFeatureFlag(GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG):
510+ client = getUtility(IGPGClient)
511+ return [
512+ GPGServiceKey(key_data)
513+ for key_data in client.getKeysByFingerprints(fingerprints)]
514+ else:
515+ return list(IStore(GPGKey).find(
516+ GPGKey, GPGKey.fingerprint.is_in(fingerprints)))
517
518 def getGPGKeysForPerson(self, owner, active=True):
519- if active is False:
520- query = """
521- active = false
522- AND fingerprint NOT IN
523- (SELECT fingerprint FROM LoginToken
524- WHERE fingerprint IS NOT NULL
525- AND requester = %s
526- AND date_consumed is NULL
527- )
528- """ % sqlvalues(owner.id)
529+ if getFeatureFlag(GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG):
530+ client = getUtility(IGPGClient)
531+ owner_ids = self._getAllOwnerIdsForPerson(owner)
532+ if not owner_ids:
533+ return []
534+ gpg_keys = []
535+ for owner_id in owner_ids:
536+ key_data_list = client.getKeysForOwner(owner_id)['keys']
537+ gpg_keys.extend([
538+ GPGServiceKey(d) for d in key_data_list
539+ if d['enabled'] == active])
540+ if active is False:
541+ login_tokens = getUtility(ILoginTokenSet).getPendingGPGKeys(
542+ owner.id)
543+ token_fingerprints = [t.fingerprint for t in login_tokens]
544+ return [
545+ k for k in gpg_keys
546+ if k.fingerprint not in token_fingerprints]
547+ return gpg_keys
548 else:
549- query = 'active=true'
550-
551- query += ' AND owner=%s' % sqlvalues(owner.id)
552-
553- return list(GPGKey.select(query, orderBy='id'))
554+ if active is False:
555+ query = """
556+ active = false
557+ AND fingerprint NOT IN
558+ (SELECT fingerprint FROM LoginToken
559+ WHERE fingerprint IS NOT NULL
560+ AND requester = %s
561+ AND date_consumed is NULL
562+ )
563+ """ % sqlvalues(owner.id)
564+ else:
565+ query = 'active=true'
566+ query += ' AND owner=%s' % sqlvalues(owner.id)
567+ return list(GPGKey.select(query, orderBy='id'))
568
569 def getOwnerIdForPerson(self, owner):
570 """See IGPGKeySet."""
571 url = IOpenIDPersistentIdentity(owner).openid_identity_url
572 assert url is not None
573 return url
574+
575+ def _getAllOwnerIdsForPerson(self, owner):
576+ identifiers = IStore(OpenIdIdentifier).find(
577+ OpenIdIdentifier, account=owner.account)
578+ openid_provider_root = config.launchpad.openid_provider_root
579+ return [
580+ openid_provider_root + '+id/' + i.identifier.encode('ascii')
581+ for i in identifiers]
582
583=== modified file 'lib/lp/registry/stories/gpg-coc/gpg-with-gpgservice-ff.txt'
584--- lib/lp/registry/stories/gpg-coc/gpg-with-gpgservice-ff.txt 2016-02-22 23:45:24 +0000
585+++ lib/lp/registry/stories/gpg-coc/gpg-with-gpgservice-ff.txt 2016-03-21 05:51:58 +0000
586@@ -7,9 +7,13 @@
587 tests will be deleted.
588
589 >>> from lp.services.features.testing import FeatureFixture
590- >>> from lp.services.gpg.interfaces import GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG
591+ >>> from lp.services.gpg.interfaces import (
592+ ... GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG,
593+ ... GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG,
594+ ... )
595 >>> feature_fixture = FeatureFixture(
596- ... {GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG: True})
597+ ... {GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG: True,
598+ ... GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG: True})
599 >>> feature_fixture.setUp()
600
601 == Setup ==
602
603=== modified file 'lib/lp/services/gpg/configure.zcml'
604--- lib/lp/services/gpg/configure.zcml 2016-02-18 03:35:43 +0000
605+++ lib/lp/services/gpg/configure.zcml 2016-03-21 05:51:58 +0000
606@@ -24,7 +24,7 @@
607 </class>
608
609 <securedutility
610- class="lp.services.gpg.handler.GPGClient"
611+ class="lp.services.gpg.handler.LPGPGClient"
612 provides="lp.services.gpg.interfaces.IGPGClient">
613 <allow interface="lp.services.gpg.interfaces.IGPGClient" />
614 </securedutility>
615
616=== modified file 'lib/lp/services/gpg/handler.py'
617--- lib/lp/services/gpg/handler.py 2016-03-18 00:48:59 +0000
618+++ lib/lp/services/gpg/handler.py 2016-03-21 05:51:58 +0000
619@@ -11,7 +11,6 @@
620 ]
621
622 import atexit
623-import base64
624 import httplib
625 import os
626 import shutil
627@@ -22,12 +21,10 @@
628 import tempfile
629 import urllib
630 import urllib2
631-from urlparse import urljoin
632
633 import gpgme
634+from gpgservice_client import GPGClient
635 from lazr.restful.utils import get_current_browser_request
636-import requests
637-from requests.status_codes import codes as http_codes
638 from zope.interface import implementer
639
640 from lp.app.validators.email import valid_email
641@@ -39,7 +36,6 @@
642 GPGKeyNotFoundError,
643 GPGKeyRevoked,
644 GPGKeyTemporarilyNotFoundError,
645- GPGServiceException,
646 GPGUploadFailure,
647 GPGVerificationError,
648 IGPGClient,
649@@ -51,7 +47,6 @@
650 SecretGPGKeyImportDetected,
651 valid_fingerprint,
652 )
653-from lp.services.openid.model.openididentifier import OpenIdIdentifier
654 from lp.services.timeline.requesttimeline import get_request_timeline
655 from lp.services.timeout import (
656 TimeoutError,
657@@ -640,132 +635,12 @@
658 return fingerprint
659
660
661-def sanitize_fingerprint_or_raise(fingerprint):
662- """Check the sanity of 'fingerprint'.
663-
664- If 'fingerprint' is a valid fingerprint, the sanitised version will be
665- returned (see sanitize_fingerprint).
666-
667- Otherwise, a ValueError will be raised.
668- """
669- sane_fingerprint = sanitize_fingerprint(fingerprint)
670- if sane_fingerprint is None:
671- raise ValueError("Invalid fingerprint: %r." % fingerprint)
672- return sane_fingerprint
673-
674-
675 @implementer(IGPGClient)
676-class GPGClient:
677+class LPGPGClient(GPGClient):
678 """See IGPGClient."""
679
680- def __init__(self):
681- self.write_hooks = set()
682-
683- def getKeysForOwner(self, owner_id):
684- """See IGPGClient."""
685- path = '/users/%s/keys' % self._encode_owner_id(owner_id)
686- resp = self._request('get', path)
687- if resp.status_code != http_codes['OK']:
688- self.raise_for_error(resp)
689- return resp.json()
690-
691- def addKeyForOwner(self, owner_id, fingerprint):
692- """See IGPGClient."""
693- fingerprint = sanitize_fingerprint_or_raise(fingerprint)
694- path = '/users/%s/keys' % self._encode_owner_id(owner_id)
695- data = dict(fingerprint=fingerprint)
696- resp = self._request('post', path, data)
697- if resp.status_code == http_codes['CREATED']:
698- self._notify_writes()
699- # status 200 (OK) is returned if the key was already enabled:
700- elif resp.status_code != http_codes['OK']:
701- self.raise_for_error(resp)
702-
703- def disableKeyForOwner(self, owner_id, fingerprint):
704- """See IGPGClient."""
705- fingerprint = sanitize_fingerprint_or_raise(fingerprint)
706- path = '/users/%s/keys/%s' % (self._encode_owner_id(owner_id), fingerprint)
707- resp = self._request('delete', path)
708- if resp.status_code == http_codes['OK']:
709- self._notify_writes()
710- else:
711- self.raise_for_error(resp)
712-
713- def getKeyByFingerprint(self, fingerprint):
714- fingerprint = sanitize_fingerprint_or_raise(fingerprint)
715- path = '/keys/%s' % fingerprint
716- resp = self._request('get', path)
717- if resp.status_code == http_codes['OK']:
718- return resp.json()
719- elif resp.status_code == http_codes['NOT_FOUND']:
720- return None
721- else:
722- self.raise_for_error(resp)
723-
724- def registerWriteHook(self, hook_callable):
725- """See IGPGClient."""
726- if not callable(hook_callable):
727- raise TypeError("'hook_callable' parameter must be a callable.")
728- self.write_hooks.add(hook_callable)
729-
730- def unregisterWriteHook(self, hook_callable):
731- """See IGPGClient."""
732- if hook_callable not in self.write_hooks:
733- raise ValueError("%r not registered.")
734- self.write_hooks.remove(hook_callable)
735-
736- def addKeyForTest(self, owner_id, keyid, fingerprint, keysize, algorithm, enabled,
737- can_encrypt):
738- """See IGPGClient."""
739- document = {'keys': [{
740- 'owner': owner_id,
741- 'id': keyid,
742- 'fingerprint': fingerprint,
743- 'size': keysize,
744- 'algorithm': algorithm,
745- 'enabled': enabled,
746- 'can_encrypt': can_encrypt}]}
747- path = '/test/add_keys'
748- resp = self._request('post', path, document)
749- if resp.status_code == http_codes['NOT_FOUND']:
750- raise RuntimeError(
751- "gpgservice was not configured with test endpoints enabled.")
752- elif resp.status_code != http_codes['OK']:
753- self.raise_for_error(resp)
754-
755- def _notify_writes(self):
756- errors = []
757- for hook in self.write_hooks:
758- try:
759- hook()
760- except Exception as e:
761- errors.append(str(e))
762- if errors:
763- raise Exception("The operation succeeded, but one or more write"
764- " hooks failed: %s" % ', '.join(errors))
765-
766- def _encode_owner_id(self, owner_id):
767- return base64.b64encode(owner_id, altchars='-_')
768-
769- def raise_for_error(self, response):
770- """Raise GPGServiceException based on what's in 'response'."""
771- if response.headers['Content-Type'] == 'application/json':
772- message = response.json()['status']
773- else:
774- message = "Unhandled service error. HTTP Status: %d HTTP Body: %s" % (
775- response.status_code, response.content)
776- raise GPGServiceException(message)
777-
778- @property
779- def timeout(self):
780- # Perhaps this should be from config?
781+ def get_endpoint(self):
782+ return "http://{}".format(config.gpgservice.api_endpoint)
783+
784+ def get_timeout(self):
785 return 30.0
786-
787- @property
788- def endpoint(self):
789- return "http://{}".format(config.gpgservice.api_endpoint)
790-
791- def _request(self, method, path, data=None, **kwargs):
792- response = getattr(requests, method)(
793- urljoin(self.endpoint, path), json=data, timeout=self.timeout, **kwargs)
794- return response
795
796=== modified file 'lib/lp/services/gpg/interfaces.py'
797--- lib/lp/services/gpg/interfaces.py 2016-03-16 00:32:46 +0000
798+++ lib/lp/services/gpg/interfaces.py 2016-03-21 05:51:58 +0000
799@@ -3,6 +3,7 @@
800
801 __all__ = [
802 'GPG_DATABASE_READONLY_FEATURE_FLAG',
803+ 'GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG',
804 'GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG',
805 'GPGKeyAlgorithm',
806 'GPGKeyDoesNotExistOnServer',
807@@ -28,6 +29,7 @@
808 import httplib
809 import re
810
811+from gpgservice_client import GPGServiceException
812 from lazr.enum import (
813 DBEnumeratedType,
814 DBItem,
815@@ -52,6 +54,7 @@
816
817 GPG_DATABASE_READONLY_FEATURE_FLAG = u"gpg.database_read_only"
818 GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG = u"gpg.write_to_gpgservice"
819+GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG = u"gpg.read_from_gpgservice"
820
821
822 def valid_fingerprint(fingerprint):
823@@ -425,15 +428,6 @@
824 comment = Attribute("The comment portion of this user ID")
825
826
827-class GPGServiceException(Exception):
828-
829- """Raised when we get an error from the gpgservice.
830-
831- More specific errors for commonly encountered errors may be added once we
832- actually integrate gpgservice with the rest of launchpad.
833- """
834-
835-
836 class IGPGClient(Interface):
837
838 """A client for querying a gpgservice instance."""
839@@ -467,6 +461,13 @@
840 :raises ValueError: if the fingerprint isn't valid.
841 """
842
843+ def getKeysByFingerprints(fingerprints):
844+ """Bulk retrieve GPG keys by a list of fingerprints.
845+
846+ :param fingerprints: A list of fingerprints to retrieve.
847+ :returns: A list of keys that were found.
848+ """
849+
850 def registerWriteHook(hook_callable):
851 """Register a write hook.
852
853
854=== modified file 'lib/lp/services/gpg/tests/test_gpghandler.py'
855--- lib/lp/services/gpg/tests/test_gpghandler.py 2016-03-18 00:48:59 +0000
856+++ lib/lp/services/gpg/tests/test_gpghandler.py 2016-03-21 05:51:58 +0000
857@@ -16,6 +16,11 @@
858 from zope.component import getUtility
859 from zope.security.proxy import removeSecurityProxy
860
861+from lp.registry.interfaces.gpg import IGPGKeySet
862+from lp.registry.interfaces.person import IPersonSet
863+from lp.services.database.constants import THIRTY_DAYS_AGO
864+from lp.services.database.interfaces import IMasterStore
865+from lp.services.features.testing import FeatureFixture
866 from lp.services.gpg.interfaces import (
867 GPGKeyDoesNotExistOnServer,
868 GPGKeyTemporarilyNotFoundError,
869@@ -24,6 +29,7 @@
870 IGPGHandler,
871 )
872 from lp.services.log.logger import BufferLogger
873+from lp.services.openid.model.openididentifier import OpenIdIdentifier
874 from lp.services.timeout import (
875 get_default_timeout_function,
876 set_default_timeout_function,
877@@ -277,7 +283,9 @@
878
879 def test_get_key_for_user_with_sampledata(self):
880 client = getUtility(IGPGClient)
881- data = client.getKeysForOwner('name16_oid')
882+ person = getUtility(IPersonSet).getByName('name16')
883+ openid_id = getUtility(IGPGKeySet).getOwnerIdForPerson(person)
884+ data = client.getKeysForOwner(openid_id)
885 self.assertThat(data, ContainsDict({'keys': HasLength(1)}))
886
887 def test_get_key_for_unknown_user(self):
888@@ -339,7 +347,8 @@
889 def test_adding_invalid_fingerprint_raises_ValueError(self):
890 client = getUtility(IGPGClient)
891 self.assertThat(
892- lambda: client.addKeyForOwner(self.get_random_owner_id_string(), ''),
893+ lambda: client.addKeyForOwner(
894+ self.get_random_owner_id_string(), ''),
895 raises(ValueError("Invalid fingerprint: ''.")))
896
897 def test_adding_duplicate_fingerprint_raises_GPGServiceException(self):
898@@ -351,7 +360,8 @@
899 client.addKeyForOwner(user_one, fingerprint)
900 self.assertThat(
901 lambda: client.addKeyForOwner(user_two, fingerprint),
902- raises(GPGServiceException("Error: Fingerprint already in database.")))
903+ raises(GPGServiceException(
904+ "Error: Fingerprint already in database.")))
905
906 def test_disabling_active_key(self):
907 self.useFixture(KeyServerTac())
908@@ -382,7 +392,8 @@
909 def test_disabling_invalid_fingerprint_raises_ValueError(self):
910 client = getUtility(IGPGClient)
911 self.assertThat(
912- lambda: client.disableKeyForOwner(self.get_random_owner_id_string(), ''),
913+ lambda: client.disableKeyForOwner(
914+ self.get_random_owner_id_string(), ''),
915 raises(ValueError("Invalid fingerprint: ''."))
916 )
917
918@@ -409,15 +420,76 @@
919 raises(ValueError))
920
921 def test_can_add_IGPGKey_to_test_enabled_gpgservice(self):
922+ self.useFixture(
923+ FeatureFixture({'gpg.write_to_gpgservice': True}))
924 client = getUtility(IGPGClient)
925 person = self.factory.makePerson()
926+ # With the feature flag enabled, the following creates a
927+ # gpg key on the gpgservice.
928 gpgkey = self.factory.makeGPGKey(person)
929- user = self.get_random_owner_id_string()
930- client.addKeyForTest(user, gpgkey.keyid, gpgkey.fingerprint,
931- gpgkey.keysize, gpgkey.algorithm.name,
932- gpgkey.active, gpgkey.can_encrypt)
933-
934+ user = getUtility(IGPGKeySet).getOwnerIdForPerson(person)
935 key = client.getKeyByFingerprint(gpgkey.fingerprint)
936 self.assertThat(
937 key, ContainsDict({'owner': Equals(user),
938 'fingerprint': Equals(gpgkey.fingerprint)}))
939+
940+ def makePersonWithMultipleGPGKeysInDifferentOpenids(self):
941+ """Make a person with multiple GPG keys owned by
942+ different openid identifiers. This happens as a result
943+ of an account merge.
944+
945+ :returns: an IPerson instance with two keys under
946+ different openid identifiers.
947+ """
948+ person = self.factory.makePerson()
949+ self.factory.makeGPGKey(person)
950+ # Create a second openid identifier from 30 days ago.
951+ # This simulates the account merge:
952+ identifier = OpenIdIdentifier()
953+ identifier.account = person.account
954+ identifier.identifier = u'openid_identifier'
955+ identifier.date_created = THIRTY_DAYS_AGO
956+ IMasterStore(OpenIdIdentifier).add(identifier)
957+ self.factory.makeGPGKey(person)
958+ return person
959+
960+ def test_can_retrieve_keys_for_all_openid_identifiers(self):
961+ person = self.makePersonWithMultipleGPGKeysInDifferentOpenids()
962+ keys = getUtility(IGPGKeySet).getGPGKeysForPerson(person)
963+ self.assertThat(keys, HasLength(2))
964+
965+ def test_can_deactivate_all_keys_with_multiple_openid_identifiers(self):
966+ person = self.makePersonWithMultipleGPGKeysInDifferentOpenids()
967+ keyset = getUtility(IGPGKeySet)
968+ key_one, key_two = keyset.getGPGKeysForPerson(person)
969+ keyset.deactivate(key_one)
970+ keyset.deactivate(key_two)
971+ key_one, key_two = keyset.getGPGKeysForPerson(person, active=False)
972+
973+ self.assertFalse(key_one.active)
974+ self.assertFalse(key_two.active)
975+
976+ def test_can_reactivate_all_keys_with_multiple_openid_identifiers(self):
977+ person = self.makePersonWithMultipleGPGKeysInDifferentOpenids()
978+ keyset = getUtility(IGPGKeySet)
979+ for k in keyset.getGPGKeysForPerson(person):
980+ keyset.deactivate(k)
981+ for k in keyset.getGPGKeysForPerson(person, active=False):
982+ keyset.activate(person, k, k.can_encrypt)
983+ key_one, key_two = keyset.getGPGKeysForPerson(person)
984+
985+ self.assertTrue(key_one.active)
986+ self.assertTrue(key_two.active)
987+
988+ def test_cannot_reactivate_someone_elses_key(self):
989+ person1 = self.factory.makePerson()
990+ key = self.factory.makeGPGKey(person1)
991+ person2 = self.factory.makePerson()
992+
993+ keyset = getUtility(IGPGKeySet)
994+ keyset.deactivate(key)
995+ self.assertRaises(
996+ AssertionError,
997+ keyset.activate,
998+ person2, key, key.can_encrypt
999+ )
1000
1001=== modified file 'lib/lp/services/verification/browser/tests/test_logintoken.py'
1002--- lib/lp/services/verification/browser/tests/test_logintoken.py 2016-02-10 02:37:07 +0000
1003+++ lib/lp/services/verification/browser/tests/test_logintoken.py 2016-03-21 05:51:58 +0000
1004@@ -21,12 +21,14 @@
1005 from lp.services.verification.interfaces.authtoken import LoginTokenType
1006 from lp.services.verification.interfaces.logintoken import ILoginTokenSet
1007 from lp.testing import (
1008- login_person,
1009 person_logged_in,
1010 TestCaseWithFactory,
1011 )
1012 from lp.testing.deprecated import LaunchpadFormHarness
1013-from lp.testing.layers import DatabaseFunctionalLayer
1014+from lp.testing.layers import (
1015+ DatabaseFunctionalLayer,
1016+ LaunchpadFunctionalLayer,
1017+ )
1018 from lp.testing.views import create_initialized_view
1019
1020
1021@@ -37,7 +39,7 @@
1022 token to be consumed (so it can't be used again) when the user hits
1023 Cancel.
1024 """
1025- layer = DatabaseFunctionalLayer
1026+ layer = LaunchpadFunctionalLayer
1027
1028 def setUp(self):
1029 TestCaseWithFactory.setUp(self)
1030@@ -82,7 +84,7 @@
1031
1032 class LoginTokenReadOnlyTests(TestCaseWithFactory):
1033
1034- layer = DatabaseFunctionalLayer
1035+ layer = LaunchpadFunctionalLayer
1036
1037 def test_continue_action_failed_with_gpg_database_in_ro_mode(self):
1038 self.useFixture(FeatureFixture({
1039
1040=== modified file 'lib/lp/soyuz/tests/test_archive_subscriptions.py'
1041--- lib/lp/soyuz/tests/test_archive_subscriptions.py 2015-09-24 11:30:24 +0000
1042+++ lib/lp/soyuz/tests/test_archive_subscriptions.py 2016-03-21 05:51:58 +0000
1043@@ -21,7 +21,10 @@
1044 StormStatementRecorder,
1045 TestCaseWithFactory,
1046 )
1047-from lp.testing.layers import DatabaseFunctionalLayer
1048+from lp.testing.layers import (
1049+ DatabaseFunctionalLayer,
1050+ LaunchpadFunctionalLayer,
1051+ )
1052 from lp.testing.mail_helpers import pop_notifications
1053 from lp.testing.matchers import HasQueryCount
1054 from lp.testing.pages import (
1055@@ -129,7 +132,7 @@
1056 class PrivateArtifactsViewTestCase(BrowserTestCase):
1057 """ Tests that private team archives can be viewed."""
1058
1059- layer = DatabaseFunctionalLayer
1060+ layer = LaunchpadFunctionalLayer
1061
1062 def setUp(self):
1063 """Create a test archive."""
1064
1065=== modified file 'lib/lp/testing/factory.py'
1066--- lib/lp/testing/factory.py 2016-03-09 01:37:56 +0000
1067+++ lib/lp/testing/factory.py 2016-03-21 05:51:58 +0000
1068@@ -178,6 +178,7 @@
1069 )
1070 from lp.registry.interfaces.distroseriesparent import IDistroSeriesParentSet
1071 from lp.registry.interfaces.gpg import IGPGKeySet
1072+from lp.registry.model.gpgkey import GPGServiceKey
1073 from lp.registry.interfaces.mailinglist import (
1074 IMailingListSet,
1075 MailingListStatus,
1076@@ -233,8 +234,10 @@
1077 from lp.services.database.sqlbase import flush_database_updates
1078 from lp.services.features import getFeatureFlag
1079 from lp.services.gpg.interfaces import (
1080+ GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG,
1081 GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG,
1082 GPGKeyAlgorithm,
1083+ IGPGClient,
1084 IGPGHandler,
1085 )
1086 from lp.services.identity.interfaces.account import (
1087@@ -603,6 +606,11 @@
1088 client.addKeyForTest(
1089 openid_identifier, key.keyid, key.fingerprint, key.keysize,
1090 key.algorithm.name, key.active, key.can_encrypt)
1091+ # Sadly client.addKeyForTest does not return the key that
1092+ # was added:
1093+ if getFeatureFlag(GPG_READ_FROM_GPGSERVICE_FEATURE_FLAG):
1094+ return GPGServiceKey(
1095+ client.getKeyByFingerprint(key.fingerprint))
1096 return key
1097
1098 def makePerson(
1099
1100=== modified file 'lib/lp/testing/gpgkeys/__init__.py'
1101--- lib/lp/testing/gpgkeys/__init__.py 2016-02-24 22:43:07 +0000
1102+++ lib/lp/testing/gpgkeys/__init__.py 2016-03-21 05:51:58 +0000
1103@@ -31,6 +31,7 @@
1104 from lp.services.gpg.interfaces import (
1105 GPG_WRITE_TO_GPGSERVICE_FEATURE_FLAG,
1106 GPGKeyAlgorithm,
1107+ IGPGClient,
1108 IGPGHandler,
1109 )
1110
1111
1112=== modified file 'lib/lp/testing/gpgservice/_fixture.py'
1113--- lib/lp/testing/gpgservice/_fixture.py 2016-02-16 05:36:37 +0000
1114+++ lib/lp/testing/gpgservice/_fixture.py 2016-03-21 05:51:58 +0000
1115@@ -36,8 +36,8 @@
1116
1117 def setUp(self):
1118 super(GPGKeyServiceFixture, self).setUp()
1119- # Write service config to a file on disk. This file gets deleted when the
1120- # fixture ends.
1121+ # Write service config to a file on disk. This file gets deleted
1122+ # when the fixture ends.
1123 service_config = _get_default_service_config()
1124 self._config_file = NamedTemporaryFile()
1125 self.addCleanup(self._config_file.close)
1126@@ -94,12 +94,15 @@
1127 raise RuntimeError("Service not responding: %r" % errors)
1128
1129 def reset_service_database(self):
1130- """Reset the gpgservice instance database to the launchpad sampledata."""
1131+ """Reset the gpgservice instance database to the launchpad sampledata.
1132+ """
1133 conn = httplib.HTTPConnection(self.bind_address)
1134 test_data = {
1135 'keys': [
1136 {
1137- 'owner': 'name16_oid',
1138+ 'owner':
1139+ config.launchpad.openid_provider_root
1140+ + '+id/name16_oid',
1141 'id': '12345678',
1142 'fingerprint': 'ABCDEF0123456789ABCDDCBA0000111112345678',
1143 'size': 1024,
1144
1145=== modified file 'lib/lp/testing/gpgservice/tests/test_fixture.py'
1146--- lib/lp/testing/gpgservice/tests/test_fixture.py 2016-03-16 20:41:49 +0000
1147+++ lib/lp/testing/gpgservice/tests/test_fixture.py 2016-03-21 05:51:58 +0000
1148@@ -47,9 +47,10 @@
1149 def test_fixture_can_create_test_data(self):
1150 fixture = self.useFixture(GPGKeyServiceFixture())
1151 conn = httplib.HTTPConnection(fixture.bind_address)
1152- conn.request(
1153- 'GET',
1154- '/users/%s/keys' % base64.b64encode('name16_oid', altchars='-_'))
1155+ user = base64.b64encode(
1156+ config.launchpad.openid_provider_root + '+id/name16_oid',
1157+ altchars='-_')
1158+ conn.request('GET', '/users/%s/keys' % user)
1159 resp = conn.getresponse()
1160 self.assertEqual(200, resp.status)
1161 data = json.loads(resp.read())
1162
1163=== modified file 'setup.py'
1164--- setup.py 2016-02-05 20:28:29 +0000
1165+++ setup.py 2016-03-21 05:51:58 +0000
1166@@ -41,6 +41,7 @@
1167 'FeedParser',
1168 'feedvalidator',
1169 'funkload',
1170+ 'gpgservice-client',
1171 'html5browser',
1172 'httmock',
1173 'pygpgme',
1174
1175=== modified file 'versions.cfg'
1176--- versions.cfg 2016-03-14 02:01:04 +0000
1177+++ versions.cfg 2016-03-21 05:51:58 +0000
1178@@ -39,6 +39,7 @@
1179 FormEncode = 1.2.4
1180 funkload = 1.16.1
1181 gpgservice = 0.1.2
1182+gpgservice-client = 0.0.2
1183 grokcore.component = 1.6
1184 gunicorn = 19.4.5
1185 html5browser = 0.0.9