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
            }
        }
    }
}

Поэтому вместо написания политик, ограничивающих доступ к вещам, я бы предпочел, чтобы все было неявно определено в схеме. Это потребовало бы определения и ответа. Что может сделать администратор? более интуитивно понятный и легкий для понимания, потому что он записан в одном месте, а не в нескольких политиках.

Я полагаю, что это невозможно, как я описал выше. Что ближе всего к этому? Или есть проблемы с этим подходом?


person Lutan    schedule 09.11.2020    source источник


Ответы (3)


А как насчет директивы @can? Вы можете использовать его в запросе, вводе или поле. С небольшими изменениями можно установить роль вместо разрешения.

Вторая идея - обслуживание другой схемы для разных аутентифицированных пользователей в зависимости от ролей.

person Bartłomiej Gajda    schedule 09.11.2020

Взгляните на мой ответ здесь: https://stackoverflow.com/a/63405046/2397915

Я описываю там два разных подхода для разных целей. В конце концов, эти двое позволяют мне ограничивать каждую часть моей схемы так, как я хочу. Отлично работает в ролевой настройке

person lorado    schedule 10.11.2020

В конце я написал специальную директиву, похожую на ту, что упоминал Лорадо, но немного проще:

<?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")
}
person Lutan    schedule 13.11.2020