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
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:
svc
loop opens a new connection to the API for some reason.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:
@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....