First of all splitting API endpoints into public/private make sense only when public interface differs significantly from private one. If not then you will face redundancy in code problem.
In our project we came up with simple solution which seems to be pointed by many as desired solution.
We use the following decorator on resolve
methods:
# decorators.py
def permission_required(permission):
""" Checking permissions on per method basis. """
def wrapped_decorator(func):
def inner(cls, info, *args, **kwargs):
if check_permission(permission, info.context):
return func(cls, info, **kwargs)
raise Exception("Permission Denied.")
return inner
return wrapped_decorator
def check_permission(permission, context):
"""
Helper function to resolve permissions.
Permission can be a string "app_name.perm_codename"
or callable (lambda) function with user passed as an argument:
example: lambda(user): user.username.startswith('a')
"""
if callable(permission):
if not permission(context.user):
return False
else:
if not context.user.has_perm(permission):
return False
return True
You can use this decorator as follows:
# schema.py
from . decorators import permission_required
class UserNode(DjangoObjectType):
class Meta:
model = User
interfaces = (relay.Node,)
only_fields = (
'id', 'first_name', 'last_name',
'email', 'username'
)
filter_fields = {
'username': ['exact'],
'id': ['exact'],
}
role = graphene.String(description="User's role in the system.")
@permission_required('our_app.some_perm')
def resolve_role(self, info, **kwargs):
if info.context.user.username in ['dev1', 'dev2']:
return "developer"
if info.context.user.is_superuser:
return "admin"
if info.context.user.is_staff:
return "staff"
return "guest"
If you don't have this particular permission our_app.some_perm
you'll get the following response:
{
"errors": [
{
"message": "Permission Denied.",
"locations": [
{
"line": 7,
"column": 9
}
],
"path": [
"userSet",
"edges",
0,
"node",
"role"
]
},
{
"message": "Permission Denied.",
"locations": [
{
"line": 7,
"column": 9
}
],
"path": [
"userSet",
"edges",
1,
"node",
"role"
]
}
],
"data": {
"userSet": {
"edges": [
{
"node": {
"id": "VXNlck5vZGU6MQ==",
"username": "user1",
"role": null
}
},
{
"node": {
"id": "VXNlck5vZGU6Mg==",
"username": "user2",
"role": null
}
}
]
}
}
}
When you need more expressive way to check permission, for example when
checking multiple permissions with or
statement, use lambdas in @required_permission
decorator:
@permission_required(lambda u: u.has_perm('app.perm1') or u.has_perm('app.perm2'))
def resolve_something1(self, info, **kwargs):
# ... do your stuff here
return data
@permission_required(lambda user: user.username.startswith('a'))
def resolve_something2(self, info, **kwargs):
# ... do your stuff here
return data