41. Making Our Package Reusable – Mastering Plone 5 development

41. Making Our Package Reusable#

In this part you will:

  • Add Permissions

Topics covered:

  • Permissions

The package contains some problems.

  • No permission settings, Users can't customize who and when users can vote

  • We do things, but don't trigger events. Events allow others to react.

41.1. Adding permissions#

Permissions have a long history, there are two types of permissions.

In Zope2, a permission was just a string.

In ZTK, a permission is an object that gets registered as a Utility.

We must support both, in some cases we have to reference the permission by their Zope2 version, in some by their ZTK Version.

Luckily, there is a zcml statement to register a permission both ways in one step.

See also

The configuration registry was meant to solve a problem, but we will now stumble over a problem that did not get resolved properly.

Our permission is a utility. Our browser views declare this permission as a requirement for viewing them.

When our browser views get registered, the permissions must exist already. If you try to register the permissions after the views, Zope won't start because it doesn't know about the permissions.

Let's modify the file configure.zcml

 1<configure xmlns="...">
 3  <includeDependencies package="." />
 5  <permission
 6      id="starzel.votable_behavior.view_vote"
 7      title="starzel.votable_behavior: View Vote"
 8      />
10  <permission
11      id="starzel.votable_behavior.do_vote"
12      title="starzel.votable_behavior: Do Vote"
13      />
15  <include package=".browser" />
17  ...

In some places we have to reference the Zope 2 permission strings. It is best practice to provide a static variable for this.

We provide this in __init__.py

2DoVote = 'starzel.votable_behavior: Do Vote'
3ViewVote = 'starzel.votable_behavior: View Vote'

41.2. Using our permissions#

As you can see, we created two permissions, one for voting, one for viewing the votes.

If a user is not allowed to see the votes, she does not need access to the vote viewlet.

While we are at it, if a user can't vote, she needs no access to the helper view to actually submit a vote.

We can add this restriction to browser/configure.zcml

 2  xmlns="http://namespaces.zope.org/zope"
 3  xmlns:browser="http://namespaces.zope.org/browser"
 4  i18n_domain="starzel.votable_behavior">
 6  <browser:viewlet
 7    name="voting"
 8    for="starzel.votable_behavior.interfaces.IVotable"
 9    manager="plone.app.layout.viewlets.interfaces.IBelowContentTitle"
10    template="templates/voting_viewlet.pt"
11    layer="..interfaces.IVotableLayer"
12    class=".viewlets.Vote"
13    permission="starzel.votable_behavior.view_vote"
14    />
16  <browser:page
17    name="vote"
18    for="starzel.votable_behavior.interfaces.IVotable"
19    layer="..interfaces.IVotableLayer"
20    class=".vote.Vote"
21    permission="starzel.votable_behavior.do_vote"
22    />
24  ...

We are configuring components, so we use the component name of the permission, which is the id part of the declaration we added earlier.

See also

So, what happens if we do not protect the browser view to vote?

The person could still vote, by handcrafting the URL. Browser Views run code without any restriction, it is your job to take care of security.

But... if a person has no access to the object at all, maybe because the site is configured that Anonymous users cannot access private objects, the unauthorized users will not be able to submit a vote.

That is because Zope checks security permissions when trying to find the right object. If it can't find the object due to security constraints not met, no view ill ever be called, because that would have been the next step.

We now protect our views and viewlets. We still show the option to vote though.

We must add a condition in our page template, and we must provide the condition information in our viewlet class.

Lets move on to browser/viewlets.py.

 1# ...
 3from starzel.votable_behavior import DoVote
 6class Vote(base.ViewletBase):
 8#   ...
 9    can_vote = None
11    def update(self):
13#       ...
15        if self.is_manager is None:
16            membership_tool = getToolByName(self.context, 'portal_membership')
17            self.is_manager = membership_tool.checkPermission(
18                ViewManagementScreens,
19                self.context,
20            )
21            self.can_vote = membership_tool.checkPermission(
22                DoVote,
23                self.context,
24            )
26#  ...

And the template in browser/templates/voting_viewlet.pt

 1<tal:snippet omit-tag="">
 2  <div class="voting">
 4    ...
 6    <div id="notyetvoted" class="voting_option"
 7            tal:condition="view/can_vote">
 8      What do you think of this talk?
 9      <div class="votes"><span id="voting_plus">+1</span> <span id="voting_neutral">0</span> <span id="voting_negative">-1</span>
10      </div>
11    </div>
12    <div id="no_ratings" tal:condition="not: view/has_votes">
13      This talk has not been voted yet.<span tal:omit-tag="" tal:condition="view/can_vote"> Be the first!</span>
14    </div>
16  ...
18  </div>

Sometimes subtle bugs come up because of changes. In this case I noticed that I should only prompt people to vote if they are allowed to vote!

41.3. Provide defaults#

Are we done yet? Who may vote now?

We have to tell that someone.

Who has which permissions is managed in Zope. This is persistent, and persistent configuration is handled by GenericSetup.

The persistent configuration is managed in another file: profiles/default/rolemap.xml

 1<?xml version="1.0"?>
 3  <permissions>
 4    <permission name="starzel.votable_behavior: View Vote" acquire="True">
 5      <role name="Anonymous"/>
 6    </permission>
 7    <permission name="starzel.votable_behavior: Do Vote" acquire="True">
 8      <role name="Anonymous"/>
 9    </permission>
10  </permissions>