Merge lp:~henninge/launchpad/bug-503454 into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Henning Eggers
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~henninge/launchpad/bug-503454
Merge into: lp:launchpad
Diff against target: 612 lines (+376/-68)
8 files modified
lib/canonical/launchpad/doc/celebrities.txt (+32/-1)
lib/canonical/launchpad/interfaces/launchpad.py (+98/-0)
lib/canonical/launchpad/security.py (+20/-13)
lib/canonical/launchpad/tests/test_personroles.py (+151/-0)
lib/canonical/launchpad/utilities/personroles.py (+55/-0)
lib/canonical/launchpad/zcml/launchpad.zcml (+10/-0)
lib/lp/services/permission_helpers.py (+0/-46)
lib/lp/translations/model/translationimportqueue.py (+10/-8)
To merge this branch: bzr merge lp:~henninge/launchpad/bug-503454
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+17009@code.launchpad.net

Commit message

Added IPersonRoles to augment ILaunchpadCelebrities and get rid of permission_helpers.py.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (5.1 KiB)

= Details =

See bug 503454. This is a branch that reliefs me of some techdebt that I incurred when I introduced the file permission_helpers.py. It was conclude on the mailing list that such specific functions (like is_admin_or_rosetta_expert) are not usefull. On the other hand, I still disliked the overhead needed to check if a person is an admin. So I proposed this adapter and discussed it with Curtis, wo agreed that it is a good idea. Curtis and I agreed on the name PersonRoles for the adapter because it resembles goes in the same direction as the roles concept that he had put forward earlier.

This branch introduces PersonRoles and removes permission_helpers.py, replacing all references to its functions with checks on attributes of PersonRoles. A follow-up branch will make AuthorizationBase use the adapter directly.

Care was taken to make sure that ILaunchpadCelebrities and IPersonRoles stay in sync, so that whenever a person celebrity is added or removed, the corresponding entry in IPersonRoles has to be added or removed, too.

== Implementation details ==

lib/canonical/launchpad/doc/celebrities.txt

 * Added a test for celebs.person_names.

lib/canonical/launchpad/interfaces/launchpad.py

 * Added the person_names attribute to ILaunchpadCelebrities. This is mainly
   intended to be used by the test that keeps ILauchpatCelebrities in sync
   with IPersonRoles while tests should not be importing from model code.

 * Added IPersonRoles. I placed this in this file intentionally instead of
   giving it its own because it needs to be kept synced with
   ILaunchpadCelbrities. The attributes are named exactly like the
   corresponding attributes in ILaunchpadCelebrities, prefixed with "in_".

lib/canonical/launchpad/security.py

 * Removed import for permission_helpers because that file is deleted.

 * Replaced uses of functions from permission_helpers with checks on the
   adapter attributes. The adapter still needs to be applied locally in each
   function.

lib/canonical/launchpad/tests/test_personroles.py

 * Added interface test for PersonRoles. This will fail if a person celbrity
   is removed from ILaunchpadCelebrity but not from IPersonRoles, or such
   an attribute has been renamed.

 * Added test that checks number of person celebrities in both interfaces.
   This will fail when a person celebtrity is added to ILaunchpadCelebrities
   but not to IPersonRoles.

 * Added coverage for the "in_" attributes for all currently known person
   celebrities, most of which are teams, some are individuals.

 * The rest of the test case is concerned with the methods as defined in
   IPersonRoles.

lib/canonical/launchpad/utilities/celebrities.py

 * Added implementation for person_names. PersonCelebrityDescritor was
   already collection them in a "set" attribute.

lib/canonical/launchpad/utilities/personroles.py

 * Implemented the "in_" attributes using __getattr__, thus automatically
   getting the corresponding attribute from the ILaunchpadCelebrities
   implementation by stripping the prefix off the name.

 * isDriver is implemented to also check for the "drivers" attribute, so that
   all possible drivers are checked.

lib/canonical/launc...

Read more...

Revision history for this message
Henning Eggers (henninge) wrote :

I moved the test from test_personroles.py to celbrities.txt to be sure that somebody adding a celebrity is made aware of the need to update IPersonRoles, too.

Revision history for this message
Abel Deuring (adeuring) wrote :
Download full text (13.8 KiB)

Hi Henning,

overall, a very nice branch! r=me with the changes from
http://paste.ubuntu.com/353497/.

We discussed on IRC that it would be better not to use the list of
"SQL names" of the IPerson celebrities for the comparison of the
set person celebrities and the corresponding set of in_.* methods
in IPersonRoles.

Other that that, I have a few mostly formal nitpicks, see below.

r=me

Abel

>
> === modified file 'lib/canonical/launchpad/doc/celebrities.txt'
> --- lib/canonical/launchpad/doc/celebrities.txt 2009-11-18 07:19:14 +0000
> +++ lib/canonical/launchpad/doc/celebrities.txt 2010-01-08 10:36:25 +0000
> @@ -225,6 +225,49 @@
> True
>
>
> +== Person celebrities ==
> +
> +A list of all IPerson celebrities is provided.
> +
> + >>> for name in celebs.person_names:
> + ... print name
> + admins
> + bazaar-experts
> + bug-importer
> + bug-watch-updater
> + commercial-admins
> + hwdb-team
> + janitor
> + katie
> + launchpad
> + launchpad-beta-testers
> + launchpad-buildd-admins
> + mailing-list-experts
> + ppa-key-guard
> + registry
> + rosetta-admins
> + shipit-admins
> + techboard
> + ubuntu-branches
> + ubuntu-security
> + vcs-imports
> +
> +The number of Person celebrities is identical to the number of "in_"
> +attributes of the IPersonRoles interface. If they differ, IPersonRoles needs
> +to be synced to ILaunchpadCelebrities by adding/removing the appropriate
> +"in_" attribute(s).
> +
> + >>> from canonical.launchpad.interfaces.launchpad import IPersonRoles
> + >>> def number_of_in_attributes():
> + ... num = 0
> + ... for name in IPersonRoles.names():
> + ... if name.startswith("in_"):
> + ... num += 1
> + ... return num
> + >>> print number_of_in_attributes() == len(celebs.person_names)
> + True
> +
> +
> == Detecting if a person is a celebrity ==
>
> We can ask if a person has celebrity status.
>
> === modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
> --- lib/canonical/launchpad/interfaces/launchpad.py 2009-12-03 04:50:40 +0000
> +++ lib/canonical/launchpad/interfaces/launchpad.py 2010-01-08 10:36:25 +0000
> @@ -57,6 +57,7 @@
> 'IPasswordChangeApp',
> 'IPasswordEncryptor',
> 'IPasswordResets',
> + 'IPersonRoles',
> 'IPrivateApplication',
> 'IPrivateMaloneApplication',
> 'IPrivacy',
> @@ -133,11 +134,110 @@
> ubuntu_techboard = Attribute("The Ubuntu technical board.")
> vcs_imports = Attribute("The 'vcs-imports' team.")
>
> + person_names = Attribute("A list of the names of all celebrity persons.")
> +
> def isCelebrityPerson(name):
> """Return true if there is an IPerson celebrity with the given name.
> """
>
>
> +class IPersonRoles(Interface):
> + """What celebrity teams a person is member of and similar helpers.
> +
> + Convenience methods that remove frequent calls to ILaunchpadCelebrities
> + and IPerson.inTeam from permission checkers. May also be used in model
> + or view code.
> +
> + All person celebrities in ILaunchpadCelbrities must have a matching
> + in_ attribute here and vice versa.
>...

review: Approve (code)
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (3.2 KiB)

> overall, a very nice branch! r=me with the changes from
> http://paste.ubuntu.com/353497/.
>
> We discussed on IRC that it would be better not to use the list of
> "SQL names" of the IPerson celebrities for the comparison of the
> set person celebrities and the corresponding set of in_.* methods
> in IPersonRoles.

Thanks, I did that as seen in the paste and some more. The test will now fail with a message that describes what needs to be done to sync the two Interfaces again. I like this solution a lot. ;)

>
> Other that that, I have a few mostly formal nitpicks, see below.

Cool, please see my comments below.

> > + def isDriver(obj):
> > + """Is this person on of the drivers of the object?"""
>
> s/on of/one of/

Done.

>
> > +
> > + def isOneOf(obj, attributes):
> > + """Is this person on of the roles in relation to the object?
>
> s/on of/one of/
>

Done.

> > +
> > + Check if the person is (inTeam) one of the given IPerson attributes
>
> s/(inTeam) one/(inTeam) of one/

Fixed. Thanks for you patience.

> > @@ -195,7 +194,8 @@
> > usedfor = None
> >
> > def checkAuthenticated(self, user):
> > - return is_admin_or_registry_expert(user)
> > + user = IPersonRoles(user)
>
> I find this sort of using one variable for somwhat different thingies
> (first for an IPerson, next for an IPersonRoles) a bit confusing. What
> do you think about "user_roles = IPersonRoles(user)"?
>

Yes, I know what you mean. I am not going to do that here, though, because in my next branch, the user parameter will already be IPersonRoles, thus eliminating this line completely again.

But I fixed it in translationimportqueue.py. Thanks for pointing it out.

> > === added file 'lib/canonical/launchpad/tests/test_personroles.py'
> > + def _test_in_team(self, attribute):
> > + roles_attribute = self.prefix+attribute
>
> Please insert spaces before and after the '+'.

Fixed that, thank you.

> > + def test_in_teams(self):
> > + # Test all celebrity teams are available.
> > + team_attributes = [
> > + 'admin',
> > + 'bazaar_experts',
> > + 'buildd_admin',
> > + 'commercial_admin',
> > + 'hwdb_team',
> > + 'launchpad_beta_testers',
> > + 'launchpad_developers',
> > + 'mailing_list_experts',
> > + 'registry_experts',
> > + 'rosetta_experts',
> > + 'shipit_admin',
> > + 'ubuntu_branches',
> > + 'ubuntu_security',
> > + 'ubuntu_techboard',
> > + 'vcs_imports',
> > + ]
>
> This list isn't complete ;) I think you can create the list of
> person celebrity attribute names here in the same way as in
> celebrities.txt in http://paste.ubuntu.com/353497/.
>

That is a great idea and it has the code much shorter. Thanks for that! ;)

> > + for attribute in team_attributes:
> > + self._test_in_team(attribute)
> > +
> > + def _test_in_person(self, attribute):
> > + roles_attribute = self.prefix+attribute
>
> Please insert spaces before and after the '+'.

Done.

Thank you for your review, my c...

Read more...

Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (9.2 KiB)

=== modified file 'lib/canonical/launchpad/doc/celebrities.txt'
--- lib/canonical/launchpad/doc/celebrities.txt 2010-01-08 10:34:28 +0000
+++ lib/canonical/launchpad/doc/celebrities.txt 2010-01-08 15:25:33 +0000
@@ -8,7 +8,8 @@
 to these well-known objects through attributes.

     >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
- >>> from canonical.launchpad.interfaces import ILanguageSet, IPersonSet
+ >>> from lp.services.worlddata.interfaces.language import ILanguageSet
+ >>> from lp.registry.interfaces.person import IPerson, IPersonSet
     >>> celebs = getUtility(ILaunchpadCelebrities)

     >>> from canonical.launchpad.webapp.testing import verifyObject
@@ -227,45 +228,32 @@

 == Person celebrities ==

-A list of all IPerson celebrities is provided.
-
- >>> for name in celebs.person_names:
- ... print name
- admins
- bazaar-experts
- bug-importer
- bug-watch-updater
- commercial-admins
- hwdb-team
- janitor
- katie
- launchpad
- launchpad-beta-testers
- launchpad-buildd-admins
- mailing-list-experts
- ppa-key-guard
- registry
- rosetta-admins
- shipit-admins
- techboard
- ubuntu-branches
- ubuntu-security
- vcs-imports
-
-The number of Person celebrities is identical to the number of "in_"
-attributes of the IPersonRoles interface. If they differ, IPersonRoles needs
+Each person celebrity has a corresponding "in_" attribute in IPersonRoles, to
+check if a person has that role. If the attributes differ, IPersonRoles needs
 to be synced to ILaunchpadCelebrities by adding/removing the appropriate
 "in_" attribute(s).

     >>> from canonical.launchpad.interfaces.launchpad import IPersonRoles
- >>> def number_of_in_attributes():
- ... num = 0
+ >>> def get_person_celebrity_names():
+ ... for name in ILaunchpadCelebrities.names():
+ ... if IPerson.providedBy(getattr(celebs, name)):
+ ... yield "in_" + name
+ >>> def get_person_roles_names():
     ... for name in IPersonRoles.names():
     ... if name.startswith("in_"):
- ... num += 1
- ... return num
- >>> print number_of_in_attributes() == len(celebs.person_names)
- True
+ ... yield name
+
+Treating the lists as sets and determining their difference gives us a clear
+picture of what is missing where.
+
+ >>> person_celebrity_names = set(get_person_celebrity_names())
+ >>> person_roles_names = set(get_person_roles_names())
+ >>> print "Please add to IPersonRoles: " + (
+ ... ", ".join(list(person_celebrity_names - person_roles_names)))
+ Please add to IPersonRoles:
+ >>> print "Please remove from IPersonRoles: " + (
+ ... ", ".join(list(person_roles_names - person_celebrity_names)))
+ Please remove from IPersonRoles:

 == Detecting if a person is a celebrity ==

=== modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
--- lib/canonical/launchpad/interfaces/launchpad.py 2010-01-08 09:55:37 +0000
+++ lib/canonical/launchpad/interfaces/launchpad.py 2010-01-08 16:13:43 +0000
@@ -134,8 +134,6 @@
     ubuntu_techboard = Attribute("The Ubuntu t...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/celebrities.txt'
2--- lib/canonical/launchpad/doc/celebrities.txt 2009-11-18 07:19:14 +0000
3+++ lib/canonical/launchpad/doc/celebrities.txt 2010-01-08 23:43:19 +0000
4@@ -8,7 +8,8 @@
5 to these well-known objects through attributes.
6
7 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
8- >>> from canonical.launchpad.interfaces import ILanguageSet, IPersonSet
9+ >>> from lp.services.worlddata.interfaces.language import ILanguageSet
10+ >>> from lp.registry.interfaces.person import IPerson, IPersonSet
11 >>> celebs = getUtility(ILaunchpadCelebrities)
12
13 >>> from canonical.launchpad.webapp.testing import verifyObject
14@@ -225,6 +226,36 @@
15 True
16
17
18+== Person celebrities ==
19+
20+Each person celebrity has a corresponding "in_" attribute in IPersonRoles, to
21+check if a person has that role. If the attributes differ, IPersonRoles needs
22+to be synced to ILaunchpadCelebrities by adding/removing the appropriate
23+"in_" attribute(s).
24+
25+ >>> from canonical.launchpad.interfaces.launchpad import IPersonRoles
26+ >>> def get_person_celebrity_names():
27+ ... for name in ILaunchpadCelebrities.names():
28+ ... if IPerson.providedBy(getattr(celebs, name)):
29+ ... yield "in_" + name
30+ >>> def get_person_roles_names():
31+ ... for name in IPersonRoles.names():
32+ ... if name.startswith("in_"):
33+ ... yield name
34+
35+Treating the lists as sets and determining their difference gives us a clear
36+picture of what is missing where.
37+
38+ >>> person_celebrity_names = set(get_person_celebrity_names())
39+ >>> person_roles_names = set(get_person_roles_names())
40+ >>> print "Please add to IPersonRoles: " + (
41+ ... ", ".join(list(person_celebrity_names - person_roles_names)))
42+ Please add to IPersonRoles:
43+ >>> print "Please remove from IPersonRoles: " + (
44+ ... ", ".join(list(person_roles_names - person_celebrity_names)))
45+ Please remove from IPersonRoles:
46+
47+
48 == Detecting if a person is a celebrity ==
49
50 We can ask if a person has celebrity status.
51
52=== modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
53--- lib/canonical/launchpad/interfaces/launchpad.py 2009-12-03 04:50:40 +0000
54+++ lib/canonical/launchpad/interfaces/launchpad.py 2010-01-08 23:43:19 +0000
55@@ -57,6 +57,7 @@
56 'IPasswordChangeApp',
57 'IPasswordEncryptor',
58 'IPasswordResets',
59+ 'IPersonRoles',
60 'IPrivateApplication',
61 'IPrivateMaloneApplication',
62 'IPrivacy',
63@@ -138,6 +139,103 @@
64 """
65
66
67+class IPersonRoles(Interface):
68+ """What celebrity teams a person is member of and similar helpers.
69+
70+ Convenience methods that remove frequent calls to ILaunchpadCelebrities
71+ and IPerson.inTeam from permission checkers. May also be used in model
72+ or view code.
73+
74+ All person celebrities in ILaunchpadCelbrities must have a matching
75+ in_ attribute here and vice versa.
76+ """
77+
78+ person = Attribute("The IPerson object that these checks refer to.")
79+
80+ in_admin = Bool(
81+ title=_("True if this person is a Launchpad admin."),
82+ required=True, readonly=True)
83+ in_bazaar_experts = Bool(
84+ title=_("True if this person is a Bazaar expert."),
85+ required=True, readonly=True)
86+ in_bug_importer = Bool(
87+ title=_("True if this person is a bug importer."),
88+ required=True, readonly=True)
89+ in_bug_watch_updater = Bool(
90+ title=_("True if this person is a bug watch updater."),
91+ required=True, readonly=True)
92+ in_buildd_admin = Bool(
93+ title=_("True if this person is a buildd admin."),
94+ required=True, readonly=True)
95+ in_commercial_admin = Bool(
96+ title=_("True if this person is a commercial admin."),
97+ required=True, readonly=True)
98+ in_hwdb_team = Bool(
99+ title=_("True if this person is on the hwdb team."),
100+ required=True, readonly=True)
101+ in_janitor = Bool(
102+ title=_("True if this person is the janitor."),
103+ required=True, readonly=True)
104+ in_katie = Bool(
105+ title=_("True if this person is Katie."),
106+ required=True, readonly=True)
107+ in_launchpad_beta_testers = Bool(
108+ title=_("True if this person is a Launchpad beta tester."),
109+ required=True, readonly=True)
110+ in_launchpad_developers = Bool(
111+ title=_("True if this person is a Launchpad developer."),
112+ required=True, readonly=True)
113+ in_mailing_list_experts = Bool(
114+ title=_("True if this person is a mailing list expert."),
115+ required=True, readonly=True)
116+ in_ppa_key_guard = Bool(
117+ title=_("True if this person is the ppa key guard."),
118+ required=True, readonly=True)
119+ in_registry_experts = Bool(
120+ title=_("True if this person is a registry expert."),
121+ required=True, readonly=True)
122+ in_rosetta_experts = Bool(
123+ title=_("True if this person is a rosetta expert."),
124+ required=True, readonly=True)
125+ in_shipit_admin = Bool(
126+ title=_("True if this person is a ShipIt admin."),
127+ required=True, readonly=True)
128+ in_ubuntu_branches = Bool(
129+ title=_("True if this person is on the Ubuntu branches team."),
130+ required=True, readonly=True)
131+ in_ubuntu_security = Bool(
132+ title=_("True if this person is on the Ubuntu security team."),
133+ required=True, readonly=True)
134+ in_ubuntu_techboard = Bool(
135+ title=_("True if this person is on the Ubuntu tech board."),
136+ required=True, readonly=True)
137+ in_vcs_imports = Bool(
138+ title=_("True if this person is on the vcs-imports team."),
139+ required=True, readonly=True)
140+
141+ def inTeam(team):
142+ """Is this person a member or the owner of `team`?
143+
144+ Passed through to the same method in 'IPersonPublic'.
145+ """
146+
147+ def isOwner(obj):
148+ """Is this person the owner of the object?"""
149+
150+ def isDriver(obj):
151+ """Is this person one of the drivers of the object?"""
152+
153+ def isOneOf(obj, attributes):
154+ """Is this person one of the roles in relation to the object?
155+
156+ Check if the person is inTeam of one of the given IPerson attributes
157+ of the object.
158+
159+ :param obj: The object to check the relation to.
160+ :param attributes: A list of attribute names to check with inTeam.
161+ """
162+
163+
164 class ICrowd(Interface):
165
166 def __contains__(person_or_team_or_anything):
167
168=== modified file 'lib/canonical/launchpad/security.py'
169--- lib/canonical/launchpad/security.py 2010-01-07 06:27:46 +0000
170+++ lib/canonical/launchpad/security.py 2010-01-08 23:43:19 +0000
171@@ -53,12 +53,11 @@
172 from canonical.launchpad.interfaces.hwdb import (
173 IHWDBApplication, IHWDevice, IHWDeviceClass, IHWDriver, IHWDriverName,
174 IHWDriverPackageName, IHWSubmission, IHWSubmissionDevice, IHWVendorID)
175-from lp.services.permission_helpers import (
176- is_admin, is_admin_or_registry_expert, is_admin_or_rosetta_expert)
177 from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet
178 from lp.translations.interfaces.languagepack import ILanguagePack
179 from canonical.launchpad.interfaces.launchpad import (
180- IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities)
181+ IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities,
182+ IPersonRoles)
183 from lp.registry.interfaces.role import IHasOwner
184 from lp.registry.interfaces.location import IPersonLocation
185 from lp.registry.interfaces.mailinglist import IMailingListSet
186@@ -195,7 +194,8 @@
187 usedfor = None
188
189 def checkAuthenticated(self, user):
190- return is_admin_or_registry_expert(user)
191+ user = IPersonRoles(user)
192+ return user.in_admin or user.in_registry_experts
193
194
195 class ReviewProduct(ReviewByRegistryExpertsOrAdmins):
196@@ -231,9 +231,10 @@
197 if self.obj.active:
198 return True
199 else:
200- celebrities = getUtility(ILaunchpadCelebrities)
201- return (user.inTeam(celebrities.commercial_admin)
202- or is_admin_or_registry_expert(user))
203+ user = IPersonRoles(user)
204+ return (user.in_commercial_admin or
205+ user.in_admin or
206+ user.in_registry_experts)
207
208
209 class EditAccountBySelfOrAdmin(AuthorizationBase):
210@@ -247,7 +248,8 @@
211 EditAccountBySelfOrAdmin, self).checkAccountAuthenticated(account)
212
213 def checkAuthenticated(self, user):
214- return is_admin(user)
215+ user = IPersonRoles(user)
216+ return user.in_admin
217
218
219 class ViewAccount(EditAccountBySelfOrAdmin):
220@@ -259,7 +261,8 @@
221
222 def checkAuthenticated(self, user):
223 """Extend permission to registry experts."""
224- return is_admin_or_registry_expert(user)
225+ user = IPersonRoles(user)
226+ return user.in_admin or user.in_registry_experts
227
228
229 class ModerateAccountByRegistryExpert(AuthorizationBase):
230@@ -267,7 +270,8 @@
231 permission = 'launchpad.Moderate'
232
233 def checkAuthenticated(self, user):
234- return is_admin_or_registry_expert(user)
235+ user = IPersonRoles(user)
236+ return user.in_admin or user.in_registry_experts
237
238
239 class EditOAuthAccessToken(AuthorizationBase):
240@@ -498,7 +502,8 @@
241
242 def checkAuthenticated(self, user):
243 """Allow Launchpad's admins and Rosetta experts edit all fields."""
244- return is_admin_or_rosetta_expert(user)
245+ user = IPersonRoles(user)
246+ return user.in_admin or user.in_rosetta_experts
247
248
249 class AdminProductTranslations(AuthorizationBase):
250@@ -511,8 +516,10 @@
251 Any Launchpad/Launchpad Translations administrator or owners are
252 able to change translation settings for a product.
253 """
254- return (user.inTeam(self.obj.owner) or
255- is_admin_or_rosetta_expert(user))
256+ user = IPersonRoles(user)
257+ return (user.isOwner(self.obj) or
258+ user.in_rosetta_experts or
259+ user.in_admin)
260
261
262 class AdminSeriesByVCSImports(AuthorizationBase):
263
264=== added file 'lib/canonical/launchpad/tests/test_personroles.py'
265--- lib/canonical/launchpad/tests/test_personroles.py 1970-01-01 00:00:00 +0000
266+++ lib/canonical/launchpad/tests/test_personroles.py 2010-01-08 23:43:19 +0000
267@@ -0,0 +1,151 @@
268+# Copyright 2010 Canonical Ltd. This software is licensed under the
269+# GNU Affero General Public License version 3 (see the file LICENSE).
270+
271+__metaclass__ = type
272+
273+import unittest
274+
275+from zope.interface.verify import verifyObject
276+from zope.component import getUtility
277+from zope.security.proxy import removeSecurityProxy
278+
279+from canonical.launchpad.interfaces.launchpad import (
280+ ILaunchpadCelebrities, IPersonRoles)
281+from lp.registry.interfaces.person import IPerson
282+
283+from lp.testing import TestCaseWithFactory
284+from canonical.testing import ZopelessDatabaseLayer
285+
286+
287+class TestPersonRoles(TestCaseWithFactory):
288+ """Test IPersonRoles adapter.
289+ """
290+
291+ layer = ZopelessDatabaseLayer
292+
293+ prefix = 'in_'
294+
295+ def setUp(self):
296+ super(TestPersonRoles, self).setUp()
297+ self.person = self.factory.makePerson()
298+ self.celebs = getUtility(ILaunchpadCelebrities)
299+
300+ def test_interface(self):
301+ roles = IPersonRoles(self.person)
302+ verifyObject(IPersonRoles, roles)
303+
304+ def test_person(self):
305+ # The person is available through the person attribute.
306+ roles = IPersonRoles(self.person)
307+ self.assertIs(self.person, roles.person)
308+
309+ def _get_person_celebrities(self, is_team):
310+ for name in ILaunchpadCelebrities.names():
311+ attr = getattr(self.celebs, name)
312+ if IPerson.providedBy(attr) and attr.isTeam() == is_team:
313+ yield (name, attr)
314+
315+ def test_in_teams(self):
316+ # Test all celebrity teams are available.
317+ for name, team in self._get_person_celebrities(is_team=True):
318+ roles_attribute = self.prefix + name
319+ roles = IPersonRoles(self.person)
320+ self.assertFalse(
321+ getattr(roles, roles_attribute),
322+ "%s should be False" % roles_attribute)
323+
324+ team.addMember(self.person, team.teamowner)
325+ roles = IPersonRoles(self.person)
326+ self.assertTrue(
327+ getattr(roles, roles_attribute),
328+ "%s should be True" % roles_attribute)
329+ self.person.leave(team)
330+
331+ def test_is_person(self):
332+ # All celebrity persons are available.
333+ for name, celeb in self._get_person_celebrities(is_team=False):
334+ roles_attribute = self.prefix + name
335+ roles = IPersonRoles(celeb)
336+ self.assertTrue(
337+ getattr(roles, roles_attribute),
338+ "%s should be True" % roles_attribute)
339+
340+ def test_in_AttributeError(self):
341+ # Do not check for non-existent attributes, even if it has the
342+ # right prefix.
343+ roles = IPersonRoles(self.person)
344+ fake_attr = self.factory.getUniqueString()
345+ self.assertRaises(AttributeError, getattr, roles, fake_attr)
346+ fake_attr = self.factory.getUniqueString(self.prefix)
347+ self.assertRaises(AttributeError, getattr, roles, fake_attr)
348+
349+ def test_inTeam(self):
350+ # The method person.inTeam is available as the inTeam attribute.
351+ roles = IPersonRoles(self.person)
352+ self.assertEquals(self.person.inTeam, roles.inTeam)
353+
354+ def test_inTeam_works(self):
355+ # Make sure it actually works.
356+ team = self.factory.makeTeam(self.person)
357+ roles = IPersonRoles(self.person)
358+ self.assertTrue(roles.inTeam(team))
359+
360+ def test_isOwner(self):
361+ # The person can be the owner of something, e.g. a product.
362+ product = self.factory.makeProduct(owner=self.person)
363+ roles = IPersonRoles(self.person)
364+ self.assertTrue(roles.isOwner(product))
365+
366+ def test_isDriver(self):
367+ # The person can be the driver of something, e.g. a sprint.
368+ sprint = self.factory.makeSprint()
369+ sprint.driver = self.person
370+ roles = IPersonRoles(self.person)
371+ self.assertTrue(roles.isDriver(sprint))
372+
373+ def test_isDriver_multiple_drivers(self):
374+ # The person can be one of multiple drivers of if a product and its
375+ # series each has a driver.
376+ productseries = self.factory.makeProductSeries()
377+ productseries.product.driver = self.person
378+ productseries.driver = self.factory.makePerson()
379+ roles = IPersonRoles(self.person)
380+ self.assertTrue(roles.isDriver(productseries))
381+
382+ def test_isOneOf(self):
383+ # Objects may have multiple roles that a person can fulfill.
384+ # Specifications are such a case.
385+ spec = removeSecurityProxy(self.factory.makeSpecification())
386+ spec.owner = self.factory.makePerson()
387+ spec.drafter = self.factory.makePerson()
388+ spec.assignee = self.factory.makePerson()
389+ spec.approver = self.person
390+
391+ roles = IPersonRoles(self.person)
392+ self.assertTrue(roles.isOneOf(
393+ spec, ['owner', 'drafter', 'assignee', 'approver']))
394+
395+ def test_isOneOf_None(self):
396+ # Objects may have multiple roles that a person can fulfill.
397+ # Specifications are such a case. Some roles may be None.
398+ spec = removeSecurityProxy(self.factory.makeSpecification())
399+ spec.owner = self.factory.makePerson()
400+ spec.drafter = None
401+ spec.assignee = None
402+ spec.approver = self.person
403+
404+ roles = IPersonRoles(self.person)
405+ self.assertTrue(roles.isOneOf(
406+ spec, ['owner', 'drafter', 'assignee', 'approver']))
407+
408+ def test_isOneOf_AttributeError(self):
409+ # Do not try to check for none-existent attributes.
410+ obj = self.factory.makeProduct()
411+ fake_attr = self.factory.getUniqueString()
412+ roles = IPersonRoles(self.person)
413+ self.assertRaises(AttributeError, roles.isOneOf, obj, [fake_attr])
414+
415+
416+def test_suite():
417+ return unittest.TestLoader().loadTestsFromName(__name__)
418+
419
420=== added file 'lib/canonical/launchpad/utilities/personroles.py'
421--- lib/canonical/launchpad/utilities/personroles.py 1970-01-01 00:00:00 +0000
422+++ lib/canonical/launchpad/utilities/personroles.py 2010-01-08 23:43:19 +0000
423@@ -0,0 +1,55 @@
424+# Copyright 2010 Canonical Ltd. This software is licensed under the
425+# GNU Affero General Public License version 3 (see the file LICENSE).
426+
427+"""Class that implements the IPersonRoles interface."""
428+
429+__metaclass__ = type
430+__all__ = ['PersonRoles']
431+
432+from zope.interface import implements
433+from zope.component import adapts, getUtility
434+from canonical.launchpad.interfaces import (
435+ ILaunchpadCelebrities, IPersonRoles)
436+
437+from lp.registry.interfaces.person import IPerson
438+
439+
440+class PersonRoles:
441+ implements(IPersonRoles)
442+ adapts(IPerson)
443+
444+ def __init__(self, person):
445+ self.person = person
446+ self._celebrities = getUtility(ILaunchpadCelebrities)
447+ self.inTeam = self.person.inTeam
448+
449+ def __getattr__(self, name):
450+ """Handle all in_* attributes."""
451+ prefix = 'in_'
452+ if not name.startswith(prefix):
453+ raise AttributeError
454+ attribute = name[len(prefix):]
455+ return self.person.inTeam(getattr(self._celebrities, attribute))
456+
457+ def isOwner(self, obj):
458+ """See IPersonRoles."""
459+ return self.person.inTeam(obj.owner)
460+
461+ def isDriver(self, obj):
462+ """See IPersonRoles."""
463+ drivers = getattr(obj, 'drivers', None)
464+ if drivers is None:
465+ return self.person.inTeam(obj.driver)
466+ for driver in drivers:
467+ if self.person.inTeam(driver):
468+ return True
469+ return False
470+
471+ def isOneOf(self, obj, attributes):
472+ """See IPersonRoles."""
473+ for attr in attributes:
474+ role = getattr(obj, attr)
475+ if self.person.inTeam(role):
476+ return True
477+ return False
478+
479
480=== modified file 'lib/canonical/launchpad/zcml/launchpad.zcml'
481--- lib/canonical/launchpad/zcml/launchpad.zcml 2009-12-23 16:17:12 +0000
482+++ lib/canonical/launchpad/zcml/launchpad.zcml 2010-01-08 23:43:19 +0000
483@@ -356,6 +356,16 @@
484
485 </facet>
486
487+<class
488+ class="canonical.launchpad.utilities.personroles.PersonRoles">
489+ <allow
490+ interface="canonical.launchpad.interfaces.IPersonRoles"/>
491+</class>
492+<adapter
493+ for="lp.registry.interfaces.person.IPerson"
494+ provides="canonical.launchpad.interfaces.IPersonRoles"
495+ factory="canonical.launchpad.utilities.personroles.PersonRoles" />
496+
497
498 <adapter
499 provides="canonical.lazr.interfaces.IObjectPrivacy"
500
501=== removed file 'lib/lp/services/permission_helpers.py'
502--- lib/lp/services/permission_helpers.py 2009-12-15 21:34:43 +0000
503+++ lib/lp/services/permission_helpers.py 1970-01-01 00:00:00 +0000
504@@ -1,46 +0,0 @@
505-# Copyright 2009 Canonical Ltd. This software is licensed under the
506-# GNU Affero General Public License version 3 (see the file LICENSE).
507-
508-"""Helpful functions to enforce permissions."""
509-
510-__metaclass__ = type
511-
512-__all__ = [
513- 'is_admin',
514- 'is_admin_or_registry_expert',
515- 'is_admin_or_rosetta_expert',
516- 'is_registry_expert',
517- 'is_rosetta_expert',
518- ]
519-
520-from zope.component import getUtility
521-
522-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
523-
524-
525-def is_admin(user):
526- """Check if the user is a Launchpad admin."""
527- celebrities = getUtility(ILaunchpadCelebrities)
528- return user.inTeam(celebrities.admin)
529-
530-
531-def is_rosetta_expert(user):
532- """Check if the user is a Rosetta expert."""
533- celebrities = getUtility(ILaunchpadCelebrities)
534- return user.inTeam(celebrities.rosetta_experts)
535-
536-
537-def is_registry_expert(user):
538- """Check if the user is a registry expert."""
539- celebrities = getUtility(ILaunchpadCelebrities)
540- return user.inTeam(celebrities.registry_experts)
541-
542-
543-def is_admin_or_rosetta_expert(user):
544- """Check if the user is a Launchpad admin or a Rosetta expert."""
545- return is_admin(user) or is_rosetta_expert(user)
546-
547-
548-def is_admin_or_registry_expert(user):
549- """Check if the user is a Launchpad admin or a registry expert."""
550- return is_admin(user) or is_registry_expert(user)
551
552=== modified file 'lib/lp/translations/model/translationimportqueue.py'
553--- lib/lp/translations/model/translationimportqueue.py 2009-12-24 11:34:12 +0000
554+++ lib/lp/translations/model/translationimportqueue.py 2010-01-08 23:43:19 +0000
555@@ -32,7 +32,8 @@
556 from canonical.database.constants import UTC_NOW, DEFAULT
557 from canonical.database.enumcol import EnumCol
558 from canonical.launchpad.helpers import shortlist
559-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
560+from canonical.launchpad.interfaces.launchpad import (
561+ ILaunchpadCelebrities, IPersonRoles)
562 from canonical.launchpad.interfaces.lpstorm import IMasterStore
563 from canonical.launchpad.webapp.interfaces import NotFoundError
564 from lp.registry.interfaces.distribution import IDistribution
565@@ -43,7 +44,6 @@
566 from lp.registry.interfaces.product import IProduct
567 from lp.registry.interfaces.productseries import IProductSeries
568 from lp.registry.interfaces.sourcepackage import ISourcePackage
569-from lp.services.permission_helpers import is_admin_or_rosetta_expert
570 from lp.services.worlddata.interfaces.language import ILanguageSet
571 from lp.translations.interfaces.pofile import IPOFileSet
572 from lp.translations.interfaces.potemplate import IPOTemplateSet
573@@ -285,12 +285,13 @@
574
575 def isUserUploaderOrOwner(self, user):
576 """See `ITranslationImportQueueEntry`."""
577- if user.inTeam(self.importer):
578+ roles = IPersonRoles(user)
579+ if roles.inTeam(self.importer):
580 return True
581 if self.productseries is not None:
582- return user.inTeam(self.productseries.product.owner)
583+ return roles.isOwner(self.productseries.product)
584 if self.distroseries is not None:
585- return user.inTeam(self.distroseries.distribution.owner)
586+ return roles.isOwner(self.distroseries.distribution)
587 return False
588
589 def canSetStatus(self, new_status, user):
590@@ -298,7 +299,8 @@
591 if user is None:
592 # Anonymous user cannot do anything.
593 return False
594- can_admin = (is_admin_or_rosetta_expert(user) or
595+ roles = IPersonRoles(user)
596+ can_admin = (roles.in_admin or roles.in_rosetta_experts or
597 self.isUbuntuAndIsUserTranslationGroupOwner(user))
598 if new_status == RosettaImportStatus.APPROVED:
599 # Only administrators are able to set the APPROVED status, and
600@@ -309,11 +311,11 @@
601 # Only rosetta experts are able to set the IMPORTED status, and
602 # that's only possible if we know where to import it
603 # (import_into not None).
604- return (is_admin_or_rosetta_expert(user) and
605+ return ((roles.in_admin or roles.in_rosetta_experts) and
606 self.import_into is not None)
607 if new_status == RosettaImportStatus.FAILED:
608 # Only rosetta experts are able to set the FAILED status.
609- return is_admin_or_rosetta_expert(user)
610+ return roles.in_admin or roles.in_rosetta_experts
611 # All other statuses can bset set by all authorized persons.
612 return self.isUserUploaderOrOwner(user) or can_admin
613