在石墨烯-django中分离公共API和私有API的推荐最佳实践是什么?

时间:2019-09-25 07:02:38

标签: permissions graphql graphene-django

我已经看到了许多关于如何在石墨烯中实现许可系统的讨论,但是除了这些讨论之外,还没有看到任何确定的实际结果。有关该主题的一些讨论示例如下:

不幸的是,这些都不是推荐在石墨烯中实现权限的首选方法。有谁知道这样做的最佳实践是什么?

1 个答案:

答案 0 :(得分:0)

首先,仅当公共接口与私有接口有显着差异时,才将API端点拆分为公共/私有。否则,您将面临代码问题中的冗余。

在我们的项目中,我们提出了一个简单的解决方案,似乎许多人都将其作为所需的解决方案。

我们在resolve方法上使用以下修饰符:

# 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

您可以按如下方式使用此装饰器:

# 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"

如果您没有此特定权限our_app.some_perm,则会收到以下响应:

{
  "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
          }
        }
      ]
    }
  }
}

当您需要更富有表现力的方式来检查权限时,例如 使用or语句检查多个权限,请在@required_permission装饰器中使用lambda:


@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