Merge lp:~michael.nelson/launchpad/db-598464-priv-xmlrpc-access-getOrCreate into lp:launchpad/db-devel

Proposed by Michael Nelson
Status: Merged
Approved by: Michael Nelson
Approved revision: no longer in the source branch.
Merged at revision: 9526
Proposed branch: lp:~michael.nelson/launchpad/db-598464-priv-xmlrpc-access-getOrCreate
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~michael.nelson/launchpad/db-598464-get-or-create-from-identity
Diff against target: 323 lines (+229/-1)
8 files modified
lib/canonical/launchpad/interfaces/launchpad.py (+3/-0)
lib/canonical/launchpad/xmlrpc/application.py (+6/-0)
lib/canonical/launchpad/xmlrpc/configure.zcml (+3/-0)
lib/canonical/launchpad/xmlrpc/faults.py (+11/-0)
lib/lp/registry/configure.zcml (+10/-0)
lib/lp/registry/interfaces/person.py (+24/-1)
lib/lp/registry/tests/test_xmlrpc.py (+125/-0)
lib/lp/registry/xmlrpc/softwarecenteragent.py (+47/-0)
To merge this branch: bzr merge lp:~michael.nelson/launchpad/db-598464-priv-xmlrpc-access-getOrCreate
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+29059@code.launchpad.net

Description of the change

Overview
========
This branch is related to bug 598464 and follows on from the MP at:

https://code.edge.launchpad.net/~michael.nelson/launchpad/db-598464-get-or-create-from-identity/+merge/28885

It aims to enable the software-center-agent access to the IPersonSet.getOrCreateByOpenIDIdentifier() method via private XML-RPC. It was decided against using the API to expose something similar for the agent only, due to the fact that exposing this functionality for the agent is not a long-term solution (and therefore something belonging on the API). Long term, it should be possible to create subscriptions to private PPAs without requiring ubuntu users to have Launchpad accounts.

Issue
=====
Initially I'd tried restricting the rpc call to the agent only, but then realised the reason that I couldn't find any examples in the code for other private xmlrpc services was that it's unnecessary (given that the private service is only accessible internally).

To test:
bin/test -vvm test_xmlrpc

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

(13:09:31) adeuring: noodles775: again, a nice branch. But I think it is a bit odd to have a class PersonSetAPIView (ie.e.,a quite generic name) where the method getorCreateByOpenIDIdentifier() unconditionally providesthe rationale "software centerpurchase" as a account creation rationale. Shouldn't we either make the class name more specific or provide the rationale as a parameter of the method?
(13:10:47) adeuring: (or use a method name like getOrCreateForSoftwarePurchase())
(13:45:12) noodles775: adeuring: yes - I thought the same when I pushed the last rev. So I'd be keen to change the application name... most of the other private xmlrpc apps have names like 'ICodehostingApplication' identifying who uses them.
(13:46:42) noodles775: So perhaps, ISoftwareCenterAgentAPI, http://..../softwarecenteragent etc.?
(13:47:07) adeuring: noodles775: ok, so something like s/Person/Shopper/ ?
(13:48:29) noodles775: The app name usually matches the API, so perhaps ISoftwareCenterAgentApplication...
(13:50:06) adeuring: noodles775: ah, right. so, that, combined with a method name like getOrCreateShopCustomer()?
(13:50:20) noodles775: Sounds good.

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/launchpad.py'
2--- lib/canonical/launchpad/interfaces/launchpad.py 2010-06-22 17:08:23 +0000
3+++ lib/canonical/launchpad/interfaces/launchpad.py 2010-07-02 12:20:58 +0000
4@@ -296,6 +296,9 @@
5
6 bugs = Attribute("""Launchpad Bugs XML-RPC end point.""")
7
8+ softwarecenteragent = Attribute(
9+ """Software center agent XML-RPC end point.""")
10+
11
12 class IAuthServerApplication(ILaunchpadApplication):
13 """Launchpad legacy AuthServer application root."""
14
15=== modified file 'lib/canonical/launchpad/xmlrpc/application.py'
16--- lib/canonical/launchpad/xmlrpc/application.py 2010-04-19 06:35:23 +0000
17+++ lib/canonical/launchpad/xmlrpc/application.py 2010-07-02 12:20:58 +0000
18@@ -27,6 +27,7 @@
19 from lp.code.interfaces.codehosting import ICodehostingApplication
20 from lp.code.interfaces.codeimportscheduler import (
21 ICodeImportSchedulerApplication)
22+from lp.registry.interfaces.person import ISoftwareCenterAgentApplication
23 from canonical.launchpad.webapp import LaunchpadXMLRPCView
24
25
26@@ -58,6 +59,11 @@
27 """See `IPrivateApplication`."""
28 return getUtility(IPrivateMaloneApplication)
29
30+ @property
31+ def softwarecenteragent(self):
32+ """See `IPrivateApplication`."""
33+ return getUtility(ISoftwareCenterAgentApplication)
34+
35
36 class ISelfTest(Interface):
37 """XMLRPC external interface for testing the XMLRPC external interface."""
38
39=== modified file 'lib/canonical/launchpad/xmlrpc/configure.zcml'
40--- lib/canonical/launchpad/xmlrpc/configure.zcml 2010-04-19 23:35:41 +0000
41+++ lib/canonical/launchpad/xmlrpc/configure.zcml 2010-07-02 12:20:58 +0000
42@@ -204,4 +204,7 @@
43 <require like_class="xmlrpclib.Fault" />
44 </class>
45
46+ <class class="canonical.launchpad.xmlrpc.faults.AccountSuspended">
47+ <require like_class="xmlrpclib.Fault" />
48+ </class>
49 </configure>
50
51=== modified file 'lib/canonical/launchpad/xmlrpc/faults.py'
52--- lib/canonical/launchpad/xmlrpc/faults.py 2010-04-09 12:58:01 +0000
53+++ lib/canonical/launchpad/xmlrpc/faults.py 2010-07-02 12:20:58 +0000
54@@ -450,3 +450,14 @@
55
56 def __init__(self, job_id):
57 LaunchpadFault.__init__(self, job_id=job_id)
58+
59+
60+class AccountSuspended(LaunchpadFault):
61+ """Raised by `ISoftwareCenterAgentAPI` when an account is suspended."""
62+
63+ error_code = 370
64+ msg_template = ('The openid_identifier \'%(openid_identifier)s\''
65+ ' is linked to a suspended account.')
66+
67+ def __init__(self, openid_identifier):
68+ LaunchpadFault.__init__(self, openid_identifier=openid_identifier)
69
70=== modified file 'lib/lp/registry/configure.zcml'
71--- lib/lp/registry/configure.zcml 2010-06-09 08:26:26 +0000
72+++ lib/lp/registry/configure.zcml 2010-07-02 12:20:58 +0000
73@@ -1006,6 +1006,16 @@
74 interface="lp.registry.interfaces.mailinglist.IMailingListAPIView"
75 class="canonical.launchpad.xmlrpc.MailingListAPIView"
76 permission="zope.Public"/>
77+ <securedutility
78+ class="lp.registry.xmlrpc.softwarecenteragent.SoftwareCenterAgentApplication"
79+ provides="lp.registry.interfaces.person.ISoftwareCenterAgentApplication">
80+ <allow interface="lp.registry.interfaces.person.ISoftwareCenterAgentApplication" />
81+ </securedutility>
82+ <xmlrpc:view
83+ for="lp.registry.interfaces.person.ISoftwareCenterAgentApplication"
84+ interface="lp.registry.interfaces.person.ISoftwareCenterAgentAPI"
85+ class="lp.registry.xmlrpc.softwarecenteragent.SoftwareCenterAgentAPI"
86+ permission="zope.Public"/>
87
88 <!-- Helper page for held message approval -->
89
90
91=== modified file 'lib/lp/registry/interfaces/person.py'
92--- lib/lp/registry/interfaces/person.py 2010-07-02 12:20:53 +0000
93+++ lib/lp/registry/interfaces/person.py 2010-07-02 12:20:58 +0000
94@@ -16,6 +16,8 @@
95 'IPersonClaim',
96 'IPersonPublic', # Required for a monkey patch in interfaces/archive.py
97 'IPersonSet',
98+ 'ISoftwareCenterAgentAPI',
99+ 'ISoftwareCenterAgentApplication',
100 'IPersonViewRestricted',
101 'IRequestPeopleMerge',
102 'ITeam',
103@@ -75,7 +77,8 @@
104 from canonical.launchpad.validators.email import email_validator
105 from canonical.launchpad.validators.name import name_validator
106 from canonical.launchpad.webapp.authorization import check_permission
107-from canonical.launchpad.webapp.interfaces import NameLookupFailed
108+from canonical.launchpad.webapp.interfaces import (
109+ ILaunchpadApplication, NameLookupFailed)
110
111 from lp.app.interfaces.headings import IRootContext
112 from lp.blueprints.interfaces.specificationtarget import (
113@@ -2086,6 +2089,26 @@
114 required=True, vocabulary=TeamContactMethod)
115
116
117+class ISoftwareCenterAgentAPI(Interface):
118+ """XMLRPC API used by the software center agent."""
119+
120+ def getOrCreateSoftwareCenterCustomer(openid_identifier, email,
121+ full_name):
122+ """Get or create an LP person based on a given identifier.
123+
124+ See the method of the same name on `IPersonSet`. This XMLRPC version
125+ doesn't require the creation rationale and comment.
126+
127+ This is added as a private XMLRPC method instead of exposing via the
128+ API as it should not be needed long-term. Long term we should allow
129+ the software center to create subscriptions to private PPAs without
130+ requiring a Launchpad account.
131+ """
132+
133+class ISoftwareCenterAgentApplication(ILaunchpadApplication):
134+ """XMLRPC application root for ISoftwareCenterAgentAPI."""
135+
136+
137 class JoinNotAllowed(Exception):
138 """User is not allowed to join a given team."""
139
140
141=== added file 'lib/lp/registry/tests/test_xmlrpc.py'
142--- lib/lp/registry/tests/test_xmlrpc.py 1970-01-01 00:00:00 +0000
143+++ lib/lp/registry/tests/test_xmlrpc.py 2010-07-02 12:20:58 +0000
144@@ -0,0 +1,125 @@
145+# Copyright 2010 Canonical Ltd. This software is licensed under the
146+# GNU Affero General Public License version 3 (see the file LICENSE).
147+
148+"""Testing registry-related xmlrpc calls."""
149+
150+__metaclass__ = type
151+
152+import unittest
153+import xmlrpclib
154+from zope.component import getUtility
155+from zope.security.proxy import removeSecurityProxy
156+
157+from canonical.functional import XMLRPCTestTransport
158+from canonical.launchpad.interfaces import IPrivateApplication
159+from canonical.launchpad.interfaces.account import AccountStatus
160+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
161+from canonical.testing.layers import LaunchpadFunctionalLayer
162+from lp.registry.interfaces.person import (
163+ IPersonSet, ISoftwareCenterAgentAPI, ISoftwareCenterAgentApplication,
164+ PersonCreationRationale)
165+from lp.registry.xmlrpc.softwarecenteragent import SoftwareCenterAgentAPI
166+from lp.testing import TestCaseWithFactory
167+
168+
169+class TestSoftwareCenterAgentAPI(TestCaseWithFactory):
170+
171+ layer = LaunchpadFunctionalLayer
172+
173+ def setUp(self):
174+ super(TestSoftwareCenterAgentAPI, self).setUp()
175+ self.private_root = getUtility(IPrivateApplication)
176+ self.sca_api = SoftwareCenterAgentAPI(
177+ context=self.private_root.softwarecenteragent,
178+ request=LaunchpadTestRequest())
179+
180+ def test_provides_interface(self):
181+ # The view interface is provided.
182+ self.assertProvides(self.sca_api, ISoftwareCenterAgentAPI)
183+
184+ def test_getOrCreateSoftwareCenterCustomer(self):
185+ # The method returns the username of the person, and sets the
186+ # correct creation rational/comment.
187+ user_name = self.sca_api.getOrCreateSoftwareCenterCustomer(
188+ 'openid-ident', 'alice@b.com', 'Joe Blogs')
189+
190+ self.assertEqual('alice', user_name)
191+ person = getUtility(IPersonSet).getByName(user_name)
192+ self.assertEqual(
193+ 'openid-ident',
194+ removeSecurityProxy(person.account).openid_identifier)
195+ self.assertEqual(
196+ PersonCreationRationale.SOFTWARE_CENTER_PURCHASE,
197+ person.creation_rationale)
198+ self.assertEqual(
199+ "when purchasing an application via Software Center.",
200+ person.creation_comment)
201+
202+
203+class TestSoftwareCenterAgentApplication(TestCaseWithFactory):
204+
205+ layer = LaunchpadFunctionalLayer
206+
207+ def setUp(self):
208+ super(TestSoftwareCenterAgentApplication, self).setUp()
209+ self.private_root = getUtility(IPrivateApplication)
210+ self.rpc_proxy = xmlrpclib.ServerProxy(
211+ 'http://xmlrpc-private.launchpad.dev:8087/softwarecenteragent',
212+ transport=XMLRPCTestTransport())
213+
214+ def test_provides_interface(self):
215+ # The application is provided.
216+ self.assertProvides(
217+ self.private_root.softwarecenteragent,
218+ ISoftwareCenterAgentApplication)
219+
220+ def test_getOrCreateSoftwareCenterCustomer_xmlrpc(self):
221+ # The method can be called via xmlrpc
222+ user_name = self.rpc_proxy.getOrCreateSoftwareCenterCustomer(
223+ 'openid-ident', 'a@b.com', 'Joe Blogs')
224+ person = getUtility(IPersonSet).getByName(user_name)
225+ self.assertEqual(
226+ 'openid-ident',
227+ removeSecurityProxy(person.account).openid_identifier)
228+
229+ def test_getOrCreateSoftwareCenterCustomer_xmlrpc_error(self):
230+ # A suspended account results in an appropriate xmlrpc fault.
231+ suspended_account = self.factory.makeAccount(
232+ 'Joe Blogs', email='a@b.com', status=AccountStatus.SUSPENDED)
233+ openid_identifier = removeSecurityProxy(
234+ suspended_account).openid_identifier
235+
236+ # assertRaises doesn't let us check the type of Fault.
237+ fault_raised = False
238+ try:
239+ self.rpc_proxy.getOrCreateSoftwareCenterCustomer(
240+ openid_identifier, 'a@b.com', 'Joe Blogs')
241+ except xmlrpclib.Fault, e:
242+ fault_raised = True
243+ self.assertEqual(370, e.faultCode)
244+ self.assertIn(openid_identifier, e.faultString)
245+
246+ self.assertTrue(fault_raised)
247+
248+ def test_not_available_on_public_api(self):
249+ # The person set api is not available on the public xmlrpc
250+ # service.
251+ public_rpc_proxy = xmlrpclib.ServerProxy(
252+ 'http://test@canonical.com:test@'
253+ 'xmlrpc.launchpad.dev/softwarecenteragent',
254+ transport=XMLRPCTestTransport())
255+
256+ # assertRaises doesn't let us check the type of Fault.
257+ protocol_error_raised = False
258+ try:
259+ public_rpc_proxy.getOrCreateSoftwareCenterCustomer(
260+ 'openid-ident', 'a@b.com', 'Joe Blogs')
261+ except xmlrpclib.ProtocolError, e:
262+ protocol_error_raised = True
263+ self.assertEqual(404, e.errcode)
264+
265+ self.assertTrue(protocol_error_raised)
266+
267+
268+def test_suite():
269+ return unittest.TestLoader().loadTestsFromName(__name__)
270
271=== added directory 'lib/lp/registry/xmlrpc'
272=== added file 'lib/lp/registry/xmlrpc/__init__.py'
273=== added file 'lib/lp/registry/xmlrpc/softwarecenteragent.py'
274--- lib/lp/registry/xmlrpc/softwarecenteragent.py 1970-01-01 00:00:00 +0000
275+++ lib/lp/registry/xmlrpc/softwarecenteragent.py 2010-07-02 12:20:58 +0000
276@@ -0,0 +1,47 @@
277+# Copyright 2010 Canonical Ltd. This software is licensed under the
278+# GNU Affero General Public License version 3 (see the file LICENSE).
279+
280+"""XMLRPC APIs for person set."""
281+
282+__metaclass__ = type
283+__all__ = [
284+ 'SoftwareCenterAgentAPI',
285+ ]
286+
287+
288+from zope.component import getUtility
289+from zope.interface import implements
290+
291+from canonical.launchpad.interfaces.account import AccountSuspendedError
292+from canonical.launchpad.webapp import LaunchpadXMLRPCView
293+from canonical.launchpad.xmlrpc import faults
294+from lp.registry.interfaces.person import (
295+ IPersonSet, ISoftwareCenterAgentAPI, ISoftwareCenterAgentApplication,
296+ PersonCreationRationale)
297+
298+
299+class SoftwareCenterAgentAPI(LaunchpadXMLRPCView):
300+ """See `ISoftwareCenterAgentAPI`."""
301+
302+ implements(ISoftwareCenterAgentAPI)
303+
304+ def getOrCreateSoftwareCenterCustomer(self, openid_identifier, email,
305+ full_name):
306+ try:
307+ person, db_updated = getUtility(
308+ IPersonSet).getOrCreateByOpenIDIdentifier(
309+ openid_identifier, email, full_name,
310+ PersonCreationRationale.SOFTWARE_CENTER_PURCHASE,
311+ "when purchasing an application via Software Center.")
312+ except AccountSuspendedError:
313+ return faults.AccountSuspended(openid_identifier)
314+
315+ return person.name
316+
317+
318+class SoftwareCenterAgentApplication:
319+ """Software center agent end-point."""
320+ implements(ISoftwareCenterAgentApplication)
321+
322+ title = "Software Center Agent API"
323+

Subscribers

People subscribed via source and target branches

to status/vote changes: