Set content access based on list of users ids

I have:

  • a custom content type: FooBar (folderish)
  • a list of users subscribed to a FooBar item (so a list of users ids for each FooBar item) - FooBar members

I need to have the possibility to allow access to FooBar contents (child items) only for FooBar members (subscribed users in FooBar item's context).

The list of subscribed users is changed multiple times (when a user is approved, rejected, deleted etc).
So, the permissions will be automatically updated in order to give access to particular files only to FooBar members.
Also a member of FooBar item A will not see the contents of FooBar item B only if he subscribes to B, too.

What is the best solution in this case? Will I create a plone group of users for each FooBar item or is there a simpler solution?

(Plone 4)

Only some files inside of a FooBar item will be restricted. Some of them will have the default behavior (private / published etc). It's OK if I can set the access option for a folder and the access is set recursively for its contents. The new implementation will work as an added filter to existing default permissions. So, private items will still be private.

Can't you just grant or revoke local roles on the related folder?

1 Like

I'm trying to imagine this. Can you add some details?
I have kind of 30.000 accounts.

So, by default when a folder is added to a FooBar item no restrictions are added.
Then let's say I want to restrict the access. Will I revoke view permission for all close to 30.000 users to added folder?

Plone is using the View permission to control access to content items. Thus, your "list of item subscribed users` could in principle be local role assignments at this item.

There are several potential pitfalls:

  • by default local roles at a content item affect this item and its descendants. This is mostly relevant only for folders. Maybe, this behaviour is what you want.

  • you must partially reindex content items to make a local role change effective.

  • the View permission is typically controlled by the workflow. You must be aware that granting the View permission via local roles restricts this control.

If you are ready to use your own content types for the affected items, you can make the local roles attribute a property and ensure by its definition that your "subscribed users list" behaves like local role assignments for the View permission.

1 Like

@GhitaB I don't understand your spec fully, but I think I recognise workspace like functionality in here, where you want to have local permissions based on users being a member of a workspace.

collective.workspace is an add'on you could take a look at, it is implemented as a behavior you activate on your FooBar contenttype. I did some testing with it with a Dexterity type in Plone 4 about two years ago. I remember there being some scalability issues with the rosters/local membership and permissions, but these might have been solved in the meanwhile. https://github.com/collective/collective.workspace

Plone Intranet/Quaive uses a fork of collective.workspace in its own product namespace which has separate development.

1 Like

H[quote="GhitaB, post:4, topic:7471"]
'm trying to imagine this. Can you add some details?

I have kind of 30.000 accounts.

So, by default when a folder is added to a FooBar item no restrictions are added.

Then let's say I want to restrict the access. Will I revoke view permission for all close to 30.000 users to added folder?
[/quote]

Zope supports the computation of local roles at runtime...would this be an option?

1 Like

Yes -- in some way: the "no restrictions" is achieved by some "global" (user independent in this case) View permission assignment. When you want to restrict the access, you revoke/inhibit this global assignment and replace it by user dependent assignments.

1 Like

Perhaps look at https://pypi.org/project/borg.localrole/

I can provide some example code. We use this to inject additional roles for a particular user based on some business constraints that we resolve at runtime.

1 Like

Huh, this sounds like the problem is worded up side down and so, the solution sounds difficult. You can first block acquisition of permissions and start over fresh for the new subtree and second you should then use the feature of groups, (custom) workflow and local roles to achieve the goal.

1 Like
from AccessControl import Unauthorized
from my.product.interfaces import ICustomWorkspace
from plone import api
from plone.dexterity.content import Container
from zope.globalrequest import getRequest
from zope.interface import implementer


@implementer(ICustomWorkspace)
class CustomWorkspace(Container):
    """ Custom Workspace content type"""

    def block_access(self, workspace):
        raise Unauthorized(workspace)

    @property
    def __ac_local_roles__(self):
        """ Manage custom roles for specific cases
            This container and its child items should be accessed only by
            workspace members (subscribers)
        """
        # This code runs for this container and also for all its child items
        request = getRequest()
        workspace = [
            x for x in request.PARENTS[:-1]
            if x.portal_type == 'custom.workspace'
            ][0]
        parent = workspace.aq_parent

        subscribers = parent.get_subscribers()

        approved_subscribers_ids = [
            subscriber.userid for subscriber in subscribers
            if subscriber.state() == "approved"
        ]

        is_anonymous = api.user.is_anonymous()
        if not is_anonymous:
            current_user = api.user.get_current()

            if current_user.id in approved_subscribers_ids:
                is_workspace_member = True
                print "MEMBER: TODO allow access."

            else:
                is_workspace_member = False
                print "NO MEMBER: TODO block access."
                self.block_access(workspace)
        else:
            print "ANONYMOUS: TODO block access."
            self.block_access(workspace)
        return {}

I simplified the implementation a little:

  • i have a custom content type (container): CustomWorkspace (addable only to FooBar content type where my users are subscribed)
  • when a user tries to open it or any sub-items (child items of CustomWorkspace item) I verify if the user is a member of my workspace (it's ID is in my list of approved subscribers). If not, just raise Unauthorized.

TODO: do not block administrators. I expect this part to be easy. Just verify the roles of current user in workspace context.

What do you think about this solution? It seems working for me.

For me, it seems suboptimal.

First, it abuses __ac_local_roles__ destined to store local roles at the object (used by Plone's sharing functionality). Your solution interferes with this usage.

Second, it does not prevent searches to find those objects. As a consequence, an unpriviledged user can see a (search) summary for those objects and some search hits are in fact not accessible (likely without hint).

If you have a small amount of subscription sets, I would model those sets by Plone (user) groups - and use Plone's "sharing" to associate a respective group with a subhierarchy. You may need a special workflow in addition to avoid that published objects grant View to anonymous. A solution along these lines would allow a standard Plone catalog to only return "accessible" objects.

If you cannot model your subscription sets as Plone groups, I would use an extended catalog. The standard Plone catalog uses the index allowed_roles_and_users to ensure that (by default) it returns only objects for which the searching user as the View permission. To this end, it internally "and"s the user search with a subsearch based on allowed_roles_and_users. Your extended catalog would add a new index (maybe approved_subscribers) and "and" to user searches a subsearch based on this index.

In the first case, no explicit access block should be necessary (unpriviledged users should lack the View permission). In the second case, a "post authentication hook" could be used to block the access. If your objects do not provide direct access without the use of main_template (File objects can be accessed without main_template), then a simpler viewlet based approach is possible: the viewlet shows nothing but checks the access rules.

1 Like