bolt/bolt — pull request #2297 by mathias213 (2014-12-08 13:03:03+00:00)
Title: Add Per Content Permissions with default set to ROLE_ANONYMOUS

This patch is to add permissions set at content level in addition to those set at content type level.

The idea to start that change came from there: https://github.com/bolt/bolt/issues/1263 where almost all needed tool to proceed were prepared. In that thread tobias2k warned about ACLs using terms "bloat" and "creepy" as ACLs are quickly complex.

To limit the complexity inside Bolt's code it was decided to use a user defined select field added to each content types you want to protect. This select will contain all groups of roles you want to be able to set up on these content type's contents.

A new role keyword is defined: "viewers". This role keyword is used in permissions.yml to protect some content types.
Ex:

pages:
frontend: [ viewers ]

Then in contenttypes.yml the select is created:
Ex: this is an extract of "pages" content type declaration with addition of "viewers" select field

template:
type: templateselect
filter: '*.twig'
viewers:
type: select
default: anonymous
values: [ anonymous, everyone, owner, 'admin, editor' ]
taxonomy: [ chapters ]

Here each content of this content type would be grant-able to:
- role "anonymous": all users, a public content.
- role "everyone": only connected users
- role "owner": only owner could see the content in frontend
- roles "admin" or "editor": you would need to belong to admin OR editor to access that content.

So here was the way to extract from Bolt a part of permissions complexity.

As most complexity is pushed on user side now the point - IMHO - would be to help users using this system. The code modification is meant to that purpose.
This modification applies only on frontend content - or at least is meant to apply only there - and it applies in two ways:
- the checkPermission() function in Permissions.php check also per content permissions when needed to add all roles contained in database field "viewers" in available roles array then these available roles are compared to current user's roles
- the executeGetContentQueries() in Storage.php is also modified to add a SQL filter when retrieving multiple contents. This filter avoid breaking limit parameter sent to requests.

So once the patch is added to the code user would need to:
- modify some content type in contenttypes.yml to add "viewers" select.
- modify this content type permissions in permissions.yml to add this internal "viewers" role to "frontend"
- set to "true" flag "frontend_permission_checks" in config.yml
And contents will be filtered.

At this point Bolt will complain the database must be updated, if user do it Bolt will add an empty "viewers" field to this content type table structure. To avoid the whole site becomes unavailable in frontend as no "viewers" roles are defined in our empty field the default behaviour is to consider the content as public and so granted to ROLE_ANONYMOUS in case "viewers" is empty.

How listing views work:

Considering the modified content type is still "pages" with "frontend: [ 'viewers' ]" in permissions.yml.

For now all fallback behaviour (I was able to see) are set to give anonymous access to contents. The two cases are:
- listing pages (ex: /pages)
- empty "viewers" field

When a user access to /pages with "frontend: [ 'viewers' ]" in permissions.yml as there is no database item for that listing page there is no "viewers" field that why the fallback.
When twigs are building this page they call getContent() or record() which would call getContent() which finally launch executeGetContentQueries().

executeGetContentQueries() receives $decoded array which contains all we need to know if this query or list of queries are to retrieve one or more item.

When executeGetContentQueries() tries to retrieve a list of items the SQL filter is applied which returns only database items according to user's roles including ownership.

This filter applies only if these 3 parameters are defined, if one is missing, no filter, no per content permissions:
- viewed page is in frontend
- "frontend_permission_checks: true" in config.yml
- "frontend: [ 'viewers' ]" in permissions.yml
So it is easily disable

But it is also quiet easily applied. Bolt simplify lot of things letting us doing complex stuffs as if we were good in creating web site. This is to simplify per content filtering.

This version moves added arguments at end of functions declaration. Thanks to GDmac.