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

Passed
Pull Request — master (#731)
by Vincent
08:04
created

AccessResolver::checkAccessForStrictMode()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0113

Importance

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