31.4. Permissions [voting story]#
In this part you will:
Add custom permissions
Protect the voting service using permissions
Configure which roles get the permissions
Topics covered:
Permissions
The rolemap
Check out mastering-plone-votable-add-on at tag actions:
git checkout actions
The code at the end of the chapter:
git checkout permissions
More info in The code for the training
We have a working voting add-on, but it currently allows too many people to vote. Currently, anyone who can view the talks can vote. We should add custom permissions so that we can make sure only the conference program committee can vote.
Add custom permissions#
Plone has lots of built-in permissions like View and Modify portal content.
We can also add our own custom permissions in our add-on.
Edit the file backend/src/ploneconf/votable/permissions.zcml:
1<configure
2 xmlns="http://namespaces.zope.org/zope"
3 xmlns:zcml="http://namespaces.zope.org/zcml"
4 i18n_domain="plone"
5 >
6
7 <permission
8 id="ploneconf.votable.view_vote"
9 title="ploneconf.votable: View votes"
10 />
11
12 <permission
13 id="ploneconf.votable.can_vote"
14 title="ploneconf.votable: Can vote"
15 />
16
17 <permission
18 id="ploneconf.votable.clear_votes"
19 title="ploneconf.votable: Clear votes"
20 />
21
22</configure>
We are adding three permissions:
View voteswill give access to view the voting resultsCan votewill give access to add a voteClear voteswill give access to reset the votes
We use the add-on name ploneconf.votable as a prefix for the permission names, to make sure they don't conflict with other add-ons.
Each permission has an id and a title.
They are used in different places.
The id is used when referring to permissions in ZCML, such as in a view declaration.
The title is used when checking permissions with plone.api.
Check permissions#
Now we can update the @votes services to check these permissions.
Update {file}`backend/src/ploneconf/votable/services/votes.py:
1from plone import api
2from plone.protect.interfaces import IDisableCSRFProtection
3from plone.restapi.deserializer import json_body
4from plone.restapi.services import Service
5from ploneconf.votable.behaviors.votable import IVotable
6from zExceptions import Unauthorized
7from zope.interface import alsoProvides
8
9
10class VotingGet(Service):
11 """Get voting information about the current object"""
12
13 def reply(self):
14 if not api.user.has_permission(
15 "ploneconf.votable: View votes", obj=self.context
16 ):
17 raise Unauthorized("User not authorized to view votes.")
18 return vote_info(self.context)
19
20
21class VotingPost(Service):
22 """Vote for an object"""
23
24 def reply(self):
25 alsoProvides(self.request, IDisableCSRFProtection)
26 voting = IVotable(self.context)
27 if not api.user.has_permission("ploneconf.votable: Can vote", obj=self.context):
28 raise Unauthorized("User not authorized to vote.")
29 data = json_body(self.request)
30 vote = data["rating"]
31 voting.vote(vote)
32
33 return vote_info(self.context)
34
35
36class VotingDelete(Service):
37 """Clear votes for an object"""
38
39 def reply(self):
40 alsoProvides(self.request, IDisableCSRFProtection)
41 if not api.user.has_permission(
42 "ploneconf.votable: Clear votes", obj=self.context
43 ):
44 raise Unauthorized("User not authorized to clear votes.")
45 voting = IVotable(self.context)
46 voting.clear()
47 return vote_info(self.context)
48
49
50def vote_info(obj):
51 """Returns voting information about the given object."""
52 voting = IVotable(obj)
53 info = {
54 "average_vote": voting.average_vote(),
55 "total_votes": voting.total_votes(),
56 "has_votes": voting.has_votes(),
57 "already_voted": voting.already_voted(),
58 "can_vote": api.user.has_permission("ploneconf.votable: Can vote", obj=obj),
59 "can_clear_votes": api.user.has_permission(
60 "ploneconf.votable: Clear votes", obj=obj
61 ),
62 }
63 return info
Raising the Unauthorized exception returns an HTTP response with status 401.
Tip
We could also update the permission for the services in configure.zcml.
But doing the check in Python lets us return a more informative error response.
We're also returning can_vote and can_clear_votes in the vote_info data,
so that the frontend component can check what the user is allowed to do.
Configure the rolemap#
Permissions are designed to provide flexibility about who actually gets the permission. We need to configure the rolemap to define which roles get the permissions.
Update the file backend/src/ploneconf/votable/profiles/default/rolemap.xml.
1<?xml version="1.0" encoding="utf-8"?>
2<rolemap>
3 <permissions>
4
5 <permission acquire="True"
6 name="ploneconf.votable: View votes"
7 >
8 <role name="Authenticated" />
9 <role name="Site Administrator" />
10 <role name="Manager" />
11 </permission>
12 <permission acquire="True"
13 name="ploneconf.votable: Can vote"
14 >
15 <role name="Reviewer" />
16 </permission>
17 <permission acquire="True"
18 name="ploneconf.votable: Clear votes"
19 >
20 <role name="Site Administrator" />
21 <role name="Manager" />
22 </permission>
23
24 </permissions>
25</rolemap>
Any authenticated user is allowed to view the voting results.
Only users with the Reviewer role are allowed to add votes. (For the conference site, it would make sense to put the Program Committee members in a group, and assign the Reviewer role to that group.)
Only users with the Site Administrator or Manager roles are allowed to reset the votes.
After reinstalling the add-on, the updated rolemap takes effect.