Check if current user has permission for pyramid service?

1.2k views Asked by At

I'm using Cornice and Pyramid with ACL Auth. This is a duplicate of an older question which I'm re-asking as Pyramid has changed.

Current docs say that pyramid.security.has_permission has ben replaced with request.has_permission which has an optional context arg. I'm trying to use has_permission in a loop through all the services to see which services the current user (request) has access to.

The end goal is to dynamically scan all Cornice services (ie view files with Cornice's @resource decorator) to see which ones are authorized for a given permission (ie 'view') for the current user. I'm open to using another way of doing this besides has_permission.

The use case of this knowledge is to provide a Swagger Spec JSON document which only documents API endpoints available to the current user.

I'm expecting code to look something like this:

from cornice import service
# Get our list of services
services = service.get_services()
# Assume we have an authenticated user logged in, thus attaching auth info to request
for svc in services:
    context = magic_context_function(svc)
    if request.has_permission('view', context) == False:
        # Code will go here to hide endpoint documentation for this endpoint
1

There are 1 answers

0
hamx0r On BEST ANSWER

It seems the answer should be to use view_execution_permitted(context, request, name=''), but I can't get it to work with an arbitrary view name, as the name arg does not match to a cornice.service.name value.

However, here's a semi-solution from a Pyramid issue on Github . You'll need a few imports to make the linked solution work (better). Here's the full code

from pyramid.security import _get_registry, Allowed
from pyramid.interfaces import IRouteRequest, IRequest, IViewClassifier, ISecuredView, IView
from zope.interface import providedBy
def route_view_execution_permitted(context, request, route_name, name=''):
    reg = _get_registry(request)
    context_iface = providedBy(context)
    request_iface = reg.queryUtility(
        IRouteRequest,
        name=route_name,
        default=IRequest)
    provides = (IViewClassifier, request_iface, context_iface)

    view = reg.adapters.lookup(provides, ISecuredView, name=name)
    if view is None:
        view = reg.adapters.lookup(provides, IView, name=name)
        if view is None:
            raise TypeError('No registered view satisfies the constraints. '
                            'It would not make sense to claim that this view '
                            '"is" or "is not" permitted.')
        return Allowed(
            'Allowed: view name %r in context %r for route %r (no permission defined)' %
            (name, context, route_name))
    return view.__permitted__(context, request)

One can use the above function to determine if the current user (determined from request object) is able to access a service (by name) like so:

from cornice import service
services = service.get_services()
for svc in services:
  view_permitted = route_view_execution_permitted(request.context, request, svc.name)
  if view_permitted == True:
      # Do something interesting...

I found that the above solution has two deficiencies:

  1. It's slow because each iteration of the svc loop opens a new connection to the API for some reason.
  2. It returns an erroneous results (ie says one has permission for services he does not, and vice versa)

Perhaps someone can see a way to improve the answer above. In the meantime, here's a solution using the ACL's attached to each service and then determining if the current request.effective_principals match.

# Now see if current user meets ACL requirements for any permission
is_permitted = None  # set our default.
for ace in acl:
    for principal in request.effective_principals:
        if ace[1] == principal:
            is_permitted = True if ace[0] == Allow else False
            break
    if is_permitted is not None:
        break

if is_permitted is True:
    # Do something interesting...

The weaknesses here are:

  1. It's slow for the same reason as the previous solution
  2. As implemented, it only looks at @resource-decorated service classes, and not the @view-decorated methods which may have permissions or acls of their own.

This can be remedied with something like:

for method, view, args in service.definitions:
    if 'permission' in args:
        # Now start looking at permission to see if they match what's given by the parent ACL in the resource class
        # Also, the special "__no_permission_required__" value means we should not have a Security Requirement Object
        if args['permission'] == NO_PERMISSION_REQUIRED :
            # Interesting....