GraphQL 隐式定义用户可以做什么

GraphQL define implicitly what a user can do

我正在使用 Laravel Lighthouse。

以下情况: 我有用户,应该允许这些用户访问不同的数据集和 运行 不同的突变。

我的解决方法: 为用户分配了角色,这些角色定义了用户可以访问哪些数据集以及可以进行哪些突变 运行.

我卡在了实现上。我能做的是在我的模式中写下我所有的查询和变更,并制定限制对它们的访问的策略。 我更喜欢的是有一种方法可以从模式中看到哪个角色可以访问什么。

我的想法: 每个角色都有一个类型,并在该类型中关联可以访问哪些数据以及可以进行哪些突变运行

这里有一些示例代码可以解释我要做什么,即使语法可能无效:

type Query {
    me: User @auth
}

type User {
    id: ID
    username: String
    first_name: String
    wage: Float
    password: String

    roles: [Role]
    role(name: String! @eq): Role @find
}

type Role {
    id: ID
    name: String
}

type AdminRole {
    #set of users whose data the admin has access to
    #also directly restrict the amount of attributes that are accessible (e.g. password is not accessible)
    #this is invalid syntax, I know
    users: [Users] @all {
        id
        first_name
        wage
    }
    
    #a mutation the admin has access to
    updateUser(id: ID!, wage: Float): User @update
}

我想运行管理员获取所有工资的查询:

query {
    me {
        role(name: "AdminRole") {
            users {
                wage
            }
        }
    }
}

我想 运行 管理员更新用户工资的变更:

mutation {
    me {
        role(name: "AdminRole") {
            updateUser(id: 7, wage: 10.00) {
                id
                wage
            }
        }
    }
}

因此,与其编写限制对事物访问的策略,我宁愿在模式中隐式定义所有内容。这将使定义和回答“管理员可以做什么?”更直观、更容易理解,因为它写在一个地方,而不是几个政策。

我认为按照我上面描述的方式这是不可能的。最接近它的是什么?还是这种方法有问题?

@can 指令呢?您可以在查询、输入或字段上使用它。只需很少的修改就可以设置角色而不是权限。

第二个想法是根据角色为不同的经过身份验证的用户提供其他模式。

在这里看看我的回答:

我描述了针对不同目标的两种不同方法。最后,这两个允许我按照自己的意愿限制模式的每个部分。在基于角色的设置中完美运行

最后我写了一个自定义指令,类似于lorado提到的,但更简单一点:

<?php

namespace App\GraphQL\Directives;

use Closure;
use GraphQL\Language\AST\TypeExtensionNode;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Exceptions\AuthorizationException;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Nuwave\Lighthouse\Support\Contracts\TypeExtensionManipulator;

class RestrictDirective extends BaseDirective implements FieldMiddleware, TypeExtensionManipulator {
    public function name() {
        return "restrict";
    }

    public static function definition(): string {
        return /** @lang GraphQL */ <<<'SDL'
directive @restrict(
    roles: Mixed!
) on FIELD_DEFINITION | OBJECT
SDL;
    }

    public function handleField(FieldValue $fieldValue, Closure $next): FieldValue {
        $resolver = $fieldValue->getResolver();

        $fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) {
            //get the passed rights
            $rights = $this->directiveArgValue("rights");
            if ($rights === null) throw new DefinitionException("Missing argument 'rights' for directive '@restrict'.");
            
            //allow both a single string and an array as input
            if (!is_array($rights)) $rights = [$rights];
            
            //current user, must be logged in
            $user = $context->user();
            if (!$user) $this->no();

            //returns an array of strings
            $user_rights = $user->getAllRightNames();
            
            //this is the part where we check whether the user has the rights or not
            if (empty(array_intersect($user_rights, $rights))) $this->no();

            return $resolver($root, $args, $context, $resolveInfo);
        });

        return $next($fieldValue);
    }

    public function no() {
        throw new AuthorizationException("You are not authorized to access {$this->nodeName()}");
    }

    public function manipulateTypeExtension(DocumentAST &$documentAST, TypeExtensionNode &$typeExtension) {
        ASTHelper::addDirectiveToFields($this->directiveNode, $typeExtension);
    }
}

这样使用:

type User {
    id: ID!
    username: String
    
    extraPayments: [ExtraPayment] @restrict(rights: ["baseWorkingTime", "someOtherRight"])
}

#how to easily restrict a subset of attributes
extend type User @restrict(rights: "baseWorkingTime") {
    wage: Float
    password: String
}

在这里,extraPayments 仅限于至少拥有这两种权利之一的人。通过限制扩展来限制一整套属性。 如果需要,突变也以同样的方式受到限制:

type Mutation {
    test: String @restrict(rights: "baseWorkingTime")
}