launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28707
[Merge] ~lgp171188/launchpad:split-global-security.py-registry into launchpad:master
Guruprasad has proposed merging ~lgp171188/launchpad:split-global-security.py-registry into launchpad:master.
Commit message:
Move the registry-related security adapters to lp.registry.security
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/425938
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:split-global-security.py-registry into launchpad:master.
diff --git a/lib/lp/app/tests/test_security.py b/lib/lp/app/tests/test_security.py
index 870a2f8..f66f75f 100644
--- a/lib/lp/app/tests/test_security.py
+++ b/lib/lp/app/tests/test_security.py
@@ -14,7 +14,7 @@ from lp.registry.interfaces.teammembership import (
ITeamMembershipSet,
TeamMembershipStatus,
)
-from lp.security import PublicOrPrivateTeamsExistence
+from lp.registry.security import PublicOrPrivateTeamsExistence
from lp.testing import (
TestCase,
TestCaseWithFactory,
diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
index eefe667..f2d0433 100644
--- a/lib/lp/registry/configure.zcml
+++ b/lib/lp/registry/configure.zcml
@@ -10,6 +10,8 @@
xmlns:lp="http://namespaces.canonical.com/lp"
xmlns:webservice="http://namespaces.canonical.com/webservice"
i18n_domain="launchpad">
+
+ <authorizations module=".security" />
<include
file="vocabularies.zcml"/>
<include
diff --git a/lib/lp/registry/security.py b/lib/lp/registry/security.py
new file mode 100644
index 0000000..5d732dc
--- /dev/null
+++ b/lib/lp/registry/security.py
@@ -0,0 +1,1244 @@
+# Copyright 2009-2022 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Security adapters for the registry module."""
+
+__all__ = [
+ 'AdminDistributionTranslations',
+ 'AdminProductTranslations',
+ 'EditDistributionSourcePackage',
+]
+
+from storm.expr import (
+ Select,
+ Union,
+ )
+from zope.component import (
+ getUtility,
+ queryAdapter,
+ )
+
+from lp.app.interfaces.security import IAuthorization
+from lp.app.security import (
+ AnonymousAuthorization,
+ AuthorizationBase,
+ DelegatedAuthorization,
+ )
+from lp.blueprints.model.specificationsubscription import (
+ SpecificationSubscription,
+ )
+from lp.bugs.model.bugsubscription import BugSubscription
+from lp.bugs.model.bugtaskflat import BugTaskFlat
+from lp.bugs.model.bugtasksearch import get_bug_privacy_filter
+from lp.code.interfaces.branchcollection import (
+ IAllBranches,
+ IBranchCollection,
+ )
+from lp.code.interfaces.gitcollection import IGitCollection
+from lp.registry.enums import PersonVisibility
+from lp.registry.interfaces.announcement import IAnnouncement
+from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.distributionmirror import IDistributionMirror
+from lp.registry.interfaces.distributionsourcepackage import (
+ IDistributionSourcePackage,
+ )
+from lp.registry.interfaces.distroseries import IDistroSeries
+from lp.registry.interfaces.distroseriesdifference import (
+ IDistroSeriesDifferenceAdmin,
+ IDistroSeriesDifferenceEdit,
+ )
+from lp.registry.interfaces.distroseriesparent import IDistroSeriesParent
+from lp.registry.interfaces.gpg import IGPGKey
+from lp.registry.interfaces.irc import IIrcID
+from lp.registry.interfaces.location import IPersonLocation
+from lp.registry.interfaces.milestone import (
+ IMilestone,
+ IProjectGroupMilestone,
+ )
+from lp.registry.interfaces.nameblacklist import (
+ INameBlacklist,
+ INameBlacklistSet,
+ )
+from lp.registry.interfaces.packaging import IPackaging
+from lp.registry.interfaces.person import (
+ IPerson,
+ IPersonLimitedView,
+ IPersonSet,
+ ITeam,
+ )
+from lp.registry.interfaces.pillar import (
+ IPillar,
+ IPillarPerson,
+ )
+from lp.registry.interfaces.poll import (
+ IPoll,
+ IPollOption,
+ IPollSubset,
+ )
+from lp.registry.interfaces.product import (
+ IProduct,
+ IProductSet,
+ )
+from lp.registry.interfaces.productrelease import (
+ IProductRelease,
+ IProductReleaseFile,
+ )
+from lp.registry.interfaces.productseries import (
+ IProductSeries,
+ IProductSeriesLimitedView,
+ IProductSeriesView,
+ ITimelineProductSeries,
+ )
+from lp.registry.interfaces.projectgroup import (
+ IProjectGroup,
+ IProjectGroupSet,
+ )
+from lp.registry.interfaces.role import IHasDrivers
+from lp.registry.interfaces.ssh import ISSHKey
+from lp.registry.interfaces.teammembership import (
+ ITeamMembership,
+ TeamMembershipStatus,
+ )
+from lp.registry.interfaces.wikiname import IWikiName
+from lp.registry.model.person import Person
+from lp.security import (
+ AdminByAdminsTeam,
+ AdminByCommercialTeamOrAdmins,
+ EditByOwnersOrAdmins,
+ EditByRegistryExpertsOrAdmins,
+ EditSourcePackage,
+ is_commercial_case,
+ ModerateByRegistryExpertsOrAdmins,
+ OnlyRosettaExpertsAndAdmins,
+ )
+from lp.services.auth.interfaces import IAccessToken
+from lp.services.database.interfaces import IStore
+from lp.services.identity.interfaces.account import IAccount
+from lp.services.oauth.interfaces import (
+ IOAuthAccessToken,
+ IOAuthRequestToken,
+ )
+from lp.services.openid.interfaces.openididentifier import IOpenIdIdentifier
+from lp.services.worlddata.interfaces.country import ICountry
+from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
+from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
+from lp.soyuz.interfaces.distroarchseriesfilter import IDistroArchSeriesFilter
+
+
+def can_edit_team(team, user):
+ """Return True if the given user has edit rights for the given team."""
+ if user.in_admin:
+ return True
+ else:
+ return team in user.person.getAdministratedTeams()
+
+
+class ModerateDistroSeries(ModerateByRegistryExpertsOrAdmins):
+ usedfor = IDistroSeries
+
+
+class ModerateProduct(ModerateByRegistryExpertsOrAdmins):
+ usedfor = IProduct
+
+
+class ModerateProductSet(ModerateByRegistryExpertsOrAdmins):
+ usedfor = IProductSet
+
+
+class ModerateProject(ModerateByRegistryExpertsOrAdmins):
+ usedfor = IProjectGroup
+
+
+class ModerateProjectGroupSet(ModerateByRegistryExpertsOrAdmins):
+ usedfor = IProjectGroupSet
+
+
+class ModeratePerson(ModerateByRegistryExpertsOrAdmins):
+ permission = 'launchpad.Moderate'
+ usedfor = IPerson
+
+class ViewPillar(AuthorizationBase):
+ usedfor = IPillar
+ permission = 'launchpad.View'
+
+ def checkUnauthenticated(self):
+ return self.obj.active
+
+ def checkAuthenticated(self, user):
+ """The Admins & Commercial Admins can see inactive pillars."""
+ if self.obj.active:
+ return True
+ else:
+ return (user.in_commercial_admin or
+ user.in_admin or
+ user.in_registry_experts)
+
+
+class PillarPersonSharingDriver(AuthorizationBase):
+ usedfor = IPillarPerson
+ permission = 'launchpad.Driver'
+
+ def checkAuthenticated(self, user):
+ """Maintainers, drivers, and admins can drive projects."""
+ return (user.in_admin or
+ user.isOwner(self.obj.pillar) or
+ user.isDriver(self.obj.pillar))
+
+
+class EditAccountBySelfOrAdmin(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IAccount
+
+ def checkAuthenticated(self, user):
+ return user.in_admin or user.person.accountID == self.obj.id
+
+
+class ViewAccount(EditAccountBySelfOrAdmin):
+ permission = 'launchpad.View'
+
+ def checkAuthenticated(self, user):
+ """Extend permission to registry experts."""
+ return (
+ super().checkAuthenticated(user)
+ or user.in_registry_experts)
+
+
+class ModerateAccountByRegistryExpert(AuthorizationBase):
+ usedfor = IAccount
+ permission = 'launchpad.Moderate'
+
+ def checkAuthenticated(self, user):
+ return user.in_admin or user.in_registry_experts
+
+
+class ViewOpenIdIdentifierBySelfOrAdmin(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IOpenIdIdentifier
+
+ def checkAuthenticated(self, user):
+ return user.in_admin or user.person.accountID == self.obj.accountID
+
+
+class EditOAuthAccessToken(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IOAuthAccessToken
+
+ def checkAuthenticated(self, user):
+ return self.obj.person == user.person or user.in_admin
+
+
+class EditOAuthRequestToken(EditOAuthAccessToken):
+ permission = 'launchpad.Edit'
+ usedfor = IOAuthRequestToken
+
+
+class EditAccessToken(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IAccessToken
+
+ def checkAuthenticated(self, user):
+ if user.inTeam(self.obj.owner):
+ return True
+ # Being able to edit the token doesn't allow extracting the secret,
+ # so it's OK to allow the owner of the target to do so too. This
+ # allows target owners to exercise some control over access to their
+ # object.
+ adapter = queryAdapter(
+ self.obj.target, IAuthorization, 'launchpad.Edit')
+ if adapter is not None and adapter.checkAuthenticated(user):
+ return True
+ return False
+
+
+class ViewProduct(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IProduct
+
+ def checkAuthenticated(self, user):
+ return self.obj.userCanView(user)
+
+ def checkUnauthenticated(self):
+ return self.obj.userCanView(None)
+
+
+class LimitedViewProduct(ViewProduct):
+ permission = 'launchpad.LimitedView'
+ usedfor = IProduct
+
+ def checkAuthenticated(self, user):
+ return (
+ super().checkAuthenticated(user) or
+ self.obj.userCanLimitedView(user))
+
+
+class EditProduct(EditByOwnersOrAdmins):
+ usedfor = IProduct
+
+ def checkAuthenticated(self, user):
+ # Commercial admins may help setup commercial projects.
+ return (
+ super().checkAuthenticated(user)
+ or is_commercial_case(self.obj, user)
+ or False)
+
+
+class EditPackaging(EditByOwnersOrAdmins):
+ usedfor = IPackaging
+
+
+class EditProductRelease(EditByOwnersOrAdmins):
+ permission = 'launchpad.Edit'
+ usedfor = IProductRelease
+
+ def checkAuthenticated(self, user):
+ if (user.isOwner(self.obj.productseries.product) or
+ user.isDriver(self.obj.productseries)):
+ # The user is an owner or a release manager.
+ return True
+ return EditByOwnersOrAdmins.checkAuthenticated(
+ self, user)
+
+
+class EditProductReleaseFile(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IProductReleaseFile
+
+ def checkAuthenticated(self, user):
+ return EditProductRelease(self.obj.productrelease).checkAuthenticated(
+ user)
+
+
+class ViewTimelineProductSeries(DelegatedAuthorization):
+ """Anyone who can view the related product can also view an
+ ITimelineProductSeries.
+ """
+ permission = 'launchpad.View'
+ usedfor = ITimelineProductSeries
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.product, 'launchpad.View')
+
+
+class ViewProductReleaseFile(AnonymousAuthorization):
+ """Anyone can view an IProductReleaseFile."""
+ usedfor = IProductReleaseFile
+
+
+class AdminDistributionMirrorByDistroOwnerOrMirrorAdminsOrAdmins(
+ AuthorizationBase):
+ permission = 'launchpad.Admin'
+ usedfor = IDistributionMirror
+
+ def checkAuthenticated(self, user):
+ return (user.isOwner(self.obj.distribution) or
+ user.in_admin or
+ user.inTeam(self.obj.distribution.mirror_admin))
+
+
+class EditDistributionMirrorByOwnerOrDistroOwnerOrMirrorAdminsOrAdmins(
+ AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IDistributionMirror
+
+ def checkAuthenticated(self, user):
+ return (user.isOwner(self.obj) or user.in_admin or
+ user.isOwner(self.obj.distribution) or
+ user.inTeam(self.obj.distribution.mirror_admin))
+
+
+class ViewDistributionMirror(AnonymousAuthorization):
+ """Anyone can view an IDistributionMirror."""
+ usedfor = IDistributionMirror
+
+
+class AdminProjectTranslations(AuthorizationBase):
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IProjectGroup
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IProjectGroup` translations settings?
+
+ Any Launchpad/Launchpad Translations administrator or owner is
+ able to change translation settings for a project group.
+ """
+ return (user.isOwner(self.obj) or
+ user.in_rosetta_experts or
+ user.in_admin)
+
+
+class AdminProductTranslations(AuthorizationBase):
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IProduct
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IProduct` translations settings?
+
+ Any Launchpad/Launchpad Translations administrator or owners are
+ able to change translation settings for a product.
+ """
+ return (user.isOwner(self.obj) or
+ user.isDriver(self.obj) or
+ user.in_rosetta_experts or
+ user.in_admin)
+
+
+class ViewProjectMilestone(DelegatedAuthorization):
+ permission = 'launchpad.View'
+ usedfor = IProjectGroupMilestone
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.product, 'launchpad.View')
+
+
+class EditProjectMilestoneNever(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IProjectGroupMilestone
+
+ def checkAuthenticated(self, user):
+ """IProjectGroupMilestone is a fake content object."""
+ return False
+
+
+class LimitedViewMilestone(DelegatedAuthorization):
+ permission = 'launchpad.LimitedView'
+ usedfor = IMilestone
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.target, 'launchpad.LimitedView')
+
+
+class ViewMilestone(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IMilestone
+
+ def checkAuthenticated(self, user):
+ return self.obj.userCanView(user)
+
+ def checkUnauthenticated(self):
+ return self.obj.userCanView(user=None)
+
+
+class EditMilestoneByTargetOwnerOrAdmins(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IMilestone
+
+ def checkAuthenticated(self, user):
+ """Authorize the product or distribution owner."""
+ if user.in_admin:
+ return True
+ if user.isDriver(self.obj.series_target):
+ return True
+ return user.isOwner(self.obj.target)
+
+
+class AdminMilestoneByLaunchpadAdmins(AuthorizationBase):
+ permission = 'launchpad.Admin'
+ usedfor = IMilestone
+
+ def checkAuthenticated(self, user):
+ """Only the Launchpad admins need this, we are only going to use
+ it for connecting up series and distroseries where we did not
+ have them.
+ """
+ return user.in_admin
+
+
+class ModeratePersonSetByExpertsOrAdmins(ModerateByRegistryExpertsOrAdmins):
+ permission = 'launchpad.Moderate'
+ usedfor = IPersonSet
+
+
+class EditTeamByTeamOwnerOrLaunchpadAdmins(AuthorizationBase):
+ permission = 'launchpad.Owner'
+ usedfor = ITeam
+
+ def checkAuthenticated(self, user):
+ """Only the team owner and Launchpad admins need this.
+ """
+ return user.inTeam(self.obj.teamowner) or user.in_admin
+
+
+class EditTeamByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = ITeam
+
+ def checkAuthenticated(self, user):
+ """The team owner and team admins have launchpad.Edit on that team.
+
+ The Launchpad admins also have launchpad.Edit on all teams.
+ """
+ return can_edit_team(self.obj, user)
+
+
+class ModerateTeam(ModerateByRegistryExpertsOrAdmins):
+ permission = 'launchpad.Moderate'
+ usedfor = ITeam
+
+ def checkAuthenticated(self, user):
+ """Is the user a privileged team member or Launchpad staff?
+
+ Return true when the user is a member of Launchpad admins,
+ registry experts, team admins, or the team owners.
+ """
+ return (
+ super().checkAuthenticated(user)
+ or can_edit_team(self.obj, user))
+
+
+class EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = ITeamMembership
+
+ def checkAuthenticated(self, user):
+ return can_edit_team(self.obj.team, user)
+
+
+class ViewTeamMembership(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = ITeamMembership
+
+ def checkUnauthenticated(self):
+ """Unauthenticated users can only view public memberships."""
+ return self.obj.team.visibility == PersonVisibility.PUBLIC
+
+ def checkAuthenticated(self, user):
+ """Verify that the user can view the team's membership.
+
+ Anyone can see a public team's membership. Only a team member or
+ commercial admin or a Launchpad admin can view a private team.
+ """
+ if self.obj.team.visibility == PersonVisibility.PUBLIC:
+ return True
+ if (user.in_admin or user.in_commercial_admin
+ or user.inTeam(self.obj.team)):
+ return True
+ return False
+
+
+class AdminByCommercialTeamOrAdminsOrPerson(AdminByCommercialTeamOrAdmins):
+ permission = 'launchpad.Commercial'
+ usedfor = IPerson
+
+ def checkAuthenticated(self, user):
+ """Users can manage their commericial data and admins can help."""
+ return self.obj.id == user.id or super().checkAuthenticated(user)
+
+
+class EditPersonBySelfOrAdmins(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IPerson
+
+ def checkAuthenticated(self, user):
+ """A user can edit the Person who is themselves.
+
+ The admin team can also edit any Person.
+ """
+ return self.obj.id == user.id or user.in_admin
+
+
+class ViewPersonLocation(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IPersonLocation
+
+ def checkUnauthenticated(self):
+ return self.obj.visible
+
+ def checkAuthenticated(self, user):
+ if self.obj.visible:
+ return True
+ else:
+ return user.person == self.obj.person or user.in_admin
+
+
+class EditPersonBySelf(AuthorizationBase):
+ permission = 'launchpad.Special'
+ usedfor = IPerson
+
+ def checkAuthenticated(self, user):
+ """A user can edit the Person who is themselves."""
+ return self.obj.id == user.person.id
+
+
+class ViewPublicOrPrivateTeamMembers(AuthorizationBase):
+ """Restrict viewing of private teams.
+
+ Only members of a private team can view the
+ membership list.
+ """
+ permission = 'launchpad.View'
+ usedfor = IPerson
+
+ def checkUnauthenticated(self):
+ """Unauthenticated users can only view public memberships."""
+ if self.obj.visibility == PersonVisibility.PUBLIC:
+ return True
+ return False
+
+ def checkAuthenticated(self, user):
+ """Verify that the user can view the team's membership.
+
+ Anyone can see a public team's membership. Only a team member,
+ commercial admin, or a Launchpad admin can view a private team's
+ members.
+ """
+ if self.obj.visibility == PersonVisibility.PUBLIC:
+ return True
+ if user.in_admin or user.in_commercial_admin or user.inTeam(self.obj):
+ return True
+ # Private team owners have visibility.
+ if self.obj.is_team and user.inTeam(self.obj.teamowner):
+ return True
+ # We also grant visibility of the private team to administrators of
+ # other teams that have been invited to join the private team.
+ for invitee in self.obj.invited_members:
+ if (invitee.is_team and
+ invitee in user.person.getAdministratedTeams()):
+ return True
+ return False
+
+
+class PublicOrPrivateTeamsExistence(AuthorizationBase):
+ """Restrict knowing about private teams' existence.
+
+ Knowing the existence of a private team allow traversing to its URL and
+ displaying basic information like name, displayname.
+ """
+ permission = 'launchpad.LimitedView'
+ usedfor = IPersonLimitedView
+
+ def checkUnauthenticated(self):
+ """Unauthenticated users can only view public teams."""
+ if self.obj.visibility == PersonVisibility.PUBLIC:
+ return True
+ return False
+
+ def checkAuthenticated(self, user):
+ """By default, we simply perform a View permission check.
+
+ We also grant limited viewability to users who can see PPAs and
+ branches owned by the team, and members of parent teams so they can
+ see the member-listings.
+
+ In other scenarios, the context in which the permission is required is
+ responsible for pre-caching the launchpad.LimitedView permission on
+ each team which requires it.
+ """
+ if self.forwardCheckAuthenticated(
+ user, self.obj, 'launchpad.View'):
+ return True
+
+ if (self.obj.is_team
+ and self.obj.visibility == PersonVisibility.PRIVATE):
+ # Grant visibility to people with subscriptions on a private
+ # team's private PPA.
+ subscriptions = getUtility(
+ IArchiveSubscriberSet).getBySubscriber(user.person)
+ subscriber_archive_ids = {
+ sub.archive_id for sub in subscriptions}
+ team_ppa_ids = {
+ ppa.id for ppa in self.obj.ppas if ppa.private}
+ if len(subscriber_archive_ids.intersection(team_ppa_ids)) > 0:
+ return True
+
+ # Grant visibility to people who can see branches owned by the
+ # private team.
+ team_branches = IBranchCollection(self.obj)
+ if not team_branches.visibleByUser(user.person).is_empty():
+ return True
+
+ # Grant visibility to people who can see branches subscribed to
+ # by the private team.
+ team_branches = getUtility(IAllBranches).subscribedBy(self.obj)
+ if not team_branches.visibleByUser(user.person).is_empty():
+ return True
+
+ # Grant visibility to branches visible to the user and which have
+ # review requests for the private team.
+ branches = getUtility(IAllBranches)
+ visible_branches = branches.visibleByUser(user.person)
+ mp = visible_branches.getMergeProposalsForReviewer(self.obj)
+ if not mp.is_empty():
+ return True
+
+ # Grant visibility to people who can see Git repositories owned
+ # by the private team.
+ team_repositories = IGitCollection(self.obj)
+ if not team_repositories.visibleByUser(user.person).is_empty():
+ return True
+
+ # Grant visibility to people who own Git repositories that grant
+ # some kind of write access to the private team.
+ owned_repositories = IGitCollection(user.person)
+ grants = owned_repositories.getRuleGrantsForGrantee(self.obj)
+ if not grants.is_empty():
+ return True
+
+ # Grant visibility to users in a team that has the private team as
+ # a member, so that they can see the team properly in member
+ # listings.
+
+ # The easiest check is just to see if the user is in a team that
+ # is a super team for the private team.
+
+ # Do comparison by ids because they may be needed for comparison
+ # to membership.team.ids later.
+ user_teams = [
+ team.id for team in user.person.teams_participated_in]
+ super_teams = [team.id for team in self.obj.super_teams]
+ intersection_teams = set(user_teams) & set(super_teams)
+
+ if len(intersection_teams) > 0:
+ return True
+
+ # If it's not, the private team may still be a pending membership,
+ # deactivated membership, or an expired membership,
+ # which still needs to be visible to team members.
+ BAD_STATES = (
+ TeamMembershipStatus.DECLINED.value,
+ TeamMembershipStatus.INVITATION_DECLINED.value,
+ )
+ team_memberships_query = """
+ SELECT team from TeamMembership WHERE person = %s AND
+ status NOT IN %s
+ """ % (self.obj.id, BAD_STATES)
+ store = IStore(Person)
+ future_super_teams = [team[0] for team in
+ store.execute(team_memberships_query)]
+ intersection_teams = set(user_teams) & set(future_super_teams)
+
+ if len(intersection_teams) > 0:
+ return True
+
+ # Teams subscribed to blueprints are visible. This needs to
+ # be taught about privacy eventually.
+ specsubs = store.find(SpecificationSubscription, person=self.obj)
+
+ # Teams subscribed or assigned to bugs that the user can see
+ # are visible.
+ bugs = store.find(
+ BugTaskFlat,
+ get_bug_privacy_filter(user.person),
+ BugTaskFlat.bug_id.is_in(
+ Union(
+ Select(
+ BugSubscription.bug_id,
+ tables=(BugSubscription,),
+ where=BugSubscription.person == self.obj),
+ Select(
+ BugTaskFlat.bug_id,
+ tables=(BugTaskFlat,),
+ where=BugTaskFlat.assignee == self.obj),
+ all=True)))
+
+ if not specsubs.is_empty() or not bugs.is_empty():
+ return True
+ return False
+
+
+class EditPollByTeamOwnerOrTeamAdminsOrAdmins(
+ EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins):
+ permission = 'launchpad.Edit'
+ usedfor = IPoll
+
+
+class EditPollSubsetByTeamOwnerOrTeamAdminsOrAdmins(
+ EditPollByTeamOwnerOrTeamAdminsOrAdmins):
+ permission = 'launchpad.Edit'
+ usedfor = IPollSubset
+
+
+class EditPollOptionByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IPollOption
+
+ def checkAuthenticated(self, user):
+ return can_edit_team(self.obj.poll.team, user)
+
+
+class ViewDistribution(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ return self.obj.userCanView(user)
+
+ def checkUnauthenticated(self):
+ return self.obj.userCanView(None)
+
+
+class LimitedViewDistribution(ViewDistribution):
+ permission = 'launchpad.LimitedView'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ return (
+ super().checkAuthenticated(user) or
+ self.obj.userCanLimitedView(user))
+
+
+class AdminDistribution(AdminByAdminsTeam):
+ """Soyuz involves huge chunks of data in the archive and librarian,
+ so for the moment we are locking down admin and edit on distributions
+ and distroseriess to the Launchpad admin team."""
+ permission = 'launchpad.Admin'
+ usedfor = IDistribution
+
+
+class EditDistributionByDistroOwnersOrAdmins(AuthorizationBase):
+ """The owner of a distribution should be able to edit its
+ information; it is mainly administrative data, such as bug supervisors.
+ Note that creation of new distributions and distribution
+ series is still protected with launchpad.Admin"""
+ permission = 'launchpad.Edit'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ # Commercial admins may help setup commercial distributions.
+ return (
+ user.isOwner(self.obj)
+ or is_commercial_case(self.obj, user)
+ or user.in_admin)
+
+
+class SecurityAdminDistribution(AuthorizationBase):
+ """The security admins of a distribution should be able to create
+ and edit vulnerabilities in the distribution."""
+ permission = 'launchpad.SecurityAdmin'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ return (
+ user.isOwner(self.obj)
+ or is_commercial_case(self.obj, user)
+ or user.in_admin
+ or user.inTeam(self.obj.security_admin)
+ )
+
+
+class ModerateDistributionByDriversOrOwnersOrAdmins(AuthorizationBase):
+ """Distribution drivers, owners, and admins may plan releases.
+
+ Drivers of distributions that don't manage their packages in
+ Launchpad can create series. Owners and admins can create series for
+ all `IDistribution`s.
+ """
+ permission = 'launchpad.Moderate'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ if user.isDriver(self.obj) and not self.obj.official_packages:
+ # Damage to series with packages managed in Launchpad can
+ # cause serious strife. Restrict changes to the distro
+ # owner.
+ return True
+ return user.isOwner(self.obj) or user.in_admin
+
+
+class ViewDistributionSourcePackage(AnonymousAuthorization):
+ """Anyone can view a DistributionSourcePackage."""
+ usedfor = IDistributionSourcePackage
+
+
+class AdminDistributionTranslations(AuthorizationBase):
+ """Class for deciding who can administer distribution translations.
+
+ This class is used for `launchpad.TranslationsAdmin` privilege on
+ `IDistribution` and `IDistroSeries` and corresponding `IPOTemplate`s,
+ and limits access to Rosetta experts, Launchpad admins and distribution
+ translation group owner.
+ """
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IDistribution
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IDistribution` translations settings?
+
+ Any Launchpad/Launchpad Translations administrator, translation group
+ owner or a person allowed to edit distribution details is able to
+ change translations settings for a distribution.
+ """
+ # Translation group owner for a distribution is also a
+ # translations administrator for it.
+ translation_group = self.obj.translationgroup
+ if translation_group and user.inTeam(translation_group.owner):
+ return True
+ else:
+ return (user.in_rosetta_experts or
+ EditDistributionByDistroOwnersOrAdmins(
+ self.obj).checkAuthenticated(user))
+
+
+class BugSuperviseDistributionSourcePackage(AuthorizationBase):
+ """The owner of a distribution should be able to edit its source
+ package information"""
+ permission = 'launchpad.BugSupervisor'
+ usedfor = IDistributionSourcePackage
+
+ def checkAuthenticated(self, user):
+ return (user.inTeam(self.obj.distribution.bug_supervisor) or
+ user.inTeam(self.obj.distribution.owner) or
+ user.in_admin)
+
+
+class EditDistributionSourcePackage(EditSourcePackage):
+ permission = 'launchpad.Edit'
+ usedfor = IDistributionSourcePackage
+
+ def checkAuthenticated(self, user):
+ """Anyone who can upload a package can edit it.
+
+ Checking upload permission requires a distroseries; a reasonable
+ approximation is to check whether the user can upload the package to
+ the current series.
+ """
+ if user.in_admin:
+ return True
+
+ distribution = self.obj.distribution
+ if user.inTeam(distribution.owner):
+ return True
+
+ return self._checkUpload(
+ user, distribution.main_archive, distribution.currentseries)
+
+
+
+class NominateBugForProductSeries(AuthorizationBase):
+ """Product's owners and bug supervisors can add bug nominations."""
+
+ permission = 'launchpad.BugSupervisor'
+ usedfor = IProductSeries
+
+ def checkAuthenticated(self, user):
+ return (user.inTeam(self.obj.product.bug_supervisor) or
+ user.inTeam(self.obj.product.owner) or
+ user.in_admin)
+
+
+class NominateBugForDistroSeries(AuthorizationBase):
+ """Distro's owners and bug supervisors can add bug nominations."""
+
+ permission = 'launchpad.BugSupervisor'
+ usedfor = IDistroSeries
+
+ def checkAuthenticated(self, user):
+ return (user.inTeam(self.obj.distribution.bug_supervisor) or
+ user.inTeam(self.obj.distribution.owner) or
+ user.in_admin)
+
+
+class AdminDistroSeries(AdminByAdminsTeam):
+ """Soyuz involves huge chunks of data in the archive and librarian,
+ so for the moment we are locking down admin and edit on distributions
+ and distroseriess to the Launchpad admin team.
+
+ NB: Please consult carefully before modifying this permission because
+ changing it could cause the archive to get rearranged, with tons of
+ files moved to the new namespace, and mirrors would get very very
+ upset.
+ """
+ permission = 'launchpad.Admin'
+ usedfor = IDistroSeries
+
+
+class EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins(
+ AuthorizationBase):
+ """The owner of the distro series (i.e. the owner of the distribution)
+ should be able to modify some of the fields on the IDistroSeries
+
+ NB: there is potential for a great mess if this is not done correctly,
+ so please consult carefully before modifying these permissions.
+ """
+ permission = 'launchpad.Edit'
+ usedfor = IDistroSeries
+
+ def checkAuthenticated(self, user):
+ if (user.inTeam(self.obj.driver)
+ and not self.obj.distribution.official_packages):
+ # Damage to series with packages managed in Launchpad can
+ # cause serious strife. Restrict changes to the distro
+ # owner.
+ return True
+ return (user.inTeam(self.obj.distribution.owner) or
+ user.in_admin)
+
+
+class ViewDistroSeries(AnonymousAuthorization):
+ """Anyone can view a DistroSeries."""
+ usedfor = IDistroSeries
+
+
+class EditDistroSeriesParent(AuthorizationBase):
+ """DistroSeriesParent can be edited by the same people who can edit
+ the derived_distroseries."""
+ permission = "launchpad.Edit"
+ usedfor = IDistroSeriesParent
+
+ def checkAuthenticated(self, user):
+ auth = EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins(
+ self.obj.derived_series)
+ return auth.checkAuthenticated(user)
+
+
+class ViewCountry(AnonymousAuthorization):
+ """Anyone can view a Country."""
+ usedfor = ICountry
+
+
+class AdminDistroSeriesDifference(AuthorizationBase):
+ """You need to be an archive admin or LP admin to get lp.Admin."""
+ permission = 'launchpad.Admin'
+ usedfor = IDistroSeriesDifferenceAdmin
+
+ def checkAuthenticated(self, user):
+ # Archive admin is done by component, so here we just
+ # see if the user has that permission on any components
+ # at all.
+ archive = self.obj.derived_series.main_archive
+ return (
+ not archive.getComponentsForQueueAdmin(user.person).is_empty() or
+ user.in_admin)
+
+
+class EditDistroSeriesDifference(DelegatedAuthorization):
+ """Anyone with lp.View on the distribution can edit a DSD."""
+ permission = 'launchpad.Edit'
+ usedfor = IDistroSeriesDifferenceEdit
+
+ def __init__(self, obj):
+ super().__init__(
+ obj, obj.derived_series.distribution, 'launchpad.View')
+
+ def checkUnauthenticated(self):
+ return False
+
+
+class SeriesDrivers(AuthorizationBase):
+ """Drivers can approve or decline features and target bugs.
+
+ Drivers exist for distribution and product series. Distribution and
+ product owners are implicitly drivers too.
+ """
+ permission = 'launchpad.Driver'
+ usedfor = IHasDrivers
+
+ def checkAuthenticated(self, user):
+ return self.obj.personHasDriverRights(user)
+
+
+class DriveProduct(SeriesDrivers):
+
+ permission = 'launchpad.Driver'
+ usedfor = IProduct
+
+ def checkAuthenticated(self, user):
+ # Commercial admins may help setup commercial projects.
+ return (
+ super().checkAuthenticated(user)
+ or is_commercial_case(self.obj, user)
+ or False)
+
+
+class LimitedViewProductSeries(DelegatedAuthorization):
+ permission = 'launchpad.LimitedView'
+ usedfor = IProductSeriesLimitedView
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.product, 'launchpad.LimitedView')
+
+
+class ViewProductSeries(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IProductSeriesView
+
+ def checkAuthenticated(self, user):
+ return self.obj.userCanView(user)
+
+ def checkUnauthenticated(self):
+ return self.obj.userCanView(None)
+
+
+class EditProductSeries(EditByOwnersOrAdmins):
+ usedfor = IProductSeries
+
+ def checkAuthenticated(self, user):
+ """Allow product owner, drivers, some experts, or admins."""
+ if (user.isOwner(self.obj.product) or
+ user.isDriver(self.obj)):
+ # The user is the owner of the product, or the release manager.
+ return True
+ # Rosetta experts need to be able to upload translations.
+ # Registry admins are just special.
+ if (user.in_registry_experts or
+ user.in_rosetta_experts):
+ return True
+ return EditByOwnersOrAdmins.checkAuthenticated(self, user)
+
+
+class ViewDistroArchSeries(AnonymousAuthorization):
+ """Anyone can view a DistroArchSeries."""
+ usedfor = IDistroArchSeries
+
+
+class ModerateDistroArchSeries(AuthorizationBase):
+ permission = 'launchpad.Moderate'
+ usedfor = IDistroArchSeries
+
+ def checkAuthenticated(self, user):
+ return (
+ user.isOwner(self.obj.distroseries.distribution.main_archive)
+ or user.in_admin)
+
+
+class ViewDistroArchSeriesFilter(DelegatedAuthorization):
+ permission = 'launchpad.View'
+ usedfor = IDistroArchSeriesFilter
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.distroarchseries, 'launchpad.View')
+
+
+class EditDistroArchSeriesFilter(DelegatedAuthorization):
+ permission = 'launchpad.Edit'
+ usedfor = IDistroArchSeriesFilter
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.distroarchseries, 'launchpad.Moderate')
+
+
+class ViewAnnouncement(AuthorizationBase):
+ permission = 'launchpad.View'
+ usedfor = IAnnouncement
+
+ def checkUnauthenticated(self):
+ """Let anonymous users see published announcements."""
+ if self.obj.published:
+ return True
+ return False
+
+ def checkAuthenticated(self, user):
+ """Keep project news invisible to end-users unless they are project
+ admins, until the announcements are published."""
+
+ # Every user can view published announcements.
+ if self.obj.published:
+ return True
+
+ # Project drivers can view any project announcements.
+ # Launchpad admins can view any announcement.
+ assert self.obj.target
+ return (user.isDriver(self.obj.target) or
+ user.isOwner(self.obj.target) or
+ user.in_admin)
+
+
+class EditAnnouncement(AuthorizationBase):
+ permission = 'launchpad.Edit'
+ usedfor = IAnnouncement
+
+ def checkAuthenticated(self, user):
+ """Allow the project owner and drivers to edit any project news."""
+
+ assert self.obj.target
+ return (user.isDriver(self.obj.target) or
+ user.isOwner(self.obj.target) or
+ user.in_admin)
+
+
+class ViewProductRelease(DelegatedAuthorization):
+ permission = 'launchpad.View'
+ usedfor = IProductRelease
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.milestone, 'launchpad.View')
+
+
+class ViewNameBlacklist(EditByRegistryExpertsOrAdmins):
+ permission = 'launchpad.View'
+ usedfor = INameBlacklist
+
+
+class EditNameBlacklist(EditByRegistryExpertsOrAdmins):
+ permission = 'launchpad.Edit'
+ usedfor = INameBlacklist
+
+
+class ViewNameBlacklistSet(EditByRegistryExpertsOrAdmins):
+ permission = 'launchpad.View'
+ usedfor = INameBlacklistSet
+
+
+class EditNameBlacklistSet(EditByRegistryExpertsOrAdmins):
+ permission = 'launchpad.Edit'
+ usedfor = INameBlacklistSet
+
+
+class AdminDistroSeriesTranslations(AuthorizationBase):
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IDistroSeries
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IDistroSeries` translations.
+
+ Distribution translation managers and distribution series drivers
+ can manage IDistroSeries translations.
+ """
+ return (user.isDriver(self.obj) or
+ self.forwardCheckAuthenticated(user, self.obj.distribution))
+
+
+class AdminDistributionSourcePackageTranslations(DelegatedAuthorization):
+ """DistributionSourcePackage objects link to a distribution."""
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IDistributionSourcePackage
+
+ def __init__(self, obj):
+ super().__init__(obj, obj.distribution)
+
+
+class AdminProductSeriesTranslations(AuthorizationBase):
+ permission = 'launchpad.TranslationsAdmin'
+ usedfor = IProductSeries
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IProductSeries` translations."""
+
+ return (user.isOwner(self.obj) or
+ user.isDriver(self.obj) or
+ self.forwardCheckAuthenticated(user, self.obj.product))
+
+
+class AdminDistroSeriesLanguagePacks(
+ OnlyRosettaExpertsAndAdmins,
+ EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins):
+ permission = 'launchpad.LanguagePacksAdmin'
+ usedfor = IDistroSeries
+
+ def checkAuthenticated(self, user):
+ """Is the user able to manage `IDistroSeries` language packs?
+
+ Any Launchpad/Launchpad Translations administrator, people allowed to
+ edit distroseries or members of IDistribution.language_pack_admin team
+ are able to change the language packs available.
+ """
+ EditDS = EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins
+ return (
+ OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
+ EditDS.checkAuthenticated(self, user) or
+ user.inTeam(self.obj.distribution.language_pack_admin))
+
+
+class ViewGPGKey(AnonymousAuthorization):
+ usedfor = IGPGKey
+
+
+class ViewSSHKey(AnonymousAuthorization):
+ usedfor = ISSHKey
+
+
+class ViewIrcID(AnonymousAuthorization):
+ usedfor = IIrcID
+
+
+class ViewWikiName(AnonymousAuthorization):
+ usedfor = IWikiName
diff --git a/lib/lp/security.py b/lib/lp/security.py
index 2ab931d..3059ce1 100644
--- a/lib/lp/security.py
+++ b/lib/lp/security.py
@@ -4,8 +4,15 @@
"""Security policies for using content objects."""
__all__ = [
+ 'AdminByAdminsTeam',
+ 'AdminByCommercialTeamOrAdmins',
'BugTargetOwnerOrBugSupervisorOrAdmins',
+ 'EditByOwnersOrAdmins',
+ 'EditByRegistryExpertsOrAdmins',
+ 'EditSourcePackage',
+ 'is_commercial_case',
'ModerateByRegistryExpertsOrAdmins',
+ 'OnlyRosettaExpertsAndAdmins',
]
from datetime import (
@@ -15,11 +22,7 @@ from datetime import (
from operator import methodcaller
import pytz
-from storm.expr import (
- And,
- Select,
- Union,
- )
+from storm.expr import And
from zope.component import (
getUtility,
queryAdapter,
@@ -50,14 +53,8 @@ from lp.blueprints.interfaces.specificationsubscription import (
)
from lp.blueprints.interfaces.sprint import ISprint
from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
-from lp.blueprints.model.specificationsubscription import (
- SpecificationSubscription,
- )
from lp.bugs.interfaces.bugtarget import IOfficialBugTagTargetRestricted
from lp.bugs.interfaces.structuralsubscription import IStructuralSubscription
-from lp.bugs.model.bugsubscription import BugSubscription
-from lp.bugs.model.bugtaskflat import BugTaskFlat
-from lp.bugs.model.bugtasksearch import get_bug_privacy_filter
from lp.buildmaster.interfaces.builder import (
IBuilder,
IBuilderSet,
@@ -77,10 +74,6 @@ from lp.code.interfaces.branch import (
IBranch,
user_has_special_branch_access,
)
-from lp.code.interfaces.branchcollection import (
- IAllBranches,
- IBranchCollection,
- )
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
from lp.code.interfaces.cibuild import ICIBuild
from lp.code.interfaces.codeimport import ICodeImport
@@ -96,7 +89,6 @@ from lp.code.interfaces.codereviewcomment import (
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
from lp.code.interfaces.diff import IPreviewDiff
from lp.code.interfaces.gitactivity import IGitActivity
-from lp.code.interfaces.gitcollection import IGitCollection
from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.gitrepository import (
IGitRepository,
@@ -122,97 +114,26 @@ from lp.oci.interfaces.ocirecipe import (
from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild
from lp.oci.interfaces.ocirecipesubscription import IOCIRecipeSubscription
from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentials
-from lp.registry.enums import PersonVisibility
-from lp.registry.interfaces.announcement import IAnnouncement
-from lp.registry.interfaces.distribution import IDistribution
-from lp.registry.interfaces.distributionmirror import IDistributionMirror
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
)
-from lp.registry.interfaces.distroseries import IDistroSeries
-from lp.registry.interfaces.distroseriesdifference import (
- IDistroSeriesDifferenceAdmin,
- IDistroSeriesDifferenceEdit,
- )
-from lp.registry.interfaces.distroseriesparent import IDistroSeriesParent
-from lp.registry.interfaces.gpg import IGPGKey
-from lp.registry.interfaces.irc import IIrcID
-from lp.registry.interfaces.location import IPersonLocation
-from lp.registry.interfaces.milestone import (
- IMilestone,
- IProjectGroupMilestone,
- )
-from lp.registry.interfaces.nameblacklist import (
- INameBlacklist,
- INameBlacklistSet,
- )
from lp.registry.interfaces.ociproject import IOCIProject
from lp.registry.interfaces.ociprojectseries import IOCIProjectSeries
-from lp.registry.interfaces.packaging import IPackaging
-from lp.registry.interfaces.person import (
- IPerson,
- IPersonLimitedView,
- IPersonSet,
- ITeam,
- )
-from lp.registry.interfaces.pillar import (
- IPillar,
- IPillarPerson,
- )
-from lp.registry.interfaces.poll import (
- IPoll,
- IPollOption,
- IPollSubset,
- )
-from lp.registry.interfaces.product import (
- IProduct,
- IProductSet,
- )
-from lp.registry.interfaces.productrelease import (
- IProductRelease,
- IProductReleaseFile,
- )
-from lp.registry.interfaces.productseries import (
- IProductSeries,
- IProductSeriesLimitedView,
- IProductSeriesView,
- ITimelineProductSeries,
- )
-from lp.registry.interfaces.projectgroup import (
- IProjectGroup,
- IProjectGroupSet,
- )
-from lp.registry.interfaces.role import (
- IHasDrivers,
- IHasOwner,
- )
+from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.productseries import IProductSeries
+from lp.registry.interfaces.role import IHasOwner
from lp.registry.interfaces.sourcepackage import ISourcePackage
-from lp.registry.interfaces.ssh import ISSHKey
-from lp.registry.interfaces.teammembership import (
- ITeamMembership,
- TeamMembershipStatus,
- )
-from lp.registry.interfaces.wikiname import IWikiName
-from lp.registry.model.person import Person
-from lp.services.auth.interfaces import IAccessToken
from lp.services.config import config
from lp.services.database.interfaces import IStore
-from lp.services.identity.interfaces.account import IAccount
from lp.services.identity.interfaces.emailaddress import IEmailAddress
from lp.services.librarian.interfaces import ILibraryFileAliasWithParent
from lp.services.messages.interfaces.message import IMessage
from lp.services.messages.interfaces.messagerevision import IMessageRevision
-from lp.services.oauth.interfaces import (
- IOAuthAccessToken,
- IOAuthRequestToken,
- )
-from lp.services.openid.interfaces.openididentifier import IOpenIdIdentifier
from lp.services.webapp.interfaces import ILaunchpadRoot
from lp.services.webhooks.interfaces import (
IWebhook,
IWebhookDeliveryJob,
)
-from lp.services.worlddata.interfaces.country import ICountry
from lp.services.worlddata.interfaces.language import (
ILanguage,
ILanguageSet,
@@ -246,8 +167,6 @@ from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
from lp.soyuz.interfaces.binarypackagerelease import (
IBinaryPackageReleaseDownloadCount,
)
-from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
-from lp.soyuz.interfaces.distroarchseriesfilter import IDistroArchSeriesFilter
from lp.soyuz.interfaces.livefs import ILiveFS
from lp.soyuz.interfaces.livefsbuild import ILiveFSBuild
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJob
@@ -405,124 +324,6 @@ class ModerateByRegistryExpertsOrAdmins(AuthorizationBase):
return user.in_admin or user.in_registry_experts
-class ModerateDistroSeries(ModerateByRegistryExpertsOrAdmins):
- usedfor = IDistroSeries
-
-
-class ModerateProduct(ModerateByRegistryExpertsOrAdmins):
- usedfor = IProduct
-
-
-class ModerateProductSet(ModerateByRegistryExpertsOrAdmins):
- usedfor = IProductSet
-
-
-class ModerateProject(ModerateByRegistryExpertsOrAdmins):
- usedfor = IProjectGroup
-
-
-class ModerateProjectGroupSet(ModerateByRegistryExpertsOrAdmins):
- usedfor = IProjectGroupSet
-
-
-class ModeratePerson(ModerateByRegistryExpertsOrAdmins):
- permission = 'launchpad.Moderate'
- usedfor = IPerson
-
-
-class ViewPillar(AuthorizationBase):
- usedfor = IPillar
- permission = 'launchpad.View'
-
- def checkUnauthenticated(self):
- return self.obj.active
-
- def checkAuthenticated(self, user):
- """The Admins & Commercial Admins can see inactive pillars."""
- if self.obj.active:
- return True
- else:
- return (user.in_commercial_admin or
- user.in_admin or
- user.in_registry_experts)
-
-
-class PillarPersonSharingDriver(AuthorizationBase):
- usedfor = IPillarPerson
- permission = 'launchpad.Driver'
-
- def checkAuthenticated(self, user):
- """Maintainers, drivers, and admins can drive projects."""
- return (user.in_admin or
- user.isOwner(self.obj.pillar) or
- user.isDriver(self.obj.pillar))
-
-
-class EditAccountBySelfOrAdmin(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IAccount
-
- def checkAuthenticated(self, user):
- return user.in_admin or user.person.accountID == self.obj.id
-
-
-class ViewAccount(EditAccountBySelfOrAdmin):
- permission = 'launchpad.View'
-
- def checkAuthenticated(self, user):
- """Extend permission to registry experts."""
- return (
- super().checkAuthenticated(user)
- or user.in_registry_experts)
-
-
-class ModerateAccountByRegistryExpert(AuthorizationBase):
- usedfor = IAccount
- permission = 'launchpad.Moderate'
-
- def checkAuthenticated(self, user):
- return user.in_admin or user.in_registry_experts
-
-
-class ViewOpenIdIdentifierBySelfOrAdmin(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IOpenIdIdentifier
-
- def checkAuthenticated(self, user):
- return user.in_admin or user.person.accountID == self.obj.accountID
-
-
-class EditOAuthAccessToken(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IOAuthAccessToken
-
- def checkAuthenticated(self, user):
- return self.obj.person == user.person or user.in_admin
-
-
-class EditOAuthRequestToken(EditOAuthAccessToken):
- permission = 'launchpad.Edit'
- usedfor = IOAuthRequestToken
-
-
-class EditAccessToken(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IAccessToken
-
- def checkAuthenticated(self, user):
- if user.inTeam(self.obj.owner):
- return True
- # Being able to edit the token doesn't allow extracting the secret,
- # so it's OK to allow the owner of the target to do so too. This
- # allows target owners to exercise some control over access to their
- # object.
- adapter = queryAdapter(
- self.obj.target, IAuthorization, 'launchpad.Edit')
- if adapter is not None and adapter.checkAuthenticated(user):
- return True
- return False
-
-
class EditByOwnersOrAdmins(AuthorizationBase):
permission = 'launchpad.Edit'
usedfor = IHasOwner
@@ -531,94 +332,6 @@ class EditByOwnersOrAdmins(AuthorizationBase):
return user.isOwner(self.obj) or user.in_admin
-class ViewProduct(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IProduct
-
- def checkAuthenticated(self, user):
- return self.obj.userCanView(user)
-
- def checkUnauthenticated(self):
- return self.obj.userCanView(None)
-
-
-class LimitedViewProduct(ViewProduct):
- permission = 'launchpad.LimitedView'
- usedfor = IProduct
-
- def checkAuthenticated(self, user):
- return (
- super().checkAuthenticated(user) or
- self.obj.userCanLimitedView(user))
-
-
-class EditProduct(EditByOwnersOrAdmins):
- usedfor = IProduct
-
- def checkAuthenticated(self, user):
- # Commercial admins may help setup commercial projects.
- return (
- super().checkAuthenticated(user)
- or is_commercial_case(self.obj, user)
- or False)
-
-
-class EditPackaging(EditByOwnersOrAdmins):
- usedfor = IPackaging
-
-
-class EditProductReleaseFile(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IProductReleaseFile
-
- def checkAuthenticated(self, user):
- return EditProductRelease(self.obj.productrelease).checkAuthenticated(
- user)
-
-
-class ViewTimelineProductSeries(DelegatedAuthorization):
- """Anyone who can view the related product can also view an
- ITimelineProductSeries.
- """
- permission = 'launchpad.View'
- usedfor = ITimelineProductSeries
-
- def __init__(self, obj):
- super().__init__(obj, obj.product, 'launchpad.View')
-
-
-class ViewProductReleaseFile(AnonymousAuthorization):
- """Anyone can view an IProductReleaseFile."""
- usedfor = IProductReleaseFile
-
-
-class AdminDistributionMirrorByDistroOwnerOrMirrorAdminsOrAdmins(
- AuthorizationBase):
- permission = 'launchpad.Admin'
- usedfor = IDistributionMirror
-
- def checkAuthenticated(self, user):
- return (user.isOwner(self.obj.distribution) or
- user.in_admin or
- user.inTeam(self.obj.distribution.mirror_admin))
-
-
-class EditDistributionMirrorByOwnerOrDistroOwnerOrMirrorAdminsOrAdmins(
- AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IDistributionMirror
-
- def checkAuthenticated(self, user):
- return (user.isOwner(self.obj) or user.in_admin or
- user.isOwner(self.obj.distribution) or
- user.inTeam(self.obj.distribution.mirror_admin))
-
-
-class ViewDistributionMirror(AnonymousAuthorization):
- """Anyone can view an IDistributionMirror."""
- usedfor = IDistributionMirror
-
-
class EditSpecificationBranch(AuthorizationBase):
usedfor = ISpecificationBranch
@@ -677,959 +390,181 @@ class EditSpecificationByRelatedPeople(AuthorizationBase):
usedfor = ISpecification
def checkAuthenticated(self, user):
- assert self.obj.target
- goal = self.obj.goal
- if goal is not None:
- if user.isOwner(goal) or user.isDriver(goal):
- return True
- return (user.in_admin or
- user.in_registry_experts or
- user.isOwner(self.obj.target) or
- user.isDriver(self.obj.target) or
- user.isOneOf(
- self.obj, ['owner', 'drafter', 'assignee', 'approver']))
-
-
-class ViewRevisionStatusReport(DelegatedAuthorization):
- """Anyone who can see a Git repository can see its status reports."""
- permission = 'launchpad.View'
- usedfor = IRevisionStatusReport
-
- def __init__(self, obj):
- super().__init__(obj, obj.git_repository, 'launchpad.View')
-
-
-class EditRevisionStatusReport(AuthorizationBase):
- """The owner of a Git repository can edit its status reports."""
- permission = 'launchpad.Edit'
- usedfor = IRevisionStatusReport
-
- def checkAuthenticated(self, user):
- return user.isOwner(self.obj.git_repository)
-
-
-class ViewRevisionStatusArtifact(DelegatedAuthorization):
- permission = 'launchpad.View'
- usedfor = IRevisionStatusArtifact
-
- def __init__(self, obj):
- super().__init__(obj, obj.report, 'launchpad.View')
-
-
-class EditRevisionStatusArtifact(DelegatedAuthorization):
- permission = 'launchpad.Edit'
- usedfor = IRevisionStatusArtifact
-
- def __init__(self, obj):
- super().__init__(obj, obj.report, 'launchpad.Edit')
-
-
-class AdminSpecification(AuthorizationBase):
- permission = 'launchpad.Admin'
- usedfor = ISpecification
-
- def checkAuthenticated(self, user):
- assert self.obj.target
- return (
- user.in_admin or
- user.in_registry_experts or
- user.isOwner(self.obj.target) or
- user.isDriver(self.obj.target))
-
-
-class DriverSpecification(AuthorizationBase):
- permission = 'launchpad.Driver'
- usedfor = ISpecification
-
- def checkAuthenticated(self, user):
- # If no goal is proposed for the spec then there can be no
- # drivers for it - we use launchpad.Driver on a spec to decide
- # if the person can see the page which lets you decide whether
- # to accept the goal, and if there is no goal then this is
- # extremely difficult to do :-)
- return (
- self.obj.goal and
- self.forwardCheckAuthenticated(user, self.obj.goal))
-
-
-class EditSprintSpecification(AuthorizationBase):
- """The sprint owner or driver can say what makes it onto the agenda for
- the sprint.
- """
- permission = 'launchpad.Driver'
- usedfor = ISprintSpecification
-
- def checkAuthenticated(self, user):
- sprint = self.obj.sprint
- return user.isOwner(sprint) or user.isDriver(sprint) or user.in_admin
-
-
-class DriveSprint(AuthorizationBase):
- """The sprint owner or driver can say what makes it onto the agenda for
- the sprint.
- """
- permission = 'launchpad.Driver'
- usedfor = ISprint
-
- def checkAuthenticated(self, user):
- return (user.isOwner(self.obj) or
- user.isDriver(self.obj) or
- user.in_admin)
-
-
-class ViewSprint(AuthorizationBase):
- """An attendee, owner, or driver of a sprint."""
- permission = 'launchpad.View'
- usedfor = ISprint
-
- def checkAuthenticated(self, user):
- return (user.isOwner(self.obj) or
- user.isDriver(self.obj) or
- user.person in [attendance.attendee
- for attendance in self.obj.attendances] or
- user.in_admin)
-
-
-class EditSprint(EditByOwnersOrAdmins):
- usedfor = ISprint
-
-
-class ModerateSprint(ModerateByRegistryExpertsOrAdmins):
- """The sprint owner, registry experts, and admins can moderate sprints."""
- permission = 'launchpad.Moderate'
- usedfor = ISprint
-
- def checkAuthenticated(self, user):
- return (
- super().checkAuthenticated(user) or
- user.isOwner(self.obj))
-
-
-class EditSpecificationSubscription(AuthorizationBase):
- """The subscriber, and people related to the spec or the target of the
- spec can determine who is essential."""
- permission = 'launchpad.Edit'
- usedfor = ISpecificationSubscription
-
- def checkAuthenticated(self, user):
- if self.obj.specification.goal is not None:
- if user.isDriver(self.obj.specification.goal):
- return True
- else:
- if user.isDriver(self.obj.specification.target):
- return True
- return (user.inTeam(self.obj.person) or
- user.isOneOf(
- self.obj.specification,
- ['owner', 'drafter', 'assignee', 'approver']) or
- user.in_admin)
-
-
-class OnlyRosettaExpertsAndAdmins(AuthorizationBase):
- """Base class that allow access to Rosetta experts and Launchpad admins.
- """
-
- def checkAuthenticated(self, user):
- """Allow Launchpad's admins and Rosetta experts edit all fields."""
- return user.in_admin or user.in_rosetta_experts
-
-
-class AdminProjectTranslations(AuthorizationBase):
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IProjectGroup
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IProjectGroup` translations settings?
-
- Any Launchpad/Launchpad Translations administrator or owner is
- able to change translation settings for a project group.
- """
- return (user.isOwner(self.obj) or
- user.in_rosetta_experts or
- user.in_admin)
-
-
-class AdminProductTranslations(AuthorizationBase):
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IProduct
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IProduct` translations settings?
-
- Any Launchpad/Launchpad Translations administrator or owners are
- able to change translation settings for a product.
- """
- return (user.isOwner(self.obj) or
- user.isDriver(self.obj) or
- user.in_rosetta_experts or
- user.in_admin)
-
-
-class ViewProjectMilestone(DelegatedAuthorization):
- permission = 'launchpad.View'
- usedfor = IProjectGroupMilestone
-
- def __init__(self, obj):
- super().__init__(obj, obj.product, 'launchpad.View')
-
-
-class EditProjectMilestoneNever(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IProjectGroupMilestone
-
- def checkAuthenticated(self, user):
- """IProjectGroupMilestone is a fake content object."""
- return False
-
-
-class LimitedViewMilestone(DelegatedAuthorization):
- permission = 'launchpad.LimitedView'
- usedfor = IMilestone
-
- def __init__(self, obj):
- super().__init__(obj, obj.target, 'launchpad.LimitedView')
-
-
-class ViewMilestone(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IMilestone
-
- def checkAuthenticated(self, user):
- return self.obj.userCanView(user)
-
- def checkUnauthenticated(self):
- return self.obj.userCanView(user=None)
-
-
-class EditMilestoneByTargetOwnerOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IMilestone
-
- def checkAuthenticated(self, user):
- """Authorize the product or distribution owner."""
- if user.in_admin:
- return True
- if user.isDriver(self.obj.series_target):
- return True
- return user.isOwner(self.obj.target)
-
-
-class AdminMilestoneByLaunchpadAdmins(AuthorizationBase):
- permission = 'launchpad.Admin'
- usedfor = IMilestone
-
- def checkAuthenticated(self, user):
- """Only the Launchpad admins need this, we are only going to use
- it for connecting up series and distroseries where we did not
- have them.
- """
- return user.in_admin
-
-
-class ModeratePersonSetByExpertsOrAdmins(ModerateByRegistryExpertsOrAdmins):
- permission = 'launchpad.Moderate'
- usedfor = IPersonSet
-
-
-class EditTeamByTeamOwnerOrLaunchpadAdmins(AuthorizationBase):
- permission = 'launchpad.Owner'
- usedfor = ITeam
-
- def checkAuthenticated(self, user):
- """Only the team owner and Launchpad admins need this.
- """
- return user.inTeam(self.obj.teamowner) or user.in_admin
-
-
-class EditTeamByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = ITeam
-
- def checkAuthenticated(self, user):
- """The team owner and team admins have launchpad.Edit on that team.
-
- The Launchpad admins also have launchpad.Edit on all teams.
- """
- return can_edit_team(self.obj, user)
-
-
-class ModerateTeam(ModerateByRegistryExpertsOrAdmins):
- permission = 'launchpad.Moderate'
- usedfor = ITeam
-
- def checkAuthenticated(self, user):
- """Is the user a privileged team member or Launchpad staff?
-
- Return true when the user is a member of Launchpad admins,
- registry experts, team admins, or the team owners.
- """
- return (
- super().checkAuthenticated(user)
- or can_edit_team(self.obj, user))
-
-
-class EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = ITeamMembership
-
- def checkAuthenticated(self, user):
- return can_edit_team(self.obj.team, user)
-
-
-class ViewTeamMembership(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = ITeamMembership
-
- def checkUnauthenticated(self):
- """Unauthenticated users can only view public memberships."""
- return self.obj.team.visibility == PersonVisibility.PUBLIC
-
- def checkAuthenticated(self, user):
- """Verify that the user can view the team's membership.
-
- Anyone can see a public team's membership. Only a team member or
- commercial admin or a Launchpad admin can view a private team.
- """
- if self.obj.team.visibility == PersonVisibility.PUBLIC:
- return True
- if (user.in_admin or user.in_commercial_admin
- or user.inTeam(self.obj.team)):
- return True
- return False
-
-
-class AdminByCommercialTeamOrAdminsOrPerson(AdminByCommercialTeamOrAdmins):
- permission = 'launchpad.Commercial'
- usedfor = IPerson
-
- def checkAuthenticated(self, user):
- """Users can manage their commericial data and admins can help."""
- return self.obj.id == user.id or super().checkAuthenticated(user)
-
-
-class EditPersonBySelfOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IPerson
-
- def checkAuthenticated(self, user):
- """A user can edit the Person who is themselves.
-
- The admin team can also edit any Person.
- """
- return self.obj.id == user.id or user.in_admin
-
-
-class EditTranslationsPersonByPerson(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = ITranslationsPerson
-
- def checkAuthenticated(self, user):
- person = self.obj.person
- return person == user.person or user.in_admin
-
-
-class ViewPersonLocation(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IPersonLocation
-
- def checkUnauthenticated(self):
- return self.obj.visible
-
- def checkAuthenticated(self, user):
- if self.obj.visible:
- return True
- else:
- return user.person == self.obj.person or user.in_admin
-
-
-class EditPersonBySelf(AuthorizationBase):
- permission = 'launchpad.Special'
- usedfor = IPerson
-
- def checkAuthenticated(self, user):
- """A user can edit the Person who is themselves."""
- return self.obj.id == user.person.id
-
-
-class ViewPublicOrPrivateTeamMembers(AuthorizationBase):
- """Restrict viewing of private teams.
-
- Only members of a private team can view the
- membership list.
- """
- permission = 'launchpad.View'
- usedfor = IPerson
-
- def checkUnauthenticated(self):
- """Unauthenticated users can only view public memberships."""
- if self.obj.visibility == PersonVisibility.PUBLIC:
- return True
- return False
-
- def checkAuthenticated(self, user):
- """Verify that the user can view the team's membership.
-
- Anyone can see a public team's membership. Only a team member,
- commercial admin, or a Launchpad admin can view a private team's
- members.
- """
- if self.obj.visibility == PersonVisibility.PUBLIC:
- return True
- if user.in_admin or user.in_commercial_admin or user.inTeam(self.obj):
- return True
- # Private team owners have visibility.
- if self.obj.is_team and user.inTeam(self.obj.teamowner):
- return True
- # We also grant visibility of the private team to administrators of
- # other teams that have been invited to join the private team.
- for invitee in self.obj.invited_members:
- if (invitee.is_team and
- invitee in user.person.getAdministratedTeams()):
- return True
- return False
-
-
-class PublicOrPrivateTeamsExistence(AuthorizationBase):
- """Restrict knowing about private teams' existence.
-
- Knowing the existence of a private team allow traversing to its URL and
- displaying basic information like name, displayname.
- """
- permission = 'launchpad.LimitedView'
- usedfor = IPersonLimitedView
-
- def checkUnauthenticated(self):
- """Unauthenticated users can only view public teams."""
- if self.obj.visibility == PersonVisibility.PUBLIC:
- return True
- return False
-
- def checkAuthenticated(self, user):
- """By default, we simply perform a View permission check.
-
- We also grant limited viewability to users who can see PPAs and
- branches owned by the team, and members of parent teams so they can
- see the member-listings.
-
- In other scenarios, the context in which the permission is required is
- responsible for pre-caching the launchpad.LimitedView permission on
- each team which requires it.
- """
- if self.forwardCheckAuthenticated(
- user, self.obj, 'launchpad.View'):
- return True
-
- if (self.obj.is_team
- and self.obj.visibility == PersonVisibility.PRIVATE):
- # Grant visibility to people with subscriptions on a private
- # team's private PPA.
- subscriptions = getUtility(
- IArchiveSubscriberSet).getBySubscriber(user.person)
- subscriber_archive_ids = {
- sub.archive_id for sub in subscriptions}
- team_ppa_ids = {
- ppa.id for ppa in self.obj.ppas if ppa.private}
- if len(subscriber_archive_ids.intersection(team_ppa_ids)) > 0:
- return True
-
- # Grant visibility to people who can see branches owned by the
- # private team.
- team_branches = IBranchCollection(self.obj)
- if not team_branches.visibleByUser(user.person).is_empty():
- return True
-
- # Grant visibility to people who can see branches subscribed to
- # by the private team.
- team_branches = getUtility(IAllBranches).subscribedBy(self.obj)
- if not team_branches.visibleByUser(user.person).is_empty():
- return True
-
- # Grant visibility to branches visible to the user and which have
- # review requests for the private team.
- branches = getUtility(IAllBranches)
- visible_branches = branches.visibleByUser(user.person)
- mp = visible_branches.getMergeProposalsForReviewer(self.obj)
- if not mp.is_empty():
- return True
-
- # Grant visibility to people who can see Git repositories owned
- # by the private team.
- team_repositories = IGitCollection(self.obj)
- if not team_repositories.visibleByUser(user.person).is_empty():
- return True
-
- # Grant visibility to people who own Git repositories that grant
- # some kind of write access to the private team.
- owned_repositories = IGitCollection(user.person)
- grants = owned_repositories.getRuleGrantsForGrantee(self.obj)
- if not grants.is_empty():
- return True
-
- # Grant visibility to users in a team that has the private team as
- # a member, so that they can see the team properly in member
- # listings.
-
- # The easiest check is just to see if the user is in a team that
- # is a super team for the private team.
-
- # Do comparison by ids because they may be needed for comparison
- # to membership.team.ids later.
- user_teams = [
- team.id for team in user.person.teams_participated_in]
- super_teams = [team.id for team in self.obj.super_teams]
- intersection_teams = set(user_teams) & set(super_teams)
-
- if len(intersection_teams) > 0:
- return True
-
- # If it's not, the private team may still be a pending membership,
- # deactivated membership, or an expired membership,
- # which still needs to be visible to team members.
- BAD_STATES = (
- TeamMembershipStatus.DECLINED.value,
- TeamMembershipStatus.INVITATION_DECLINED.value,
- )
- team_memberships_query = """
- SELECT team from TeamMembership WHERE person = %s AND
- status NOT IN %s
- """ % (self.obj.id, BAD_STATES)
- store = IStore(Person)
- future_super_teams = [team[0] for team in
- store.execute(team_memberships_query)]
- intersection_teams = set(user_teams) & set(future_super_teams)
-
- if len(intersection_teams) > 0:
- return True
-
- # Teams subscribed to blueprints are visible. This needs to
- # be taught about privacy eventually.
- specsubs = store.find(SpecificationSubscription, person=self.obj)
-
- # Teams subscribed or assigned to bugs that the user can see
- # are visible.
- bugs = store.find(
- BugTaskFlat,
- get_bug_privacy_filter(user.person),
- BugTaskFlat.bug_id.is_in(
- Union(
- Select(
- BugSubscription.bug_id,
- tables=(BugSubscription,),
- where=BugSubscription.person == self.obj),
- Select(
- BugTaskFlat.bug_id,
- tables=(BugTaskFlat,),
- where=BugTaskFlat.assignee == self.obj),
- all=True)))
-
- if not specsubs.is_empty() or not bugs.is_empty():
- return True
- return False
-
-
-class EditPollByTeamOwnerOrTeamAdminsOrAdmins(
- EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IPoll
-
-
-class EditPollSubsetByTeamOwnerOrTeamAdminsOrAdmins(
- EditPollByTeamOwnerOrTeamAdminsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IPollSubset
-
-
-class EditPollOptionByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IPollOption
-
- def checkAuthenticated(self, user):
- return can_edit_team(self.obj.poll.team, user)
-
-
-class ViewDistribution(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- return self.obj.userCanView(user)
-
- def checkUnauthenticated(self):
- return self.obj.userCanView(None)
-
-
-class LimitedViewDistribution(ViewDistribution):
- permission = 'launchpad.LimitedView'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- return (
- super().checkAuthenticated(user) or
- self.obj.userCanLimitedView(user))
-
-
-class AdminDistribution(AdminByAdminsTeam):
- """Soyuz involves huge chunks of data in the archive and librarian,
- so for the moment we are locking down admin and edit on distributions
- and distroseriess to the Launchpad admin team."""
- permission = 'launchpad.Admin'
- usedfor = IDistribution
-
-
-class EditDistributionByDistroOwnersOrAdmins(AuthorizationBase):
- """The owner of a distribution should be able to edit its
- information; it is mainly administrative data, such as bug supervisors.
- Note that creation of new distributions and distribution
- series is still protected with launchpad.Admin"""
- permission = 'launchpad.Edit'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- # Commercial admins may help setup commercial distributions.
- return (
- user.isOwner(self.obj)
- or is_commercial_case(self.obj, user)
- or user.in_admin)
-
-
-class SecurityAdminDistribution(AuthorizationBase):
- """The security admins of a distribution should be able to create
- and edit vulnerabilities in the distribution."""
- permission = 'launchpad.SecurityAdmin'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- return (
- user.isOwner(self.obj)
- or is_commercial_case(self.obj, user)
- or user.in_admin
- or user.inTeam(self.obj.security_admin)
- )
-
-
-class ModerateDistributionByDriversOrOwnersOrAdmins(AuthorizationBase):
- """Distribution drivers, owners, and admins may plan releases.
-
- Drivers of distributions that don't manage their packages in
- Launchpad can create series. Owners and admins can create series for
- all `IDistribution`s.
- """
- permission = 'launchpad.Moderate'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- if user.isDriver(self.obj) and not self.obj.official_packages:
- # Damage to series with packages managed in Launchpad can
- # cause serious strife. Restrict changes to the distro
- # owner.
- return True
- return user.isOwner(self.obj) or user.in_admin
-
-
-class ViewDistributionSourcePackage(AnonymousAuthorization):
- """Anyone can view a DistributionSourcePackage."""
- usedfor = IDistributionSourcePackage
-
-
-class BugSuperviseDistributionSourcePackage(AuthorizationBase):
- """The owner of a distribution should be able to edit its source
- package information"""
- permission = 'launchpad.BugSupervisor'
- usedfor = IDistributionSourcePackage
-
- def checkAuthenticated(self, user):
- return (user.inTeam(self.obj.distribution.bug_supervisor) or
- user.inTeam(self.obj.distribution.owner) or
- user.in_admin)
-
-
-class EditDistributionSourcePackage(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IDistributionSourcePackage
-
- def _checkUpload(self, user, archive, distroseries):
- # We use verifyUpload() instead of checkUpload() because we don't
- # have a pocket. It returns the reason the user can't upload or
- # None if they are allowed.
- if distroseries is None:
- return False
- sourcepackage = distroseries.getSourcePackage(
- self.obj.sourcepackagename)
- reason = archive.verifyUpload(
- user.person, sourcepackagename=self.obj.sourcepackagename,
- component=sourcepackage.latest_published_component,
- distroseries=distroseries)
- return reason is None
-
- def checkAuthenticated(self, user):
- """Anyone who can upload a package can edit it.
-
- Checking upload permission requires a distroseries; a reasonable
- approximation is to check whether the user can upload the package to
- the current series.
- """
- if user.in_admin:
- return True
-
- distribution = self.obj.distribution
- if user.inTeam(distribution.owner):
- return True
-
- return self._checkUpload(
- user, distribution.main_archive, distribution.currentseries)
-
-
-class BugTargetOwnerOrBugSupervisorOrAdmins(AuthorizationBase):
- """Product's owner and bug supervisor can set official bug tags."""
-
- permission = 'launchpad.BugSupervisor'
- usedfor = IOfficialBugTagTargetRestricted
-
- def checkAuthenticated(self, user):
- return (user.inTeam(self.obj.bug_supervisor) or
- user.inTeam(self.obj.owner) or
- user.in_admin)
-
-
-class NominateBugForProductSeries(AuthorizationBase):
- """Product's owners and bug supervisors can add bug nominations."""
-
- permission = 'launchpad.BugSupervisor'
- usedfor = IProductSeries
-
- def checkAuthenticated(self, user):
- return (user.inTeam(self.obj.product.bug_supervisor) or
- user.inTeam(self.obj.product.owner) or
- user.in_admin)
-
-
-class NominateBugForDistroSeries(AuthorizationBase):
- """Distro's owners and bug supervisors can add bug nominations."""
-
- permission = 'launchpad.BugSupervisor'
- usedfor = IDistroSeries
-
- def checkAuthenticated(self, user):
- return (user.inTeam(self.obj.distribution.bug_supervisor) or
- user.inTeam(self.obj.distribution.owner) or
- user.in_admin)
-
+ assert self.obj.target
+ goal = self.obj.goal
+ if goal is not None:
+ if user.isOwner(goal) or user.isDriver(goal):
+ return True
+ return (user.in_admin or
+ user.in_registry_experts or
+ user.isOwner(self.obj.target) or
+ user.isDriver(self.obj.target) or
+ user.isOneOf(
+ self.obj, ['owner', 'drafter', 'assignee', 'approver']))
-class AdminDistroSeries(AdminByAdminsTeam):
- """Soyuz involves huge chunks of data in the archive and librarian,
- so for the moment we are locking down admin and edit on distributions
- and distroseriess to the Launchpad admin team.
- NB: Please consult carefully before modifying this permission because
- changing it could cause the archive to get rearranged, with tons of
- files moved to the new namespace, and mirrors would get very very
- upset.
- """
- permission = 'launchpad.Admin'
- usedfor = IDistroSeries
+class ViewRevisionStatusReport(DelegatedAuthorization):
+ """Anyone who can see a Git repository can see its status reports."""
+ permission = 'launchpad.View'
+ usedfor = IRevisionStatusReport
+ def __init__(self, obj):
+ super().__init__(obj, obj.git_repository, 'launchpad.View')
-class EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins(
- AuthorizationBase):
- """The owner of the distro series (i.e. the owner of the distribution)
- should be able to modify some of the fields on the IDistroSeries
- NB: there is potential for a great mess if this is not done correctly,
- so please consult carefully before modifying these permissions.
- """
+class EditRevisionStatusReport(AuthorizationBase):
+ """The owner of a Git repository can edit its status reports."""
permission = 'launchpad.Edit'
- usedfor = IDistroSeries
+ usedfor = IRevisionStatusReport
def checkAuthenticated(self, user):
- if (user.inTeam(self.obj.driver)
- and not self.obj.distribution.official_packages):
- # Damage to series with packages managed in Launchpad can
- # cause serious strife. Restrict changes to the distro
- # owner.
- return True
- return (user.inTeam(self.obj.distribution.owner) or
- user.in_admin)
-
+ return user.isOwner(self.obj.git_repository)
-class ViewDistroSeries(AnonymousAuthorization):
- """Anyone can view a DistroSeries."""
- usedfor = IDistroSeries
+class ViewRevisionStatusArtifact(DelegatedAuthorization):
+ permission = 'launchpad.View'
+ usedfor = IRevisionStatusArtifact
-class EditDistroSeriesParent(AuthorizationBase):
- """DistroSeriesParent can be edited by the same people who can edit
- the derived_distroseries."""
- permission = "launchpad.Edit"
- usedfor = IDistroSeriesParent
+ def __init__(self, obj):
+ super().__init__(obj, obj.report, 'launchpad.View')
- def checkAuthenticated(self, user):
- auth = EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins(
- self.obj.derived_series)
- return auth.checkAuthenticated(user)
+class EditRevisionStatusArtifact(DelegatedAuthorization):
+ permission = 'launchpad.Edit'
+ usedfor = IRevisionStatusArtifact
-class ViewCountry(AnonymousAuthorization):
- """Anyone can view a Country."""
- usedfor = ICountry
+ def __init__(self, obj):
+ super().__init__(obj, obj.report, 'launchpad.Edit')
-class AdminDistroSeriesDifference(AuthorizationBase):
- """You need to be an archive admin or LP admin to get lp.Admin."""
+class AdminSpecification(AuthorizationBase):
permission = 'launchpad.Admin'
- usedfor = IDistroSeriesDifferenceAdmin
+ usedfor = ISpecification
def checkAuthenticated(self, user):
- # Archive admin is done by component, so here we just
- # see if the user has that permission on any components
- # at all.
- archive = self.obj.derived_series.main_archive
+ assert self.obj.target
return (
- not archive.getComponentsForQueueAdmin(user.person).is_empty() or
- user.in_admin)
-
-
-class EditDistroSeriesDifference(DelegatedAuthorization):
- """Anyone with lp.View on the distribution can edit a DSD."""
- permission = 'launchpad.Edit'
- usedfor = IDistroSeriesDifferenceEdit
+ user.in_admin or
+ user.in_registry_experts or
+ user.isOwner(self.obj.target) or
+ user.isDriver(self.obj.target))
- def __init__(self, obj):
- super().__init__(
- obj, obj.derived_series.distribution, 'launchpad.View')
- def checkUnauthenticated(self):
- return False
+class DriverSpecification(AuthorizationBase):
+ permission = 'launchpad.Driver'
+ usedfor = ISpecification
+ def checkAuthenticated(self, user):
+ # If no goal is proposed for the spec then there can be no
+ # drivers for it - we use launchpad.Driver on a spec to decide
+ # if the person can see the page which lets you decide whether
+ # to accept the goal, and if there is no goal then this is
+ # extremely difficult to do :-)
+ return (
+ self.obj.goal and
+ self.forwardCheckAuthenticated(user, self.obj.goal))
-class SeriesDrivers(AuthorizationBase):
- """Drivers can approve or decline features and target bugs.
- Drivers exist for distribution and product series. Distribution and
- product owners are implicitly drivers too.
+class EditSprintSpecification(AuthorizationBase):
+ """The sprint owner or driver can say what makes it onto the agenda for
+ the sprint.
"""
permission = 'launchpad.Driver'
- usedfor = IHasDrivers
+ usedfor = ISprintSpecification
def checkAuthenticated(self, user):
- return self.obj.personHasDriverRights(user)
-
+ sprint = self.obj.sprint
+ return user.isOwner(sprint) or user.isDriver(sprint) or user.in_admin
-class DriveProduct(SeriesDrivers):
+class DriveSprint(AuthorizationBase):
+ """The sprint owner or driver can say what makes it onto the agenda for
+ the sprint.
+ """
permission = 'launchpad.Driver'
- usedfor = IProduct
+ usedfor = ISprint
def checkAuthenticated(self, user):
- # Commercial admins may help setup commercial projects.
- return (
- super().checkAuthenticated(user)
- or is_commercial_case(self.obj, user)
- or False)
-
-
-class LimitedViewProductSeries(DelegatedAuthorization):
- permission = 'launchpad.LimitedView'
- usedfor = IProductSeriesLimitedView
-
- def __init__(self, obj):
- super().__init__(obj, obj.product, 'launchpad.LimitedView')
+ return (user.isOwner(self.obj) or
+ user.isDriver(self.obj) or
+ user.in_admin)
-class ViewProductSeries(AuthorizationBase):
+class ViewSprint(AuthorizationBase):
+ """An attendee, owner, or driver of a sprint."""
permission = 'launchpad.View'
- usedfor = IProductSeriesView
-
- def checkAuthenticated(self, user):
- return self.obj.userCanView(user)
-
- def checkUnauthenticated(self):
- return self.obj.userCanView(None)
-
-
-class EditProductSeries(EditByOwnersOrAdmins):
- usedfor = IProductSeries
+ usedfor = ISprint
def checkAuthenticated(self, user):
- """Allow product owner, drivers, some experts, or admins."""
- if (user.isOwner(self.obj.product) or
- user.isDriver(self.obj)):
- # The user is the owner of the product, or the release manager.
- return True
- # Rosetta experts need to be able to upload translations.
- # Registry admins are just special.
- if (user.in_registry_experts or
- user.in_rosetta_experts):
- return True
- return EditByOwnersOrAdmins.checkAuthenticated(self, user)
+ return (user.isOwner(self.obj) or
+ user.isDriver(self.obj) or
+ user.person in [attendance.attendee
+ for attendance in self.obj.attendances] or
+ user.in_admin)
-class ViewDistroArchSeries(AnonymousAuthorization):
- """Anyone can view a DistroArchSeries."""
- usedfor = IDistroArchSeries
+class EditSprint(EditByOwnersOrAdmins):
+ usedfor = ISprint
-class ModerateDistroArchSeries(AuthorizationBase):
+class ModerateSprint(ModerateByRegistryExpertsOrAdmins):
+ """The sprint owner, registry experts, and admins can moderate sprints."""
permission = 'launchpad.Moderate'
- usedfor = IDistroArchSeries
+ usedfor = ISprint
def checkAuthenticated(self, user):
return (
- user.isOwner(self.obj.distroseries.distribution.main_archive)
- or user.in_admin)
+ super().checkAuthenticated(user) or
+ user.isOwner(self.obj))
-class ViewDistroArchSeriesFilter(DelegatedAuthorization):
- permission = 'launchpad.View'
- usedfor = IDistroArchSeriesFilter
+class EditSpecificationSubscription(AuthorizationBase):
+ """The subscriber, and people related to the spec or the target of the
+ spec can determine who is essential."""
+ permission = 'launchpad.Edit'
+ usedfor = ISpecificationSubscription
- def __init__(self, obj):
- super().__init__(obj, obj.distroarchseries, 'launchpad.View')
+ def checkAuthenticated(self, user):
+ if self.obj.specification.goal is not None:
+ if user.isDriver(self.obj.specification.goal):
+ return True
+ else:
+ if user.isDriver(self.obj.specification.target):
+ return True
+ return (user.inTeam(self.obj.person) or
+ user.isOneOf(
+ self.obj.specification,
+ ['owner', 'drafter', 'assignee', 'approver']) or
+ user.in_admin)
-class EditDistroArchSeriesFilter(DelegatedAuthorization):
+class EditTranslationsPersonByPerson(AuthorizationBase):
permission = 'launchpad.Edit'
- usedfor = IDistroArchSeriesFilter
-
- def __init__(self, obj):
- super().__init__(obj, obj.distroarchseries, 'launchpad.Moderate')
+ usedfor = ITranslationsPerson
+ def checkAuthenticated(self, user):
+ person = self.obj.person
+ return person == user.person or user.in_admin
-class ViewAnnouncement(AuthorizationBase):
- permission = 'launchpad.View'
- usedfor = IAnnouncement
- def checkUnauthenticated(self):
- """Let anonymous users see published announcements."""
- if self.obj.published:
- return True
- return False
+class OnlyRosettaExpertsAndAdmins(AuthorizationBase):
+ """Base class that allow access to Rosetta experts and Launchpad admins.
+ """
def checkAuthenticated(self, user):
- """Keep project news invisible to end-users unless they are project
- admins, until the announcements are published."""
-
- # Every user can view published announcements.
- if self.obj.published:
- return True
+ """Allow Launchpad's admins and Rosetta experts edit all fields."""
+ return user.in_admin or user.in_rosetta_experts
- # Project drivers can view any project announcements.
- # Launchpad admins can view any announcement.
- assert self.obj.target
- return (user.isDriver(self.obj.target) or
- user.isOwner(self.obj.target) or
- user.in_admin)
+class BugTargetOwnerOrBugSupervisorOrAdmins(AuthorizationBase):
+ """Product's owner and bug supervisor can set official bug tags."""
-class EditAnnouncement(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IAnnouncement
+ permission = 'launchpad.BugSupervisor'
+ usedfor = IOfficialBugTagTargetRestricted
def checkAuthenticated(self, user):
- """Allow the project owner and drivers to edit any project news."""
-
- assert self.obj.target
- return (user.isDriver(self.obj.target) or
- user.isOwner(self.obj.target) or
+ return (user.inTeam(self.obj.bug_supervisor) or
+ user.inTeam(self.obj.owner) or
user.in_admin)
@@ -1725,35 +660,6 @@ class EditCodeImportMachine(OnlyBazaarExpertsAndAdmins):
usedfor = ICodeImportMachine
-class AdminDistributionTranslations(AuthorizationBase):
- """Class for deciding who can administer distribution translations.
-
- This class is used for `launchpad.TranslationsAdmin` privilege on
- `IDistribution` and `IDistroSeries` and corresponding `IPOTemplate`s,
- and limits access to Rosetta experts, Launchpad admins and distribution
- translation group owner.
- """
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IDistribution
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IDistribution` translations settings?
-
- Any Launchpad/Launchpad Translations administrator, translation group
- owner or a person allowed to edit distribution details is able to
- change translations settings for a distribution.
- """
- # Translation group owner for a distribution is also a
- # translations administrator for it.
- translation_group = self.obj.translationgroup
- if translation_group and user.inTeam(translation_group.owner):
- return True
- else:
- return (user.in_rosetta_experts or
- EditDistributionByDistroOwnersOrAdmins(
- self.obj).checkAuthenticated(user))
-
-
class ViewPOTemplates(AnonymousAuthorization):
"""Anyone can view an IPOTemplate."""
usedfor = IPOTemplate
@@ -1910,27 +816,6 @@ class GitRepositoryExpensiveRequest(AuthorizationBase):
return user.in_registry_experts or user.in_admin
-class EditProductRelease(EditByOwnersOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IProductRelease
-
- def checkAuthenticated(self, user):
- if (user.isOwner(self.obj.productseries.product) or
- user.isDriver(self.obj.productseries)):
- # The user is an owner or a release manager.
- return True
- return EditByOwnersOrAdmins.checkAuthenticated(
- self, user)
-
-
-class ViewProductRelease(DelegatedAuthorization):
- permission = 'launchpad.View'
- usedfor = IProductRelease
-
- def __init__(self, obj):
- super().__init__(obj, obj.milestone, 'launchpad.View')
-
-
class AdminTranslationImportQueueEntry(AuthorizationBase):
permission = 'launchpad.Admin'
usedfor = ITranslationImportQueueEntry
@@ -2327,34 +1212,6 @@ class DeleteFAQ(AuthorizationBase):
return user.in_registry_experts or user.in_admin
-def can_edit_team(team, user):
- """Return True if the given user has edit rights for the given team."""
- if user.in_admin:
- return True
- else:
- return team in user.person.getAdministratedTeams()
-
-
-class ViewNameBlacklist(EditByRegistryExpertsOrAdmins):
- permission = 'launchpad.View'
- usedfor = INameBlacklist
-
-
-class EditNameBlacklist(EditByRegistryExpertsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = INameBlacklist
-
-
-class ViewNameBlacklistSet(EditByRegistryExpertsOrAdmins):
- permission = 'launchpad.View'
- usedfor = INameBlacklistSet
-
-
-class EditNameBlacklistSet(EditByRegistryExpertsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = INameBlacklistSet
-
-
class ViewLanguageSet(AnonymousAuthorization):
"""Anyone can view an ILangaugeSet."""
usedfor = ILanguageSet
@@ -2385,12 +1242,10 @@ class AdminCustomLanguageCode(AuthorizationBase):
usedfor = ICustomLanguageCode
def checkAuthenticated(self, user):
- if self.obj.product is not None:
- return AdminProductTranslations(
- self.obj.product).checkAuthenticated(user)
- else:
- return AdminDistributionTranslations(
- self.obj.distribution).checkAuthenticated(user)
+ return self.forwardCheckAuthenticated(
+ user,
+ self.obj.product or self.obj.distribution
+ )
class AccessBranch(AuthorizationBase):
@@ -2599,41 +1454,6 @@ class ViewGitActivity(DelegatedAuthorization):
super().__init__(obj, obj.repository)
-class AdminDistroSeriesTranslations(AuthorizationBase):
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IDistroSeries
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IDistroSeries` translations.
-
- Distribution translation managers and distribution series drivers
- can manage IDistroSeries translations.
- """
- return (user.isDriver(self.obj) or
- self.forwardCheckAuthenticated(user, self.obj.distribution))
-
-
-class AdminDistributionSourcePackageTranslations(DelegatedAuthorization):
- """DistributionSourcePackage objects link to a distribution."""
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IDistributionSourcePackage
-
- def __init__(self, obj):
- super().__init__(obj, obj.distribution)
-
-
-class AdminProductSeriesTranslations(AuthorizationBase):
- permission = 'launchpad.TranslationsAdmin'
- usedfor = IProductSeries
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IProductSeries` translations."""
-
- return (user.isOwner(self.obj) or
- user.isDriver(self.obj) or
- self.forwardCheckAuthenticated(user, self.obj.product))
-
-
class BranchMergeProposalView(AuthorizationBase):
permission = 'launchpad.View'
usedfor = IBranchMergeProposal
@@ -2767,26 +1587,6 @@ class BranchMergeProposalEdit(AuthorizationBase):
return self.forwardCheckAuthenticated(user, self.obj.merge_target)
-class AdminDistroSeriesLanguagePacks(
- OnlyRosettaExpertsAndAdmins,
- EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins):
- permission = 'launchpad.LanguagePacksAdmin'
- usedfor = IDistroSeries
-
- def checkAuthenticated(self, user):
- """Is the user able to manage `IDistroSeries` language packs?
-
- Any Launchpad/Launchpad Translations administrator, people allowed to
- edit distroseries or members of IDistribution.language_pack_admin team
- are able to change the language packs available.
- """
- EditDS = EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins
- return (
- OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
- EditDS.checkAuthenticated(self, user) or
- user.inTeam(self.obj.distribution.language_pack_admin))
-
-
class AdminLanguagePack(OnlyRosettaExpertsAndAdmins):
permission = 'launchpad.LanguagePacksAdmin'
usedfor = ILanguagePack
@@ -3161,22 +1961,6 @@ class EditEmailAddress(EditByOwnersOrAdmins):
return super().checkAuthenticated(user)
-class ViewGPGKey(AnonymousAuthorization):
- usedfor = IGPGKey
-
-
-class ViewSSHKey(AnonymousAuthorization):
- usedfor = ISSHKey
-
-
-class ViewIrcID(AnonymousAuthorization):
- usedfor = IIrcID
-
-
-class ViewWikiName(AnonymousAuthorization):
- usedfor = IWikiName
-
-
class ViewPackageset(AnonymousAuthorization):
"""Anyone can view an IPackageset."""
usedfor = IPackageset
@@ -3279,9 +2063,24 @@ class ViewSourcePackage(AnonymousAuthorization):
usedfor = ISourcePackage
-class EditSourcePackage(EditDistributionSourcePackage):
+class EditSourcePackage(AuthorizationBase):
+ permission = 'launchpad.Edit'
usedfor = ISourcePackage
+ def _checkUpload(self, user, archive, distroseries):
+ # We use verifyUpload() instead of checkUpload() because we don't
+ # have a pocket. It returns the reason the user can't upload or
+ # None if they are allowed.
+ if distroseries is None:
+ return False
+ sourcepackage = distroseries.getSourcePackage(
+ self.obj.sourcepackagename)
+ reason = archive.verifyUpload(
+ user.person, sourcepackagename=self.obj.sourcepackagename,
+ component=sourcepackage.latest_published_component,
+ distroseries=distroseries)
+ return reason is None
+
def checkAuthenticated(self, user):
"""Anyone who can upload a package can edit it."""
if user.in_admin:
Follow ups