launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #20027
[Merge] lp:~cjwatson/launchpad/refactor-launchpad-container into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/refactor-launchpad-container into lp:launchpad.
Commit message:
Refactor LaunchpadContainer to work with scope URLs and to automatically follow the canonical URL chain.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/refactor-launchpad-container/+merge/287412
Refactor LaunchpadContainer to work with scope URLs and to automatically follow the canonical URL chain. This is in preparation for introducing caveats, where we'll definitely want to work with URL paths rather than objects.
LaunchpadPrincipal.scope_url will change again, but this is a relatively non-painful way to allow the LaunchpadContainer changes to land in isolation.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/refactor-launchpad-container into lp:launchpad.
=== modified file 'lib/lp/bugs/publisher.py'
--- lib/lp/bugs/publisher.py 2015-07-08 16:05:11 +0000
+++ lib/lp/bugs/publisher.py 2016-02-28 19:25:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Bugs' custom publication."""
@@ -55,12 +55,8 @@
class LaunchpadBugContainer(LaunchpadContainer):
- def isWithin(self, scope):
- """Is this bug within the given scope?
-
- A bug is in the scope of any of its bugtasks' targets.
- """
+ def getParentContainers(self):
+ """See `ILaunchpadContainer`."""
+ # A bug is within any of its bugtasks' targets.
for bugtask in self.context.bugtasks:
- if ILaunchpadContainer(bugtask.target).isWithin(scope):
- return True
- return False
+ yield ILaunchpadContainer(bugtask.target)
=== modified file 'lib/lp/code/publisher.py'
--- lib/lp/code/publisher.py 2015-07-08 16:05:11 +0000
+++ lib/lp/code/publisher.py 2016-02-28 19:25:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Code's custom publication."""
@@ -13,6 +13,7 @@
]
+from zope.component import queryAdapter
from zope.interface import implementer
from zope.publisher.interfaces.browser import (
IBrowserRequest,
@@ -56,12 +57,10 @@
class LaunchpadBranchContainer(LaunchpadContainer):
- def isWithin(self, scope):
- """Is this branch within the given scope?
-
- If a branch has a product, it is always in the scope that product or
- its project. Otherwise it's not in any scope.
- """
- if self.context.product is None:
- return False
- return ILaunchpadContainer(self.context.product).isWithin(scope)
+ def getParentContainers(self):
+ """See `ILaunchpadContainer`."""
+ # A branch is within its target.
+ adapter = queryAdapter(
+ self.context.target.context, ILaunchpadContainer)
+ if adapter is not None:
+ yield adapter
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2016-02-05 20:28:29 +0000
+++ lib/lp/registry/configure.zcml 2016-02-28 19:25:32 +0000
@@ -613,7 +613,7 @@
<adapter
for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
provides="lp.services.webapp.interfaces.ILaunchpadContainer"
- factory="lp.registry.publisher.LaunchpadDistributionSourcePackageContainer"/>
+ factory="lp.services.webapp.publisher.LaunchpadContainer"/>
<adapter
provides="lp.app.interfaces.launchpad.IServiceUsage"
for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
=== modified file 'lib/lp/registry/doc/launchpad-container.txt'
--- lib/lp/registry/doc/launchpad-container.txt 2015-01-29 18:43:52 +0000
+++ lib/lp/registry/doc/launchpad-container.txt 2016-02-28 19:25:32 +0000
@@ -17,51 +17,51 @@
>>> ubuntu = getUtility(IDistributionSet)['ubuntu']
>>> evolution = ubuntu.getSourcePackage('evolution')
-The ILaunchpadContainer defines only the isWithin(context) method, which
-returns True if this context is the given one or is within it.
+The ILaunchpadContainer defines only the isWithin(scope_url) method, which
+returns True if this context is at the given URL or is within it.
A product is within itself or its project group.
- >>> ILaunchpadContainer(firefox).isWithin(firefox)
- True
- >>> ILaunchpadContainer(firefox).isWithin(mozilla)
- True
- >>> ILaunchpadContainer(firefox).isWithin(ubuntu)
+ >>> ILaunchpadContainer(firefox).isWithin('/firefox')
+ True
+ >>> ILaunchpadContainer(firefox).isWithin('/mozilla')
+ True
+ >>> ILaunchpadContainer(firefox).isWithin('/ubuntu')
False
>>> verifyObject(ILaunchpadContainer, ILaunchpadContainer(firefox))
True
A project group is only within itself.
- >>> ILaunchpadContainer(mozilla).isWithin(mozilla)
+ >>> ILaunchpadContainer(mozilla).isWithin('/mozilla')
True
- >>> ILaunchpadContainer(mozilla).isWithin(firefox)
+ >>> ILaunchpadContainer(mozilla).isWithin('/firefox')
False
- >>> ILaunchpadContainer(mozilla).isWithin(ubuntu)
+ >>> ILaunchpadContainer(mozilla).isWithin('/ubuntu')
False
>>> verifyObject(ILaunchpadContainer, ILaunchpadContainer(mozilla))
True
A distribution is only within itself.
- >>> ILaunchpadContainer(ubuntu).isWithin(ubuntu)
+ >>> ILaunchpadContainer(ubuntu).isWithin('/ubuntu')
True
- >>> ILaunchpadContainer(ubuntu).isWithin(mozilla)
+ >>> ILaunchpadContainer(ubuntu).isWithin('/mozilla')
False
- >>> ILaunchpadContainer(ubuntu).isWithin(firefox)
+ >>> ILaunchpadContainer(ubuntu).isWithin('/firefox')
False
>>> verifyObject(ILaunchpadContainer, ILaunchpadContainer(ubuntu))
True
A distribution source package is within itself or its distribution.
- >>> ILaunchpadContainer(evolution).isWithin(evolution)
- True
- >>> ILaunchpadContainer(evolution).isWithin(ubuntu)
- True
- >>> ILaunchpadContainer(evolution).isWithin(firefox)
+ >>> ILaunchpadContainer(evolution).isWithin('/ubuntu/+source/evolution')
+ True
+ >>> ILaunchpadContainer(evolution).isWithin('/ubuntu')
+ True
+ >>> ILaunchpadContainer(evolution).isWithin('/firefox')
False
- >>> ILaunchpadContainer(evolution).isWithin(mozilla)
+ >>> ILaunchpadContainer(evolution).isWithin('/mozilla')
False
>>> verifyObject(ILaunchpadContainer, ILaunchpadContainer(evolution))
True
@@ -69,9 +69,7 @@
An ILaunchpadContainer will never be within something which doesn't
provide ILaunchpadContainer as well.
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> salgado = getUtility(IPersonSet).getByName('salgado')
- >>> ILaunchpadContainer(firefox).isWithin(salgado)
+ >>> ILaunchpadContainer(firefox).isWithin('/~salgado')
False
@@ -80,12 +78,14 @@
Bugs are associated to our pillars through their bug tasks, so a bug is
said to be within any of its bugtasks' targets.
+ >>> from operator import attrgetter
>>> from lp.bugs.interfaces.bug import IBugSet
- >>> from operator import attrgetter
+ >>> from lp.services.webapp.publisher import canonical_url
>>> bug_1 = getUtility(IBugSet).get(1)
>>> bugtasks = bug_1.bugtasks
>>> targets = [task.target for task in bug_1.bugtasks]
- >>> [(target.title, ILaunchpadContainer(bug_1).isWithin(target))
+ >>> [(target.title, ILaunchpadContainer(bug_1).isWithin(
+ ... canonical_url(target, force_local_path=True)))
... for target in sorted(targets, key=attrgetter('title'))]
[(u'Mozilla Firefox', True),
(...mozilla-firefox... package in Debian', True),
@@ -95,29 +95,28 @@
>>> evolution in targets
False
- >>> ILaunchpadContainer(bug_1).isWithin(evolution)
+ >>> ILaunchpadContainer(bug_1).isWithin('/ubuntu/+source/evolution')
False
== Branches ==
-A branch is within its product, in case it is associated with one.
+A branch is within its target.
+ >>> from lp.code.interfaces.branchnamespace import get_branch_namespace
+ >>> from lp.registry.interfaces.person import IPersonSet
>>> sample_person = getUtility(IPersonSet).getByName('name12')
- >>> from lp.code.interfaces.branchnamespace import (
- ... get_branch_namespace)
>>> firefox_main = get_branch_namespace(
... sample_person, product=firefox).getByName('main')
- >>> ILaunchpadContainer(firefox_main).isWithin(firefox)
- True
- >>> ILaunchpadContainer(firefox_main).isWithin(mozilla)
- True
-
-If the branch is not associated with a product, then it's not within
-anything.
-
- >>> junk= get_branch_namespace(sample_person).getByName('junk.dev')
+ >>> ILaunchpadContainer(firefox_main).isWithin('/firefox')
+ True
+ >>> ILaunchpadContainer(firefox_main).isWithin('/mozilla')
+ True
+
+But it's not within anything other than its target.
+
+ >>> junk = get_branch_namespace(sample_person).getByName('junk.dev')
>>> print junk.product
None
- >>> ILaunchpadContainer(junk).isWithin(firefox)
+ >>> ILaunchpadContainer(junk).isWithin('/firefox')
False
=== modified file 'lib/lp/registry/publisher.py'
--- lib/lp/registry/publisher.py 2015-01-29 16:28:30 +0000
+++ lib/lp/registry/publisher.py 2016-02-28 19:25:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""ILaunchpadContainer adapters."""
@@ -6,29 +6,17 @@
__metaclass__ = type
__all__ = [
'LaunchpadProductContainer',
- 'LaunchpadDistributionSourcePackageContainer',
]
+from lp.services.webapp.interfaces import ILaunchpadContainer
from lp.services.webapp.publisher import LaunchpadContainer
class LaunchpadProductContainer(LaunchpadContainer):
- def isWithin(self, scope):
- """Is this product within the given scope?
-
- A product is within itself or its project group.
- """
-
- return scope == self.context or scope == self.context.projectgroup
-
-
-class LaunchpadDistributionSourcePackageContainer(LaunchpadContainer):
-
- def isWithin(self, scope):
- """Is this distribution source package within the given scope?
-
- A distribution source package is within its distribution.
- """
- return scope == self.context or scope == self.context.distribution
+ def getParentContainers(self):
+ """See `ILaunchpadContainer`."""
+ # A project is within its project group.
+ if self.context.projectgroup is not None:
+ yield ILaunchpadContainer(self.context.projectgroup)
=== modified file 'lib/lp/services/webapp/authentication.py'
--- lib/lp/services/webapp/authentication.py 2016-01-26 15:14:01 +0000
+++ lib/lp/services/webapp/authentication.py 2016-02-28 19:25:32 +0000
@@ -176,7 +176,7 @@
"""
def getPrincipal(self, id, access_level=AccessLevel.WRITE_PRIVATE,
- scope=None):
+ scope_url=None):
"""Return an `ILaunchpadPrincipal` for the account with the given id.
Return None if there is no account with the given id.
@@ -184,9 +184,9 @@
The `access_level` can be used for further restricting the capability
of the principal. By default, no further restriction is added.
- Similarly, when a `scope` is given, the principal's capabilities will
- apply only to things within that scope. For everything else that is
- not private, the principal will have only read access.
+ Similarly, when a `scope_url` is given, the principal's capabilities
+ will apply only to things within that scope. For everything else
+ that is not private, the principal will have only read access.
Note that we currently need to be able to retrieve principals for
invalid People, as the login machinery needs the principal to
@@ -198,14 +198,14 @@
except LookupError:
return None
- return self._principalForAccount(account, access_level, scope)
+ return self._principalForAccount(account, access_level, scope_url)
def getPrincipals(self, name):
raise NotImplementedError
def getPrincipalByLogin(self, login,
access_level=AccessLevel.WRITE_PRIVATE,
- scope=None):
+ scope_url=None):
"""Return a principal based on the account with the email address
signified by "login".
@@ -214,9 +214,9 @@
The `access_level` can be used for further restricting the capability
of the principal. By default, no further restriction is added.
- Similarly, when a `scope` is given, the principal's capabilities will
- apply only to things within that scope. For everything else that is
- not private, the principal will have only read access.
+ Similarly, when a `scope_url` is given, the principal's capabilities
+ will apply only to things within that scope. For everything else
+ that is not private, the principal will have only read access.
Note that we currently need to be able to retrieve principals for
@@ -227,9 +227,10 @@
person = getUtility(IPersonSet).getByEmail(login, filter_status=False)
if person is None or person.account is None:
return None
- return self._principalForAccount(person.account, access_level, scope)
+ return self._principalForAccount(
+ person.account, access_level, scope_url)
- def _principalForAccount(self, account, access_level, scope):
+ def _principalForAccount(self, account, access_level, scope_url):
"""Return a LaunchpadPrincipal for the given account.
The LaunchpadPrincipal will also have the given access level and
@@ -239,7 +240,7 @@
principal = LaunchpadPrincipal(
naked_account.id, naked_account.displayname,
naked_account.displayname, account,
- access_level=access_level, scope=scope)
+ access_level=access_level, scope_url=scope_url)
principal.__parent__ = self
return principal
@@ -254,12 +255,12 @@
class LaunchpadPrincipal:
def __init__(self, id, title, description, account,
- access_level=AccessLevel.WRITE_PRIVATE, scope=None):
+ access_level=AccessLevel.WRITE_PRIVATE, scope_url=None):
self.id = unicode(id)
self.title = title
self.description = description
self.access_level = access_level
- self.scope = scope
+ self.scope_url = scope_url
self.account = account
self.person = IPerson(account, None)
=== modified file 'lib/lp/services/webapp/authorization.py'
--- lib/lp/services/webapp/authorization.py 2015-07-08 16:05:11 +0000
+++ lib/lp/services/webapp/authorization.py 2016-02-28 19:25:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -120,11 +120,11 @@
principal's scope, the original access level is returned. Otherwise
the access level is READ_PUBLIC.
"""
- if principal.scope is None:
+ if principal.scope_url is None:
return principal.access_level
else:
container = nearest_adapter(object, ILaunchpadContainer)
- if container.isWithin(principal.scope):
+ if container.isWithin(principal.scope_url):
return principal.access_level
else:
return AccessLevel.READ_PUBLIC
=== modified file 'lib/lp/services/webapp/doc/webapp-authorization.txt'
--- lib/lp/services/webapp/doc/webapp-authorization.txt 2012-08-07 02:31:56 +0000
+++ lib/lp/services/webapp/doc/webapp-authorization.txt 2016-02-28 19:25:32 +0000
@@ -122,7 +122,7 @@
>>> private_bug = getUtility(IBugSet).get(14)
>>> logout()
>>> principal.access_level = AccessLevel.WRITE_PRIVATE
- >>> principal.scope = firefox
+ >>> principal.scope_url = '/firefox'
>>> setupInteraction(principal)
>>> check_permission('launchpad.Edit', firefox)
True
@@ -146,7 +146,7 @@
If the scope is a ProjectGroup or Distribution, then the access level will
be used for anything which is part of that ProjectGroup/Distribution.
- >>> principal.scope = mozilla
+ >>> principal.scope_url = '/mozilla'
>>> setupInteraction(principal)
>>> check_permission('launchpad.Edit', mozilla)
True
@@ -157,7 +157,7 @@
... IDistributionSet)
>>> ubuntu = getUtility(IDistributionSet)['ubuntu']
>>> warty = ubuntu.getSeries('warty')
- >>> principal.scope = ubuntu
+ >>> principal.scope_url = '/ubuntu'
>>> setupInteraction(principal)
>>> check_permission('launchpad.Edit', ubuntu)
True
@@ -175,7 +175,7 @@
in turn is within Mozilla), so the user's access level will apply to
that bug task as well.
- >>> principal.scope = mozilla
+ >>> principal.scope_url = '/mozilla'
>>> setupInteraction(principal)
>>> from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
>>> bug_task = firefox.searchTasks(
@@ -185,7 +185,7 @@
If no scope is specified, the access level will be used for everything.
- >>> principal.scope = None
+ >>> principal.scope_url = None
>>> setupInteraction(principal)
>>> check_permission('launchpad.Edit', ubuntu)
True
=== modified file 'lib/lp/services/webapp/doc/webapp-publication.txt'
--- lib/lp/services/webapp/doc/webapp-publication.txt 2016-02-08 12:20:20 +0000
+++ lib/lp/services/webapp/doc/webapp-publication.txt 2016-02-28 19:25:32 +0000
@@ -1090,8 +1090,8 @@
Guilherme Salgado
>>> principal.access_level
<DBItem AccessLevel.WRITE_PUBLIC...
- >>> principal.scope.name
- u'firefox'
+ >>> principal.scope_url
+ u'/firefox'
If the token is expired or doesn't exist, an Unauthorized exception is
raised, though.
=== modified file 'lib/lp/services/webapp/interfaces.py'
--- lib/lp/services/webapp/interfaces.py 2016-01-26 15:47:37 +0000
+++ lib/lp/services/webapp/interfaces.py 2016-02-28 19:25:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -41,8 +41,11 @@
class ILaunchpadContainer(Interface):
"""Marker interface for objects used as the context of something."""
- def isWithin(scope):
- """Return True if this context is within the given scope."""
+ def getParentContainers():
+ """Return the containers of each parent of this context."""
+
+ def isWithin(scope_url):
+ """Return True if this context is within the given scope URL."""
class ILaunchpadRoot(IContainmentRoot):
=== modified file 'lib/lp/services/webapp/publisher.py'
--- lib/lp/services/webapp/publisher.py 2016-02-08 12:20:20 +0000
+++ lib/lp/services/webapp/publisher.py 2016-02-28 19:25:32 +0000
@@ -77,6 +77,7 @@
defaultFlagValue,
getFeatureFlag,
)
+from lp.services.propertycache import cachedproperty
from lp.services.utils import obfuscate_structure
from lp.services.webapp.interfaces import (
ICanonicalUrlData,
@@ -844,13 +845,36 @@
def __init__(self, context):
self.context = context
- def isWithin(self, scope):
- """Is this object within the given scope?
-
- By default all objects are only within itself. More specific adapters
- must override this and implement the logic they want.
+ @cachedproperty
+ def _context_url(self):
+ try:
+ return canonical_url(self.context, force_local_path=True)
+ except NoCanonicalUrl:
+ return None
+
+ def getParentContainers(self):
+ """See `ILaunchpadContainer`.
+
+ By default, we only consider the parent of this object in the
+ canonical URL iteration. Adapters for objects with more complex
+ parentage rules must override this method.
"""
- return self.context == scope
+ # Circular import.
+ from lp.services.webapp.canonicalurl import nearest_adapter
+ urldata = ICanonicalUrlData(self.context, None)
+ if urldata is not None and urldata.inside is not None:
+ container = nearest_adapter(urldata.inside, ILaunchpadContainer)
+ yield container
+
+ def isWithin(self, scope_url):
+ """See `ILaunchpadContainer`."""
+ if self._context_url is None:
+ return False
+ if self._context_url == scope_url:
+ return True
+ return any(
+ parent.isWithin(scope_url)
+ for parent in self.getParentContainers())
@implementer(IBrowserPublisher)
=== modified file 'lib/lp/services/webapp/servers.py'
--- lib/lp/services/webapp/servers.py 2016-02-10 00:51:55 +0000
+++ lib/lp/services/webapp/servers.py 2016-02-28 19:25:32 +0000
@@ -101,7 +101,10 @@
)
from lp.services.webapp.opstats import OpStats
from lp.services.webapp.publication import LaunchpadBrowserPublication
-from lp.services.webapp.publisher import RedirectionView
+from lp.services.webapp.publisher import (
+ canonical_url,
+ RedirectionView,
+ )
from lp.services.webapp.vhosts import allvhosts
from lp.services.webservice.interfaces import IWebServiceApplication
from lp.testopenid.interfaces.server import ITestOpenIDApplication
@@ -1331,9 +1334,13 @@
# Everything is fine, let's return the principal.
pass
alsoProvides(request, IOAuthSignedRequest)
+ if token.context is not None:
+ scope_url = canonical_url(token.context, force_local_path=True)
+ else:
+ scope_url = None
principal = getUtility(IPlacelessLoginSource).getPrincipal(
token.person.account.id, access_level=token.permission,
- scope=token.context)
+ scope_url=scope_url)
return principal
=== modified file 'lib/lp/services/webapp/tests/test_authorization.py'
--- lib/lp/services/webapp/tests/test_authorization.py 2015-07-08 16:05:11 +0000
+++ lib/lp/services/webapp/tests/test_authorization.py 2016-02-28 19:25:32 +0000
@@ -1,10 +1,11 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for `lp.services.webapp.authorization`."""
__metaclass__ = type
+from itertools import count
from random import getrandbits
import StringIO
@@ -38,10 +39,12 @@
)
from lp.services.webapp.interfaces import (
AccessLevel,
+ ICanonicalUrlData,
ILaunchpadContainer,
ILaunchpadPrincipal,
)
from lp.services.webapp.metazcml import ILaunchpadPermission
+from lp.services.webapp.publisher import LaunchpadContainer
from lp.services.webapp.servers import (
LaunchpadBrowserRequest,
LaunchpadTestRequest,
@@ -182,7 +185,7 @@
class FakeLaunchpadPrincipal:
"""A minimal principal implementing `ILaunchpadPrincipal`"""
person = FakePerson()
- scope = None
+ scope_url = None
access_level = ''
@@ -385,12 +388,13 @@
self.security = LaunchpadSecurityPolicy()
provideAdapter(
adapt_loneobject_to_container, [ILoneObject], ILaunchpadContainer)
+ provideAdapter(LoneObjectURL, [ILoneObject], ICanonicalUrlData)
self.addCleanup(zope.testing.cleanup.cleanUp)
def test_no_scope(self):
"""Principal's access level is used when no scope is given."""
self.principal.access_level = AccessLevel.WRITE_PUBLIC
- self.principal.scope = None
+ self.principal.scope_url = None
self.failUnlessEqual(
self.security._getPrincipalsAccessLevel(
self.principal, LoneObject()),
@@ -400,7 +404,7 @@
"""Principal's access level is used when object is within scope."""
obj = LoneObject()
self.principal.access_level = AccessLevel.WRITE_PUBLIC
- self.principal.scope = obj
+ self.principal.scope_url = '/+loneobject/%d' % obj.id
self.failUnlessEqual(
self.security._getPrincipalsAccessLevel(self.principal, obj),
self.principal.access_level)
@@ -409,7 +413,7 @@
"""READ_PUBLIC is used when object is /not/ within scope."""
obj = LoneObject()
obj2 = LoneObject() # This is out of obj's scope.
- self.principal.scope = obj
+ self.principal.scope_url = '/+loneobject/%d' % obj.id
self.principal.access_level = AccessLevel.WRITE_PUBLIC
self.failUnlessEqual(
@@ -431,11 +435,31 @@
"""A marker interface for objects that only contain themselves."""
-@implementer(ILoneObject, ILaunchpadContainer)
-class LoneObject:
-
- def isWithin(self, context):
- return self == context
+@implementer(ILoneObject)
+class LoneObject(LaunchpadContainer):
+
+ _id_counter = count(1)
+
+ def __init__(self):
+ super(LoneObject, self).__init__(self)
+ self.id = LoneObject._id_counter.next()
+
+ def getParentContainers(self):
+ return []
+
+
+@implementer(ICanonicalUrlData)
+class LoneObjectURL:
+
+ rootsite = None
+ inside = None
+
+ def __init__(self, loneobject):
+ self.loneobject = loneobject
+
+ @property
+ def path(self):
+ return '+loneobject/%d' % self.loneobject.id
def adapt_loneobject_to_container(loneobj):
=== removed file 'lib/lp/services/webapp/tests/test_webapp_authorization.py'
--- lib/lp/services/webapp/tests/test_webapp_authorization.py 2015-10-14 15:22:01 +0000
+++ lib/lp/services/webapp/tests/test_webapp_authorization.py 1970-01-01 00:00:00 +0000
@@ -1,86 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-__metaclass__ = type
-
-from unittest import TestCase
-
-from zope.component import provideAdapter
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.testing.cleanup import CleanUp
-
-from lp.services.webapp.authentication import LaunchpadPrincipal
-from lp.services.webapp.authorization import LaunchpadSecurityPolicy
-from lp.services.webapp.interfaces import (
- AccessLevel,
- ILaunchpadContainer,
- )
-
-
-class TestLaunchpadSecurityPolicy_getPrincipalsAccessLevel(
- CleanUp, TestCase):
-
- def setUp(self):
- self.principal = LaunchpadPrincipal(
- 'foo.bar@xxxxxxxxxxxxx', 'foo', 'foo', object())
- self.security = LaunchpadSecurityPolicy()
- provideAdapter(
- adapt_loneobject_to_container, [ILoneObject], ILaunchpadContainer)
-
- def test_no_scope(self):
- """Principal's access level is used when no scope is given."""
- self.principal.access_level = AccessLevel.WRITE_PUBLIC
- self.principal.scope = None
- self.failUnlessEqual(
- self.security._getPrincipalsAccessLevel(
- self.principal, LoneObject()),
- self.principal.access_level)
-
- def test_object_within_scope(self):
- """Principal's access level is used when object is within scope."""
- obj = LoneObject()
- self.principal.access_level = AccessLevel.WRITE_PUBLIC
- self.principal.scope = obj
- self.failUnlessEqual(
- self.security._getPrincipalsAccessLevel(self.principal, obj),
- self.principal.access_level)
-
- def test_object_not_within_scope(self):
- """READ_PUBLIC is used when object is /not/ within scope."""
- obj = LoneObject()
- obj2 = LoneObject() # This is out of obj's scope.
- self.principal.scope = obj
-
- self.principal.access_level = AccessLevel.WRITE_PUBLIC
- self.failUnlessEqual(
- self.security._getPrincipalsAccessLevel(self.principal, obj2),
- AccessLevel.READ_PUBLIC)
-
- self.principal.access_level = AccessLevel.READ_PRIVATE
- self.failUnlessEqual(
- self.security._getPrincipalsAccessLevel(self.principal, obj2),
- AccessLevel.READ_PUBLIC)
-
- self.principal.access_level = AccessLevel.WRITE_PRIVATE
- self.failUnlessEqual(
- self.security._getPrincipalsAccessLevel(self.principal, obj2),
- AccessLevel.READ_PUBLIC)
-
-
-class ILoneObject(Interface):
- """A marker interface for objects that only contain themselves."""
-
-
-@implementer(ILoneObject, ILaunchpadContainer)
-class LoneObject:
-
- def isWithin(self, context):
- return self == context
-
-
-def adapt_loneobject_to_container(loneobj):
- """Adapt a LoneObject to an `ILaunchpadContainer`."""
- return loneobj
Follow ups