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

问题描述

我正在使用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指令呢?您可以在查询,输入或字段上使用它。只需很少的修改,就可以设置角色而不是权限。

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

,

在这里查看我的答案:https://stackoverflow.com/a/63405046/2397915

我描述了针对不同目标的两种不同方法。最后,这两点使我能够限制自己的模式的每个部分。在基于角色的设置中完美工作

,

最后,我写了一个自定义指令,类似于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
}

在这里,额外付款仅限于拥有两项权利中至少一项的人。通过限制扩展来限制整个属性集。 如果需要,可以以相同的方式限制突变:

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