Merge lp:~al-maisan/launchpad/bedit-347768 into lp:launchpad
- bedit-347768
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Jonathan Lange |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~al-maisan/launchpad/bedit-347768 |
Merge into: | lp:launchpad |
Diff against target: |
417 lines 2 files modified
lib/canonical/launchpad/security.py (+56/-3) lib/lp/code/tests/test_branch.py (+186/-33) |
To merge this branch: | bzr merge lp:~al-maisan/launchpad/bedit-347768 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange (community) | Approve | ||
Review via email: mp+13561@code.launchpad.net |
Commit message
Description of the change
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange (jml) wrote : | # |
Hi Muharem,
Thanks for looking into this and following up on our implementation call. This branch is good, but it's missing a crucial behaviour: we only want this extended permission checking for officially linked source package branches. That is, for branches like lp:ubuntu/karmic/ssss rather than lp:~jml/ubuntu/karmic/ssss/thingamajig.
This linking is also tied into how we determine the pocket for a branch. I've made some comments on this below.
Let me know if you have any questions.
jml
> === modified file 'lib/canonical/
> --- lib/canonical/
> +++ lib/canonical/
> @@ -7,9 +7,10 @@
> __all__ = ['Authorization
>
> from zope.interface import implements, Interface
> -from zope.component import getAdapter, getUtility
> +from zope.component import getUtility
>
> from canonical.
> +from lp.archiveuploa
> from lp.registry.
> from lp.soyuz.
> from lp.soyuz.
> @@ -1591,7 +1592,57 @@
>
> def checkAuthentica
> return (user.inTeam(
> - user_has_
> + user_has_
> + can_upload_
> +
> +
> +def can_upload_
> + """True if person may upload the package linked to `branch`."""
Blank line here please.
> + def get_current_
> + """Get current release for the source package linked to branch."""
> + ds = branch.distroseries
> + package = branch.
> + releases = ds.getCurrentSo
> + return releases.
> +
> + def get_property_
> + """Get value of requested property or None."""
> + value = None
> + try:
> + value = getattr(obj, property_name)
> + except AttributeError:
> + return None
> + return value
> +
I don't know what the purpose of this method is. 'getattr(obj, name, None)'
will do exactly the same thing.
> + # Check whether we're dealing with a source package branch and
> + # whether person is authorised to upload the respective source
> + # package.
> + if branch.
> + # No package .. hmm .. this can't be a source package branch
> + # then. Abort.
> + return False
> +
> + if branch.distroseries is None:
> + # No distro series? Abort.
> + return False
> +
There's a database constraint to ensure that you cannot have sourcepackage
without distroseries. I don't have a problem with doing an extra check here,
but you can also feel free to remove it.
> + current_release = get_current_
> +
> + # Do we have a pocket and, if yes, can we upload to it?
> + pocket = get_property_
> + if not (pocket is None ...
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange wrote:
> Review: Needs Fixing
> Hi Muharem,
>
> Thanks for looking into this and following up on our implementation
> call. This branch is good, but it's missing a crucial behaviour: we
> only want this extended permission checking for officially linked
> source package branches. That is, for branches like
> lp:ubuntu/karmic/ssss rather than
> lp:~jml/ubuntu/karmic/ssss/thingamajig.
Hello Jonathan,
thanks for looking :) What is the recommended way to test for an
officially linked branch? Test the URL?
Check for the presence of a SeriesSourcePac
Use SeriesSourcePac
> This linking is also tied into how we determine the pocket for a
> branch. I've made some comments on this below.
>
> Let me know if you have any questions.
>
> jml
>
>> === modified file 'lib/canonical/
>> --- lib/canonical/
>> +++ lib/canonical/
>> @@ -7,9 +7,10 @@
>> __all__ = ['Authorization
>>
>> from zope.interface import implements, Interface
>> -from zope.component import getAdapter, getUtility
>> +from zope.component import getUtility
>>
>> from canonical.
>> +from lp.archiveuploa
>> from lp.registry.
>> from lp.soyuz.
>> from lp.soyuz.
>> @@ -1591,7 +1592,57 @@
>>
>> def checkAuthentica
>> return (user.inTeam(
>> - user_has_
>> + user_has_
>> + can_upload_
>> +
>> +
>> +def can_upload_
>> + """True if person may upload the package linked to `branch`."""
>
> Blank line here please.
Yup.
>
>> + def get_current_
>> + """Get current release for the source package linked to branch."""
>> + ds = branch.distroseries
>> + package = branch.
>> + releases = ds.getCurrentSo
>> + return releases.
>> +
>> + def get_property_
>> + """Get value of requested property or None."""
>> + value = None
>> + try:
>> + value = getattr(obj, property_name)
>> + except AttributeError:
>> + return None
>> + return value
>> +
>
> I don't know what the purpose of this method is. 'getattr(obj, name, None)'
> will do exactly the same thing.
Whoops .. sorry .. EHACKINGLATE :P
>> + # Check whether we're dealing with a source package branch and
>> + # whether person is authorised to upload the respective source
>> + # package.
>> + if branch.
>> + # No package .. hmm .. this can't be a source package branch
>> + # then. Abort.
>> + return False
>> +
>> + if branch.distroseries is None:
>> + # No distro ...
Jonathan Lange (jml) wrote : | # |
On Mon, Oct 19, 2009 at 12:51 PM, Muharem Hrnjadovic
<email address hidden> wrote:
> Jonathan Lange wrote:
>> Review: Needs Fixing
>> Hi Muharem,
>>
>> Thanks for looking into this and following up on our implementation
>> call. Â This branch is good, but it's missing a crucial behaviour: we
>> only want this extended permission checking for officially linked
>> source package branches. Â That is, for branches like
>> lp:ubuntu/karmic/ssss rather than
>> lp:~jml/ubuntu/karmic/ssss/thingamajig.
>
> Hello Jonathan,
>
> thanks for looking :) What is the recommended way to test for an
> officially linked branch? Test the URL?
> Check for the presence of a SeriesSourcePac
> Use SeriesSourcePac
>
I would use getUtility(
would do this check instead of checking that the branch is a
sourcepackage branch, and I would check for upload rights to any of
the packages that are officially linked.
...
>>
>>> + Â Â def get_current_
>>> + Â Â Â Â """Get current release for the source package linked to branch."""
>>> + Â Â Â Â ds = branch.distroseries
>>> + Â Â Â Â package = branch.
>>> + Â Â Â Â releases = ds.getCurrentSo
>>> + Â Â Â Â return releases.
>>> +
>>> + Â Â def get_property_
>>> + Â Â Â Â """Get value of requested property or None."""
>>> + Â Â Â Â value = None
>>> + Â Â Â Â try:
>>> + Â Â Â Â Â Â value = getattr(obj, property_name)
>>> + Â Â Â Â except AttributeError:
>>> + Â Â Â Â Â Â return None
>>> + Â Â Â Â return value
>>> +
>>
>> I don't know what the purpose of this method is. 'getattr(obj, name, None)'
>> will do exactly the same thing.
>
> Whoops .. sorry .. EHACKINGLATE :P
>
No worries :)
>>> + Â Â # Check whether we're dealing with a source package branch and
>>> + Â Â # whether person is authorised to upload the respective source
>>> + Â Â # package.
>>> + Â Â if branch.
>>> + Â Â Â Â # No package .. hmm .. this can't be a source package branch
>>> + Â Â Â Â # then. Abort.
>>> + Â Â Â Â return False
>>> +
>>> + Â Â if branch.distroseries is None:
>>> + Â Â Â Â # No distro series? Abort.
>>> + Â Â Â Â return False
>>> +
>>
>> There's a database constraint to ensure that you cannot have
>> sourcepackage without distroseries. I don't have a problem with doing
>> an extra check here, but you can also feel free to remove it.
>
> OK. Will do.
>
As I mention above, I think that if you use findForBranch, you won't
need these checks at all.
>>> + Â Â current_release = get_current_
>>> +
>>> + Â Â # Do we have a pocket and, if yes, can we upload to it?
>>> + Â Â pocket = get_property_
>>> + Â Â if not (pocket is None or branch.
>>> + Â Â Â Â return False
>>> +
>>
>> People have different preferences here, but I find this way of
>> phrasing the 'if' statement to be a little confusing. I mentally have
>> to apply DeMorgan's Law.
>>
>> if pocket is not None and not branch.
>> Â Â return False
>
> Fair enough.
>
M...
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange wrote:
> On Mon, Oct 19, 2009 at 12:51 PM, Muharem Hrnjadovic
> <email address hidden> wrote:
>> Jonathan Lange wrote:
>>> Review: Needs Fixing
>>> Hi Muharem,
>>>
>>> Thanks for looking into this and following up on our implementation
>>> call. This branch is good, but it's missing a crucial behaviour: we
>>> only want this extended permission checking for officially linked
>>> source package branches. That is, for branches like
>>> lp:ubuntu/karmic/ssss rather than
>>> lp:~jml/ubuntu/karmic/ssss/thingamajig.
>> Hello Jonathan,
>>
>> thanks for looking :) What is the recommended way to test for an
>> officially linked branch? Test the URL?
>> Check for the presence of a SeriesSourcePac
>> Use SeriesSourcePac
>>
>
> I would use getUtility(
> would do this check instead of checking that the branch is a
> sourcepackage branch, and I would check for upload rights to any of
> the packages that are officially linked.
Packages? Is this a one-branch-
looking at the code it looked like a 1-to-1 relationship to me ..
Anyway, after revising the code as suggested I get security exceptions
(please see http://
I guess the ISeriesSourcePa
a read-only and an edit portion respectively. As it stands all access
to it requires the "launchpad.Edit" permissions.
<class
class="
<allow
interface=
<require
set_schema=
</class>
>
> ...
>>>> + def get_current_
>>>> + """Get current release for the source package linked to branch."""
>>>> + ds = branch.distroseries
>>>> + package = branch.
>>>> + releases = ds.getCurrentSo
>>>> + return releases.
>>>> +
>>>> + def get_property_
>>>> + """Get value of requested property or None."""
>>>> + value = None
>>>> + try:
>>>> + value = getattr(obj, property_name)
>>>> + except AttributeError:
>>>> + return None
>>>> + return value
>>>> +
>>> I don't know what the purpose of this method is. 'getattr(obj, name, None)'
>>> will do exactly the same thing.
>> Whoops .. sorry .. EHACKINGLATE :P
>>
>
> No worries :)
>
>>>> + # Check whether we're dealing with a source package branch and
>>>> + # whether person is authorised to upload the respective source
>>>> + # package.
>>>> + if branch.
>>>> + # No package .. hmm .. this can't be a source package branch
>>>> + # then. Abort.
>>>> + return False
>>>> +
>>>> + if branch.distroseries is None:
>>>> + # No distro series? Abort.
>>>> + return False
>>>> +
>>> There's a database constraint to ensure that y...
Jonathan Lange (jml) wrote : | # |
On Mon, Oct 19, 2009 at 3:15 PM, Muharem Hrnjadovic
<email address hidden> wrote:
> Jonathan Lange wrote:
>> On Mon, Oct 19, 2009 at 12:51 PM, Muharem Hrnjadovic
>> <email address hidden> wrote:
>>> Jonathan Lange wrote:
>>>> Review: Needs Fixing
>>>> Hi Muharem,
>>>>
>>>> Thanks for looking into this and following up on our implementation
>>>> call. Â This branch is good, but it's missing a crucial behaviour: we
>>>> only want this extended permission checking for officially linked
>>>> source package branches. Â That is, for branches like
>>>> lp:ubuntu/karmic/ssss rather than
>>>> lp:~jml/ubuntu/karmic/ssss/thingamajig.
>>> Hello Jonathan,
>>>
>>> thanks for looking :) What is the recommended way to test for an
>>> officially linked branch? Test the URL?
>>> Check for the presence of a SeriesSourcePac
>>> Use SeriesSourcePac
>>>
>>
>> I would use getUtility(
>> would do this check instead of checking that the branch is a
>> sourcepackage branch, and I would check for upload rights to any of
>> the packages that are officially linked.
>
> Packages? Is this a one-branch-
> looking at the code it looked like a 1-to-1 relationship to me ..
>
One branch can belong to at most one package.
However, a branch can be an official package branch for many
(sourcepackage, pocket) combinations, even if the branch itself does
not belong to a source package. This is deliberate.
> Anyway, after revising the code as suggested I get security exceptions
> (please see http://
> I guess the ISeriesSourcePa
> a read-only and an edit portion respectively. As it stands all access
> to it requires the "launchpad.Edit" permissions.
>
> Â <class
>
> class="
> Â Â <allow
> interface=
> Â Â <require
> Â Â Â Â permission=
>
> set_schema=
> Â </class>
>
Something's wrong here.
ISeriesSourcePa
package branches (e.g. lp:ubuntu/emacs-snapshot). This works
anonymously. (bazaar_identity calls
branch.
IFindOfficialBr
From the pastebin,
ForbiddenAttr
This means that you cannot get pocket from a ResultSet. This makes
sense, since result sets are storm objects and don't have pockets.
File "/home/
line 1616, in can_upload_
if not branch.
AttributeError: 'NoneType' object has no attribute 'canUploadToPocket'
This error is correct, since the condition is checking the wrong
thing. The question is not, "can we upload to this pocket in the
distroseries that the branch belongs to" but rather "can we upload to
this pocket in the dist...
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange wrote:
> On Mon, Oct 19, 2009 at 3:15 PM, Muharem Hrnjadovic
> <email address hidden> wrote:
>> Jonathan Lange wrote:
>>> On Mon, Oct 19, 2009 at 12:51 PM, Muharem Hrnjadovic
>>> <email address hidden> wrote:
>>>> Jonathan Lange wrote:
>>>>> Review: Needs Fixing
[..]
>>>> thanks for looking :) What is the recommended way to test for an
>>>> officially linked branch? Test the URL?
>>>> Check for the presence of a SeriesSourcePac
>>>> Use SeriesSourcePac
>>>>
>>> I would use getUtility(
>>> would do this check instead of checking that the branch is a
>>> sourcepackage branch, and I would check for upload rights to any of
>>> the packages that are officially linked.
Right. This is what the revised can_upload_
does.
Please see the enclosed incremental diff for details.
>> Packages? Is this a one-branch-
>> looking at the code it looked like a 1-to-1 relationship to me ..
>>
>
> One branch can belong to at most one package.
>
> However, a branch can be an official package branch for many
> (sourcepackage, pocket) combinations, even if the branch itself does
> not belong to a source package. This is deliberate.
>
>> Anyway, after revising the code as suggested I get security exceptions
>> (please see http://
>> I guess the ISeriesSourcePa
>> a read-only and an edit portion respectively. As it stands all access
>> to it requires the "launchpad.Edit" permissions.
>>
>> <class
>>
>> class="
>> <allow
>> interface=
>> <require
>> permission=
>>
>> set_schema=
>> </class>
>>
>
> Something's wrong here.
>
> ISeriesSourcePa
> package branches (e.g. lp:ubuntu/emacs-snapshot). This works
> anonymously. (bazaar_identity calls
> branch.
> IFindOfficialBr
>
>>From the pastebin,
> ForbiddenAttribute: ('pocket', <storm.
>
> This means that you cannot get pocket from a ResultSet. This makes
> sense, since result sets are storm objects and don't have pockets.
>
> File "/home/
> line 1616, in can_upload_
> if not branch.
> AttributeError: 'NoneType' object has no attribute 'canUploadToPocket'
>
> This error is correct, since the condition is checking the wrong
> thing. The question is not, "can we upload to this pocket in the
> distroseries that the branch belongs to" but rather "can we upload to
> this pocket in the distroseries that the branch is official for".
>
> i.e. sspb.distroseri
I am both relieved (that the error is so easil...
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2009-10-19 10:33:14 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2009-10-20 07:51:12 +0000 |
4 | @@ -84,7 +84,8 @@ |
5 | from lp.registry.interfaces.productseries import IProductSeries |
6 | from lp.registry.interfaces.project import IProject, IProjectSet |
7 | from lp.code.interfaces.seriessourcepackagebranch import ( |
8 | - ISeriesSourcePackageBranch, IMakeOfficialBranchLinks) |
9 | + IFindOfficialBranchLinks, IMakeOfficialBranchLinks, |
10 | + ISeriesSourcePackageBranch) |
11 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
12 | from lp.soyuz.interfaces.sourcepackagerelease import ( |
13 | ISourcePackageRelease) |
14 | @@ -1598,48 +1599,47 @@ |
15 | |
16 | def can_upload_linked_package(person, branch): |
17 | """True if person may upload the package linked to `branch`.""" |
18 | - def get_current_release(): |
19 | + |
20 | + def get_current_release(sspb): |
21 | """Get current release for the source package linked to branch.""" |
22 | - ds = branch.distroseries |
23 | - package = branch.sourcepackage |
24 | - releases = ds.getCurrentSourceReleases([package.sourcepackagename]) |
25 | + package = sspb.sourcepackage |
26 | + releases = sspb.distroseries.getCurrentSourceReleases( |
27 | + [package.sourcepackagename]) |
28 | return releases.get(package, None) |
29 | |
30 | - def get_property_value(obj, property_name): |
31 | - """Get value of requested property or None.""" |
32 | - value = None |
33 | - try: |
34 | - value = getattr(obj, property_name) |
35 | - except AttributeError: |
36 | - return None |
37 | - return value |
38 | - |
39 | - # Check whether we're dealing with a source package branch and |
40 | - # whether person is authorised to upload the respective source |
41 | + # No associated `SeriesSourcePackageBranch` data -> not an official |
42 | + # branch. Abort. |
43 | + result_set = getUtility(IFindOfficialBranchLinks).findForBranch(branch) |
44 | + if result_set.count() < 1: |
45 | + return False |
46 | + |
47 | + # XXX al-maisan, 2009-10-20: a branch may currently be associated with a |
48 | + # number of (distroseries, sourcepackagename, pocket) combinations. |
49 | + # This does not seem right. But until the database model is fixed we work |
50 | + # around this by assuming that things are fine as long as we find at least |
51 | + # one combination that allows us to upload the corresponding source |
52 | # package. |
53 | - if branch.sourcepackage is None: |
54 | - # No package .. hmm .. this can't be a source package branch |
55 | - # then. Abort. |
56 | - return False |
57 | - |
58 | - if branch.distroseries is None: |
59 | - # No distro series? Abort. |
60 | - return False |
61 | - |
62 | - current_release = get_current_release() |
63 | - |
64 | - # Do we have a pocket and, if yes, can we upload to it? |
65 | - pocket = get_property_value(current_release, 'pocket') |
66 | - if not (pocket is None or branch.distroseries.canUploadToPocket(pocket)): |
67 | - return False |
68 | - |
69 | - archive = branch.distroseries.distribution.main_archive |
70 | - component = get_property_value(current_release, 'component') |
71 | + |
72 | + # See whether we can upload to any of the distro series/pocket |
73 | + # combinations. |
74 | + sspb = None |
75 | + for sspb in result_set: |
76 | + # Can we upload to the respective pocket? |
77 | + if sspb.distroseries.canUploadToPocket(sspb.pocket): |
78 | + break |
79 | + else: |
80 | + # Loop terminated normally i.e. we could not upload to any of the |
81 | + # (distroseries, pocket) combinations found. |
82 | + return False |
83 | + |
84 | + archive = sspb.distroseries.distribution.main_archive |
85 | + # Find the component the linked source package was published in. |
86 | + current_release = get_current_release(sspb) |
87 | + component = getattr(current_release, 'component', None) |
88 | |
89 | # Is person authorised to upload the source package this branch |
90 | # is targeting? |
91 | - result = verify_upload( |
92 | - person, branch.sourcepackage.sourcepackagename, archive, component) |
93 | + result = verify_upload(person, sspb.sourcepackagename, archive, component) |
94 | # verify_upload() indicates that person *is* allowed to upload by |
95 | # returning None. |
96 | return result is None |
97 | |
98 | === modified file 'lib/lp/code/tests/test_branch.py' |
99 | --- lib/lp/code/tests/test_branch.py 2009-10-19 10:12:44 +0000 |
100 | +++ lib/lp/code/tests/test_branch.py 2009-10-20 07:39:30 +0000 |
101 | @@ -17,6 +17,8 @@ |
102 | BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel, |
103 | CodeReviewNotificationLevel) |
104 | from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch |
105 | +from lp.registry.interfaces.distroseries import DistroSeriesStatus |
106 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
107 | from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet |
108 | from lp.testing import run_with_login, TestCaseWithFactory |
109 | |
110 | @@ -232,7 +234,14 @@ |
111 | def makeOfficialPackageBranch(self): |
112 | """Make a branch linked to the pocket of a source package.""" |
113 | branch = self.factory.makePackageBranch() |
114 | - pocket = self.factory.getAnyPocket() |
115 | + # Make sure the (distroseries, pocket) combination used allows us to |
116 | + # upload to it. |
117 | + stable_states = ( |
118 | + DistroSeriesStatus.SUPPORTED, DistroSeriesStatus.CURRENT) |
119 | + if branch.distroseries.status in stable_states: |
120 | + pocket = PackagePublishingPocket.BACKPORTS |
121 | + else: |
122 | + pocket = PackagePublishingPocket.RELEASE |
123 | sourcepackage = branch.sourcepackage |
124 | suite_sourcepackage = sourcepackage.getSuiteSourcePackage(pocket) |
125 | registrant = self.factory.makePerson() |
Jonathan Lange (jml) wrote : | # |
Looks good, thanks.
You might want to try:
sspbs = branch.
rather than:
result_set = getUtility(
(I only remembered the first one after I mentioned the second).
But no big deal.
Thanks again,
jml
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange wrote:
> Review: Approve
> Looks good, thanks.
>
> You might want to try:
> sspbs = branch.
>
> rather than:
> result_set = getUtility(
>
> (I only remembered the first one after I mentioned the second).
Done. Please see attached incremental diff.
Thanks for suggesting this!
Best regards
--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2009-10-20 07:53:28 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2009-10-20 09:45:16 +0000 |
4 | @@ -84,8 +84,7 @@ |
5 | from lp.registry.interfaces.productseries import IProductSeries |
6 | from lp.registry.interfaces.project import IProject, IProjectSet |
7 | from lp.code.interfaces.seriessourcepackagebranch import ( |
8 | - IFindOfficialBranchLinks, IMakeOfficialBranchLinks, |
9 | - ISeriesSourcePackageBranch) |
10 | + IMakeOfficialBranchLinks, ISeriesSourcePackageBranch) |
11 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
12 | from lp.soyuz.interfaces.sourcepackagerelease import ( |
13 | ISourcePackageRelease) |
14 | @@ -1600,17 +1599,20 @@ |
15 | def can_upload_linked_package(person, branch): |
16 | """True if person may upload the package linked to `branch`.""" |
17 | |
18 | - def get_current_release(sspb): |
19 | - """Get current release for the source package linked to branch.""" |
20 | - package = sspb.sourcepackage |
21 | - releases = sspb.distroseries.getCurrentSourceReleases( |
22 | + def get_current_release(ssp): |
23 | + """Get current release for the source package linked to branch. |
24 | + |
25 | + This function uses the `ISuiteSourcePackage` instance supplied. |
26 | + """ |
27 | + package = ssp.sourcepackage |
28 | + releases = ssp.distroseries.getCurrentSourceReleases( |
29 | [package.sourcepackagename]) |
30 | return releases.get(package, None) |
31 | |
32 | - # No associated `SeriesSourcePackageBranch` data -> not an official |
33 | - # branch. Abort. |
34 | - result_set = getUtility(IFindOfficialBranchLinks).findForBranch(branch) |
35 | - if result_set.count() < 1: |
36 | + # No associated `ISuiteSourcePackage` data -> not an official branch. |
37 | + # Abort. |
38 | + ssp_list = branch.associatedSuiteSourcePackages() |
39 | + if len(ssp_list) < 1: |
40 | return False |
41 | |
42 | # XXX al-maisan, 2009-10-20: a branch may currently be associated with a |
43 | @@ -1620,26 +1622,26 @@ |
44 | # one combination that allows us to upload the corresponding source |
45 | # package. |
46 | |
47 | - # See whether we can upload to any of the distro series/pocket |
48 | - # combinations. |
49 | - sspb = None |
50 | - for sspb in result_set: |
51 | + # Go through the associated `ISuiteSourcePackage` instances and see |
52 | + # whether we can upload to any of the distro series/pocket combinations. |
53 | + ssp = None |
54 | + for ssp in ssp_list: |
55 | # Can we upload to the respective pocket? |
56 | - if sspb.distroseries.canUploadToPocket(sspb.pocket): |
57 | + if ssp.distroseries.canUploadToPocket(ssp.pocket): |
58 | break |
59 | else: |
60 | # Loop terminated normally i.e. we could not upload to any of the |
61 | # (distroseries, pocket) combinations found. |
62 | return False |
63 | |
64 | - archive = sspb.distroseries.distribution.main_archive |
65 | + archive = ssp.distroseries.distribution.main_archive |
66 | # Find the component the linked source package was published in. |
67 | - current_release = get_current_release(sspb) |
68 | + current_release = get_current_release(ssp) |
69 | component = getattr(current_release, 'component', None) |
70 | |
71 | # Is person authorised to upload the source package this branch |
72 | # is targeting? |
73 | - result = verify_upload(person, sspb.sourcepackagename, archive, component) |
74 | + result = verify_upload(person, ssp.sourcepackagename, archive, component) |
75 | # verify_upload() indicates that person *is* allowed to upload by |
76 | # returning None. |
77 | return result is None |
Jonathan Lange (jml) wrote : | # |
On Tue, Oct 20, 2009 at 10:54 AM, Muharem Hrnjadovic
<email address hidden> wrote:
> Jonathan Lange wrote:
>> Review: Approve
>> Looks good, thanks.
>>
>> You might want to try:
>> Â sspbs = branch.
>>
>> rather than:
>> Â result_set = getUtility(
>>
>> (I only remembered the first one after I mentioned the second).
>
> Done. Please see attached incremental diff.
>
> Thanks for suggesting this!
>
My pleasure.
Please go ahead and merge the branch.
jml
Preview Diff
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2009-10-16 15:00:55 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2009-10-20 09:49:18 +0000 |
4 | @@ -7,9 +7,10 @@ |
5 | __all__ = ['AuthorizationBase'] |
6 | |
7 | from zope.interface import implements, Interface |
8 | -from zope.component import getAdapter, getUtility |
9 | +from zope.component import getUtility |
10 | |
11 | from canonical.launchpad.interfaces.account import IAccount |
12 | +from lp.archiveuploader.permission import verify_upload |
13 | from lp.registry.interfaces.announcement import IAnnouncement |
14 | from lp.soyuz.interfaces.archive import IArchive |
15 | from lp.soyuz.interfaces.archivepermission import ( |
16 | @@ -83,7 +84,7 @@ |
17 | from lp.registry.interfaces.productseries import IProductSeries |
18 | from lp.registry.interfaces.project import IProject, IProjectSet |
19 | from lp.code.interfaces.seriessourcepackagebranch import ( |
20 | - ISeriesSourcePackageBranch, IMakeOfficialBranchLinks) |
21 | + IMakeOfficialBranchLinks, ISeriesSourcePackageBranch) |
22 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
23 | from lp.soyuz.interfaces.sourcepackagerelease import ( |
24 | ISourcePackageRelease) |
25 | @@ -1591,7 +1592,59 @@ |
26 | |
27 | def checkAuthenticated(self, user): |
28 | return (user.inTeam(self.obj.owner) or |
29 | - user_has_special_branch_access(user)) |
30 | + user_has_special_branch_access(user) or |
31 | + can_upload_linked_package(user, self.obj)) |
32 | + |
33 | + |
34 | +def can_upload_linked_package(person, branch): |
35 | + """True if person may upload the package linked to `branch`.""" |
36 | + |
37 | + def get_current_release(ssp): |
38 | + """Get current release for the source package linked to branch. |
39 | + |
40 | + This function uses the `ISuiteSourcePackage` instance supplied. |
41 | + """ |
42 | + package = ssp.sourcepackage |
43 | + releases = ssp.distroseries.getCurrentSourceReleases( |
44 | + [package.sourcepackagename]) |
45 | + return releases.get(package, None) |
46 | + |
47 | + # No associated `ISuiteSourcePackage` data -> not an official branch. |
48 | + # Abort. |
49 | + ssp_list = branch.associatedSuiteSourcePackages() |
50 | + if len(ssp_list) < 1: |
51 | + return False |
52 | + |
53 | + # XXX al-maisan, 2009-10-20: a branch may currently be associated with a |
54 | + # number of (distroseries, sourcepackagename, pocket) combinations. |
55 | + # This does not seem right. But until the database model is fixed we work |
56 | + # around this by assuming that things are fine as long as we find at least |
57 | + # one combination that allows us to upload the corresponding source |
58 | + # package. |
59 | + |
60 | + # Go through the associated `ISuiteSourcePackage` instances and see |
61 | + # whether we can upload to any of the distro series/pocket combinations. |
62 | + ssp = None |
63 | + for ssp in ssp_list: |
64 | + # Can we upload to the respective pocket? |
65 | + if ssp.distroseries.canUploadToPocket(ssp.pocket): |
66 | + break |
67 | + else: |
68 | + # Loop terminated normally i.e. we could not upload to any of the |
69 | + # (distroseries, pocket) combinations found. |
70 | + return False |
71 | + |
72 | + archive = ssp.distroseries.distribution.main_archive |
73 | + # Find the component the linked source package was published in. |
74 | + current_release = get_current_release(ssp) |
75 | + component = getattr(current_release, 'component', None) |
76 | + |
77 | + # Is person authorised to upload the source package this branch |
78 | + # is targeting? |
79 | + result = verify_upload(person, ssp.sourcepackagename, archive, component) |
80 | + # verify_upload() indicates that person *is* allowed to upload by |
81 | + # returning None. |
82 | + return result is None |
83 | |
84 | |
85 | class AdminBranch(AuthorizationBase): |
86 | |
87 | === modified file 'lib/lp/code/tests/test_branch.py' |
88 | --- lib/lp/code/tests/test_branch.py 2009-06-25 04:06:00 +0000 |
89 | +++ lib/lp/code/tests/test_branch.py 2009-10-20 09:49:18 +0000 |
90 | @@ -8,50 +8,104 @@ |
91 | from zope.component import getUtility |
92 | from zope.security.proxy import removeSecurityProxy |
93 | |
94 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
95 | +from canonical.launchpad.webapp.authorization import check_permission |
96 | +from canonical.testing import DatabaseFunctionalLayer |
97 | + |
98 | +from lp.archiveuploader.permission import verify_upload |
99 | from lp.code.enums import ( |
100 | BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel, |
101 | CodeReviewNotificationLevel) |
102 | -from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
103 | -from canonical.launchpad.security import AccessBranch |
104 | -from lp.testing import TestCaseWithFactory |
105 | -from canonical.testing import DatabaseFunctionalLayer |
106 | - |
107 | - |
108 | -class TestAccessBranch(TestCaseWithFactory): |
109 | +from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch |
110 | +from lp.registry.interfaces.distroseries import DistroSeriesStatus |
111 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
112 | +from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet |
113 | +from lp.testing import run_with_login, TestCaseWithFactory |
114 | + |
115 | + |
116 | +class PermissionTest(TestCaseWithFactory): |
117 | |
118 | layer = DatabaseFunctionalLayer |
119 | |
120 | - def assertAuthenticatedAccess(self, branch, person, can_access): |
121 | - branch = removeSecurityProxy(branch) |
122 | - self.assertEqual( |
123 | - can_access, AccessBranch(branch).checkAuthenticated(person)) |
124 | - |
125 | - def assertUnauthenticatedAccess(self, branch, can_access): |
126 | - branch = removeSecurityProxy(branch) |
127 | - self.assertEqual( |
128 | - can_access, AccessBranch(branch).checkUnauthenticated()) |
129 | + def assertPermission(self, can_access, person, secure_object, permission): |
130 | + """Assert that 'person' can or cannot access 'secure_object'. |
131 | + |
132 | + :param can_access: Whether or not the person can access the object. |
133 | + :param person: The `IPerson` who is trying to access the object. |
134 | + :param secure_object: The secured object. |
135 | + :param permission: The Launchpad permission that 'person' is trying to |
136 | + access 'secure_object' with. |
137 | + """ |
138 | + self.assertEqual( |
139 | + can_access, |
140 | + run_with_login( |
141 | + person, check_permission, permission, secure_object)) |
142 | + |
143 | + def assertAuthenticatedView(self, branch, person, can_access): |
144 | + """Can 'branch' be accessed by 'person'? |
145 | + |
146 | + :param branch: The `IBranch` we're curious about. |
147 | + :param person: The `IPerson` trying to access it. |
148 | + :param can_access: Whether we expect 'person' be able to access it. |
149 | + """ |
150 | + self.assertPermission(can_access, person, branch, 'launchpad.View') |
151 | + |
152 | + def assertUnauthenticatedView(self, branch, can_access): |
153 | + """Can 'branch' be accessed anonymously? |
154 | + |
155 | + :param branch: The `IBranch` we're curious about. |
156 | + :param can_access: Whether we expect to access it anonymously. |
157 | + """ |
158 | + self.assertAuthenticatedView(branch, None, can_access) |
159 | + |
160 | + def assertCanEdit(self, person, secured_object): |
161 | + """Assert 'person' can edit 'secured_object'. |
162 | + |
163 | + That is, assert 'person' has 'launchpad.Edit' permissions on |
164 | + 'secured_object'. |
165 | + |
166 | + :param person: An `IPerson`. None means anonymous. |
167 | + :param secured_object: An object, secured through the Zope security |
168 | + layer. |
169 | + """ |
170 | + self.assertPermission(True, person, secured_object, 'launchpad.Edit') |
171 | + |
172 | + def assertCannotEdit(self, person, secured_object): |
173 | + """Assert 'person' cannot edit 'secured_object'. |
174 | + |
175 | + That is, assert 'person' does not have 'launchpad.Edit' permissions on |
176 | + 'secured_object'. |
177 | + |
178 | + :param person: An `IPerson`. None means anonymous. |
179 | + :param secured_object: An object, secured through the Zope security |
180 | + layer. |
181 | + """ |
182 | + self.assertPermission(False, person, secured_object, 'launchpad.Edit') |
183 | + |
184 | + |
185 | +class TestAccessBranch(PermissionTest): |
186 | |
187 | def test_publicBranchUnauthenticated(self): |
188 | # Public branches can be accessed without authentication. |
189 | branch = self.factory.makeAnyBranch() |
190 | - self.assertUnauthenticatedAccess(branch, True) |
191 | + self.assertUnauthenticatedView(branch, True) |
192 | |
193 | def test_publicBranchArbitraryUser(self): |
194 | # Public branches can be accessed by anyone. |
195 | branch = self.factory.makeAnyBranch() |
196 | person = self.factory.makePerson() |
197 | - self.assertAuthenticatedAccess(branch, person, True) |
198 | + self.assertAuthenticatedView(branch, person, True) |
199 | |
200 | def test_privateBranchUnauthenticated(self): |
201 | # Private branches cannot be accessed without authentication. |
202 | branch = self.factory.makeAnyBranch(private=True) |
203 | - self.assertUnauthenticatedAccess(branch, False) |
204 | + self.assertUnauthenticatedView(branch, False) |
205 | |
206 | def test_privateBranchOwner(self): |
207 | # The owner of a branch can always access it. |
208 | owner = self.factory.makePerson() |
209 | branch = self.factory.makeAnyBranch(private=True, owner=owner) |
210 | - self.assertAuthenticatedAccess(branch, owner, True) |
211 | + self.assertAuthenticatedView(branch, owner, True) |
212 | |
213 | def test_privateBranchOwnerMember(self): |
214 | # Any member of the team that owns the branch can access it. |
215 | @@ -60,19 +114,20 @@ |
216 | person = self.factory.makePerson() |
217 | removeSecurityProxy(team).addMember(person, team_owner) |
218 | branch = self.factory.makeAnyBranch(private=True, owner=team) |
219 | - self.assertAuthenticatedAccess(branch, person, True) |
220 | + self.assertAuthenticatedView(branch, person, True) |
221 | |
222 | def test_privateBranchBazaarExperts(self): |
223 | # The Bazaar experts can access any branch. |
224 | celebs = getUtility(ILaunchpadCelebrities) |
225 | branch = self.factory.makeAnyBranch(private=True) |
226 | - self.assertAuthenticatedAccess(branch, celebs.bazaar_experts, True) |
227 | + self.assertAuthenticatedView( |
228 | + branch, celebs.bazaar_experts.teamowner, True) |
229 | |
230 | def test_privateBranchAdmins(self): |
231 | # Launchpad admins can access any branch. |
232 | celebs = getUtility(ILaunchpadCelebrities) |
233 | branch = self.factory.makeAnyBranch(private=True) |
234 | - self.assertAuthenticatedAccess(branch, celebs.admin, True) |
235 | + self.assertAuthenticatedView(branch, celebs.admin.teamowner, True) |
236 | |
237 | def test_privateBranchSubscriber(self): |
238 | # If you are subscribed to a branch, you can access it. |
239 | @@ -82,13 +137,13 @@ |
240 | person, BranchSubscriptionNotificationLevel.NOEMAIL, |
241 | BranchSubscriptionDiffSize.NODIFF, |
242 | CodeReviewNotificationLevel.NOEMAIL) |
243 | - self.assertAuthenticatedAccess(branch, person, True) |
244 | + self.assertAuthenticatedView(branch, person, True) |
245 | |
246 | def test_privateBranchAnyoneElse(self): |
247 | # In general, you can't access a private branch. |
248 | branch = self.factory.makeAnyBranch(private=True) |
249 | person = self.factory.makePerson() |
250 | - self.assertAuthenticatedAccess(branch, person, False) |
251 | + self.assertAuthenticatedView(branch, person, False) |
252 | |
253 | def test_stackedOnPrivateBranchUnauthenticated(self): |
254 | # If a branch is stacked on a private branch, then you cannot access |
255 | @@ -96,7 +151,7 @@ |
256 | stacked_on_branch = self.factory.makeAnyBranch(private=True) |
257 | stacked_branch = self.factory.makeAnyBranch( |
258 | stacked_on=stacked_on_branch) |
259 | - self.assertUnauthenticatedAccess(stacked_branch, False) |
260 | + self.assertUnauthenticatedView(stacked_branch, False) |
261 | |
262 | def test_stackedOnPrivateBranchAuthenticated(self): |
263 | # If a branch is stacked on a private branch, you can only access it |
264 | @@ -105,7 +160,7 @@ |
265 | stacked_branch = self.factory.makeAnyBranch( |
266 | stacked_on=stacked_on_branch) |
267 | person = self.factory.makePerson() |
268 | - self.assertAuthenticatedAccess(stacked_branch, person, False) |
269 | + self.assertAuthenticatedView(stacked_branch, person, False) |
270 | |
271 | def test_manyLevelsOfStackingUnauthenticated(self): |
272 | # If a branch is stacked on a branch stacked on a private branch, you |
273 | @@ -113,7 +168,7 @@ |
274 | stacked_on_branch = self.factory.makeAnyBranch(private=True) |
275 | branch_a = self.factory.makeAnyBranch(stacked_on=stacked_on_branch) |
276 | branch_b = self.factory.makeAnyBranch(stacked_on=branch_a) |
277 | - self.assertUnauthenticatedAccess(branch_b, False) |
278 | + self.assertUnauthenticatedView(branch_b, False) |
279 | |
280 | def test_manyLevelsOfStackingAuthenticated(self): |
281 | # If a branch is stacked on a branch stacked on a private branch, you |
282 | @@ -122,7 +177,7 @@ |
283 | branch_a = self.factory.makeAnyBranch(stacked_on=stacked_on_branch) |
284 | branch_b = self.factory.makeAnyBranch(stacked_on=branch_a) |
285 | person = self.factory.makePerson() |
286 | - self.assertAuthenticatedAccess(branch_b, person, False) |
287 | + self.assertAuthenticatedView(branch_b, person, False) |
288 | |
289 | def test_loopedPublicStackedOn(self): |
290 | # It's possible, although nonsensical, for branch stackings to form a |
291 | @@ -132,7 +187,7 @@ |
292 | stacked_branch = self.factory.makeAnyBranch() |
293 | removeSecurityProxy(stacked_branch).stacked_on = stacked_branch |
294 | person = self.factory.makePerson() |
295 | - self.assertAuthenticatedAccess(stacked_branch, person, True) |
296 | + self.assertAuthenticatedView(stacked_branch, person, True) |
297 | |
298 | def test_loopedPrivateStackedOn(self): |
299 | # It's possible, although nonsensical, for branch stackings to form a |
300 | @@ -142,7 +197,7 @@ |
301 | stacked_branch = self.factory.makeAnyBranch(private=True) |
302 | removeSecurityProxy(stacked_branch).stacked_on = stacked_branch |
303 | person = self.factory.makePerson() |
304 | - self.assertAuthenticatedAccess(stacked_branch, person, False) |
305 | + self.assertAuthenticatedView(stacked_branch, person, False) |
306 | |
307 | def test_loopedPublicStackedOnUnauthenticated(self): |
308 | # It's possible, although nonsensical, for branch stackings to form a |
309 | @@ -151,8 +206,106 @@ |
310 | # being logged in. |
311 | stacked_branch = self.factory.makeAnyBranch() |
312 | removeSecurityProxy(stacked_branch).stacked_on = stacked_branch |
313 | - self.assertUnauthenticatedAccess(stacked_branch, True) |
314 | - |
315 | + self.assertUnauthenticatedView(stacked_branch, True) |
316 | + |
317 | + |
318 | +class TestWriteToBranch(PermissionTest): |
319 | + """Test who can write to branches.""" |
320 | + |
321 | + def test_owner_can_write(self): |
322 | + # The owner of a branch can write to the branch. |
323 | + branch = self.factory.makeAnyBranch() |
324 | + self.assertCanEdit(branch.owner, branch) |
325 | + |
326 | + def test_random_person_cannot_write(self): |
327 | + # Arbitrary logged in people cannot write to branches. |
328 | + branch = self.factory.makeAnyBranch() |
329 | + person = self.factory.makePerson() |
330 | + self.assertCannotEdit(person, branch) |
331 | + |
332 | + def test_member_of_owning_team_can_write(self): |
333 | + # Members of the team that owns a branch can write to the branch. |
334 | + team = self.factory.makeTeam() |
335 | + person = self.factory.makePerson() |
336 | + removeSecurityProxy(team).addMember(person, team.teamowner) |
337 | + branch = self.factory.makeAnyBranch(owner=team) |
338 | + self.assertCanEdit(person, branch) |
339 | + |
340 | + def makeOfficialPackageBranch(self): |
341 | + """Make a branch linked to the pocket of a source package.""" |
342 | + branch = self.factory.makePackageBranch() |
343 | + # Make sure the (distroseries, pocket) combination used allows us to |
344 | + # upload to it. |
345 | + stable_states = ( |
346 | + DistroSeriesStatus.SUPPORTED, DistroSeriesStatus.CURRENT) |
347 | + if branch.distroseries.status in stable_states: |
348 | + pocket = PackagePublishingPocket.BACKPORTS |
349 | + else: |
350 | + pocket = PackagePublishingPocket.RELEASE |
351 | + sourcepackage = branch.sourcepackage |
352 | + suite_sourcepackage = sourcepackage.getSuiteSourcePackage(pocket) |
353 | + registrant = self.factory.makePerson() |
354 | + ubuntu_branches = getUtility(ILaunchpadCelebrities).ubuntu_branches |
355 | + run_with_login( |
356 | + ubuntu_branches.teamowner, |
357 | + ICanHasLinkedBranch(suite_sourcepackage).setBranch, |
358 | + branch, registrant) |
359 | + return branch |
360 | + |
361 | + def test_owner_can_write_to_official_package_branch(self): |
362 | + # The owner of an official package branch can write to it, just like a |
363 | + # regular person. |
364 | + branch = self.makeOfficialPackageBranch() |
365 | + self.assertCanEdit(branch.owner, branch) |
366 | + |
367 | + def assertCanUpload(self, person, spn, archive, component, |
368 | + strict_component=True): |
369 | + """Assert that 'person' can upload 'spn' to 'archive'.""" |
370 | + # For now, just check that doesn't raise an exception. |
371 | + self.assertIs( |
372 | + None, |
373 | + verify_upload(person, spn, archive, component, strict_component)) |
374 | + |
375 | + def assertCannotUpload(self, reason, person, spn, archive, component): |
376 | + """Assert that 'person' cannot upload to the archive. |
377 | + |
378 | + :param reason: The expected reason for not being able to upload. A |
379 | + string. |
380 | + :param person: The person trying to upload. |
381 | + :param spn: The `ISourcePackageName` being uploaded to. None if the |
382 | + package does not yet exist. |
383 | + :param archive: The `IArchive` being uploaded to. |
384 | + :param component: The IComponent to which the package belongs. |
385 | + """ |
386 | + exception = verify_upload(person, spn, archive, component) |
387 | + self.assertEqual(reason, str(exception)) |
388 | + |
389 | + def test_package_upload_permissions_grant_branch_edit(self): |
390 | + # If you can upload to the package, then you are also allowed to write |
391 | + # to the branch. |
392 | + |
393 | + permission_set = getUtility(IArchivePermissionSet) |
394 | + # Only admins or techboard members can add permissions normally. That |
395 | + # restriction isn't relevant to these tests. |
396 | + self.permission_set = removeSecurityProxy(permission_set) |
397 | + branch = self.makeOfficialPackageBranch() |
398 | + package = branch.sourcepackage |
399 | + person = self.factory.makePerson() |
400 | + |
401 | + # Person is not allowed to edit the branch presently. |
402 | + self.assertCannotEdit(person, branch) |
403 | + |
404 | + # Now give 'person' permission to upload to 'package'. |
405 | + archive = branch.distroseries.distribution.main_archive |
406 | + spn = package.sourcepackagename |
407 | + self.permission_set.newPackageUploader(archive, person, spn) |
408 | + # Make sure person *is* authorised to upload the source package |
409 | + # targeted by the branch at hand. |
410 | + self.assertCanUpload(person, spn, archive, None) |
411 | + |
412 | + # Now person can edit the branch on the basis of the upload |
413 | + # permissions granted above. |
414 | + self.assertCanEdit(person, branch) |
415 | |
416 | |
417 | def test_suite(): |
Hello there!
The branch at hand extends the 'launchpad.Edit' authorisation check for
branches targeting source packages as follows: if a user is allowed to
upload the linked source package he is also authorised to edit the
branch in question.
Pre-implementation call (belated) with jml.
Tests to run:
bin/test -t test_branch. TestWriteToBran ch
No lint errors or warning.