Doorkeeper::grantsAccessTo()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RemotelyLiving\Doorkeeper;
6
7
use Psr\Log as PSRLog;
8
use RemotelyLiving\Doorkeeper\Features;
9
use RemotelyLiving\Doorkeeper\Logger;
10
use RemotelyLiving\Doorkeeper\Utilities;
11
12
final class Doorkeeper implements DoorkeeperInterface
13
{
14
    private Features\Set $featureSet;
15
16
    private Utilities\RuntimeCache $runtimeCache;
17
18
    private PSRLog\LoggerInterface $auditLog;
19
20
    private ?RequestorInterface $requestor = null;
21
22
    public function __construct(
23
        Features\Set $featureSet,
24
        Utilities\RuntimeCache $cache = null,
25
        PSRLog\LoggerInterface $auditLog = null
26
    ) {
27
        $this->featureSet = $featureSet;
28
        $this->auditLog = $auditLog ?? new PSRLog\NullLogger();
29
        $this->runtimeCache = $cache ?? new Utilities\RuntimeCache();
30
    }
31
32
    /**
33
     * @throws \DomainException
34
     */
35
    public function setRequestor(RequestorInterface $requestor): void
36
    {
37
        if ($this->requestor) {
38
            throw new \DomainException('Requestor already set');
39
        }
40
41
        $this->requestor = $requestor;
42
    }
43
44
    public function getRequestor(): ?RequestorInterface
45
    {
46
        return $this->requestor;
47
    }
48
49
    public function grantsAccessTo(string $featureName): bool
50
    {
51
        return $this->grantsAccessToRequestor($featureName, $this->requestor);
52
    }
53
54
    public function grantsAccessToRequestor(string $featureName, RequestorInterface $requestor = null): bool
55
    {
56
        $logContext = [
57
            Logger\Processor::CONTEXT_KEY_REQUESTOR => $requestor,
58
            Logger\Processor::FEATURE_ID => $featureName,
59
        ];
60
61
        if (!$this->featureSet->offsetExists($featureName)) {
62
            $this->logAttempt('Access denied because feature does not exist.', $logContext);
63
            return false;
64
        }
65
66
        $cache_key = md5(sprintf('%s:%s', $featureName, ($requestor) ? $requestor->getIdentityHash() : ''));
67
        $fallback = function () use ($featureName, $requestor, $logContext): bool {
68
            $feature = $this->featureSet->getFeatureByName($featureName);
69
70
            if (!$feature->isEnabled()) {
71
                $this->logAttempt('Access denied because feature is disabled.', $logContext);
72
                return false;
73
            }
74
75
            if ($feature->isEnabled() && !$feature->getRules()) {
76
                return true;
77
            }
78
79
            foreach ($feature->getRules() as $rule) {
80
                if ($rule->canBeSatisfied($requestor)) {
81
                    $this->logAttempt('Access granted to feature', $logContext);
82
                    return true;
83
                }
84
            }
85
86
            $this->logAttempt('Access denied to feature', $logContext);
87
            return false;
88
        };
89
90
        return (bool) $this->runtimeCache->get($cache_key, $fallback);
91
    }
92
93
    public function flushRuntimeCache(): void
94
    {
95
        $this->runtimeCache->flush();
96
    }
97
98
    private function logAttempt(string $message, array $context): void
99
    {
100
        $this->auditLog->info($message, $context);
101
    }
102
}
103