Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — master (#731)
by Vincent
25:54 queued 23:15
created

AccessResolver::isMutationRootField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Resolver;
6
7
use GraphQL\Executor\Promise\Adapter\SyncPromise;
8
use GraphQL\Executor\Promise\Promise;
9
use GraphQL\Executor\Promise\PromiseAdapter;
10
use GraphQL\Type\Definition\ListOfType;
11
use GraphQL\Type\Definition\ResolveInfo;
12
use Overblog\GraphQLBundle\Error\UserError;
13
use Overblog\GraphQLBundle\Error\UserWarning;
14
use Overblog\GraphQLBundle\Relay\Connection\Output\Connection;
15
use Overblog\GraphQLBundle\Relay\Connection\Output\Edge;
16
use function array_map;
17
use function is_iterable;
18
19
class AccessResolver
20
{
21
    private PromiseAdapter $promiseAdapter;
22
23 114
    public function __construct(PromiseAdapter $promiseAdapter)
24
    {
25 114
        $this->promiseAdapter = $promiseAdapter;
26 114
    }
27
28
    /**
29
     * @return Promise|mixed|Connection
30
     */
31 15
    public function resolve(callable $accessChecker, callable $resolveCallback, array $resolveArgs = [], array $accessConfig = [])
32
    {
33 15
        $useStrictAccess = $accessConfig['useStrictAccess'];
34 15
        $nullOnDenied = $accessConfig['nullOnDenied'];
35
36 15
        if ($useStrictAccess || self::isMutationRootField($resolveArgs[3])) {
37 13
            return $this->checkAccessForStrictMode($accessChecker, $resolveCallback, $resolveArgs, $nullOnDenied);
38
        }
39
40 5
        $resultOrPromise = $resolveCallback(...$resolveArgs);
41
42 5
        if ($this->isThenable($resultOrPromise)) {
43 2
            return $this->createPromise(
44
                $resultOrPromise,
45 2
                function ($result) use ($accessChecker, $resolveArgs, $nullOnDenied) {
46 2
                    return $this->processFilter($result, $accessChecker, $resolveArgs, $nullOnDenied);
47 2
                }
48
            );
49
        }
50
51 3
        return $this->processFilter($resultOrPromise, $accessChecker, $resolveArgs, $nullOnDenied);
52
    }
53
54 11
    private static function isMutationRootField(ResolveInfo $info): bool
55
    {
56 11
        return 'mutation' === $info->operation->operation && $info->parentType === $info->schema->getMutationType();
57
    }
58
59
    /**
60
     * @return Promise|mixed
61
     */
62 13
    private function checkAccessForStrictMode(callable $accessChecker, callable $resolveCallback, array $resolveArgs = [], bool $nullOnDenied = false)
63
    {
64 13
        $promiseOrHasAccess = $this->hasAccess($accessChecker, $resolveArgs);
65 13
        $callback = function ($hasAccess) use ($resolveArgs, $resolveCallback, $nullOnDenied) {
66 13
            if (true === $hasAccess) {
67 7
                return $resolveCallback(...$resolveArgs);
68
            }
69 6
            if ($nullOnDenied) {
70
                return null;
71
            }
72 6
            $exceptionClassName = self::isMutationRootField($resolveArgs[3]) ? UserError::class : UserWarning::class;
73 6
            throw new $exceptionClassName('Access denied to this field.');
74 13
        };
75
76 13
        if ($this->isThenable($promiseOrHasAccess)) {
77 2
            return $this->createPromise($promiseOrHasAccess, $callback);
78
        } else {
79 11
            return $callback($promiseOrHasAccess);
80
        }
81
    }
82
83
    /**
84
     * @param iterable|object|Connection $result
85
     *
86
     * @return Connection|iterable
87
     */
88 5
    private function processFilter($result, callable $accessChecker, array $resolveArgs, bool $nullOnDenied = false)
89
    {
90
        /** @var ResolveInfo $resolveInfo */
91 5
        $resolveInfo = $resolveArgs[3];
92
93 5
        if (is_iterable($result) && $resolveInfo->returnType instanceof ListOfType) {
94 1
            foreach ($result as $i => $object) {
95 1
                $result[$i] = $this->hasAccess($accessChecker, $resolveArgs, $object) ? $object : null; // @phpstan-ignore-line
96
            }
97 4
        } elseif ($result instanceof Connection) {
98 1
            $result->setEdges(array_map(
99 1
                function (Edge $edge) use ($accessChecker, $resolveArgs) {
100 1
                    $edge->setNode($this->hasAccess($accessChecker, $resolveArgs, $edge->getNode()) ? $edge->getNode() : null);
101
102 1
                    return $edge;
103
                },
104 1
                $result->getEdges()
105
            ));
106 3
        } elseif (!$this->hasAccess($accessChecker, $resolveArgs, $result)) {
107 2
            if ($nullOnDenied) {
108 1
                return null;
109
            }
110 1
            throw new UserWarning('Access denied to this field.');
111
        }
112
113 3
        return $result; // @phpstan-ignore-line
114
    }
115
116
    /**
117
     * @param mixed $object
118
     *
119
     * @return mixed
120
     */
121 15
    private function hasAccess(callable $accessChecker, array $resolveArgs = [], $object = null)
122
    {
123 15
        $resolveArgs[] = $object;
124 15
        $accessOrPromise = $accessChecker(...$resolveArgs);
125
126 15
        return $accessOrPromise;
127
    }
128
129
    /**
130
     * @param mixed $object
131
     */
132 15
    private function isThenable($object): bool
133
    {
134 15
        $object = $this->extractAdoptedPromise($object);
135
136 15
        return $this->promiseAdapter->isThenable($object) || $object instanceof SyncPromise;
137
    }
138
139
    /**
140
     * @param mixed $object
141
     *
142
     * @return SyncPromise|mixed|\React\Promise\Promise
143
     */
144 15
    private function extractAdoptedPromise($object)
145
    {
146 15
        if ($object instanceof Promise) {
147 4
            $object = $object->adoptedPromise;
148
        }
149
150 15
        return $object;
151
    }
152
153
    /**
154
     * @param mixed $promise
155
     */
156 4
    private function createPromise($promise, callable $onFulfilled = null): Promise
157
    {
158 4
        return $this->promiseAdapter->then(
159 4
            new Promise($this->extractAdoptedPromise($promise), $this->promiseAdapter),
160
            $onFulfilled
161
        );
162
    }
163
}
164