• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / application / 19799648203

30 Nov 2025 01:31PM UTC coverage: 81.757% (+0.2%) from 81.549%
19799648203

push

github

dg
mockery dev [WIP]

1945 of 2379 relevant lines covered (81.76%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.08
/src/Application/UI/AccessPolicy.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Application\UI;
11

12
use Nette;
13
use Nette\Application\Attributes;
14
use Nette\Utils\Reflection;
15
use function array_map, implode, in_array, str_starts_with, strtoupper;
16

17

18
/**
19
 * Manages access control to presenter elements based on attributes and built-in rules.
20
 * @internal
21
 */
22
final class AccessPolicy
23
{
24
        private Presenter $presenter;
25

26

27
        public function __construct(
1✔
28
                private readonly \ReflectionClass|\ReflectionMethod $element,
29
        ) {
30
        }
1✔
31

32

33
        public function checkAccess(Component $component): void
1✔
34
        {
35
                $this->presenter ??= $component->getPresenterIfExists() ??
1✔
36
                        throw new Nette\InvalidStateException('Presenter is required for checking requirements of ' . Reflection::toString($this->element));
×
37

38
                $attrs = $this->getAttributes();
1✔
39
                $attrs = self::applyInternalRules($attrs, $component);
1✔
40
                foreach ($attrs as $attribute) {
1✔
41
                        $this->checkAttribute($attribute);
1✔
42
                }
43
        }
1✔
44

45

46
        public function canGenerateLink(): bool
47
        {
48
                $attrs = $this->getAttributes();
1✔
49
                return !$attrs || !Nette\Utils\Arrays::some($attrs, fn($attr) => $attr->forward === true);
1✔
50
        }
51

52

53
        /**
54
         * @return Attributes\Requires[]
55
         */
56
        private function getAttributes(): array
57
        {
58
                return array_map(
1✔
59
                        fn($ra) => $ra->newInstance(),
1✔
60
                        $this->element->getAttributes(Attributes\Requires::class, \ReflectionAttribute::IS_INSTANCEOF),
1✔
61
                );
62
        }
63

64

65
        private function applyInternalRules(array $attrs, Component $component): array
1✔
66
        {
67
                if (
68
                        $this->element instanceof \ReflectionMethod
1✔
69
                        && str_starts_with($this->element->getName(), $component::formatSignalMethod(''))
1✔
70
                        && !ComponentReflection::parseAnnotation($this->element, 'crossOrigin')
1✔
71
                        && !Nette\Utils\Arrays::some($attrs, fn($attr) => $attr->sameOrigin === false)
1✔
72
                ) {
73
                        $attrs[] = new Attributes\Requires(sameOrigin: true);
1✔
74
                }
75
                return $attrs;
1✔
76
        }
77

78

79
        private function checkAttribute(Attributes\Requires $attribute): void
1✔
80
        {
81
                if ($attribute->methods !== null) {
1✔
82
                        $this->checkHttpMethod($attribute);
1✔
83
                }
84

85
                if ($attribute->actions !== null) {
1✔
86
                        $this->checkActions($attribute);
1✔
87
                }
88

89
                if ($attribute->forward && !$this->presenter->isForwarded()) {
1✔
90
                        $this->presenter->error('Forwarded request is required by ' . Reflection::toString($this->element));
1✔
91
                }
92

93
                if ($attribute->sameOrigin && !$this->presenter->getHttpRequest()->isSameSite()) {
1✔
94
                        $this->presenter->detectedCsrf();
1✔
95
                }
96

97
                if ($attribute->ajax && !$this->presenter->getHttpRequest()->isAjax()) {
1✔
98
                        $this->presenter->error('AJAX request is required by ' . Reflection::toString($this->element), Nette\Http\IResponse::S403_Forbidden);
1✔
99
                }
100
        }
1✔
101

102

103
        private function checkActions(Attributes\Requires $attribute): void
1✔
104
        {
105
                if (
106
                        $this->element instanceof \ReflectionMethod
1✔
107
                        && !$this->element->getDeclaringClass()->isSubclassOf(Presenter::class)
1✔
108
                ) {
109
                        throw new \LogicException('Requires(actions) used by ' . Reflection::toString($this->element) . ' is allowed only in presenter.');
1✔
110
                }
111

112
                if (!in_array($this->presenter->getAction(), $attribute->actions, strict: true)) {
1✔
113
                        $this->presenter->error("Action '{$this->presenter->getAction()}' is not allowed by " . Reflection::toString($this->element));
1✔
114
                }
115
        }
1✔
116

117

118
        private function checkHttpMethod(Attributes\Requires $attribute): void
1✔
119
        {
120
                if ($this->element instanceof \ReflectionClass) {
1✔
121
                        $this->presenter->allowedMethods = []; // bypass Presenter::checkHttpMethod()
1✔
122
                }
123

124
                $allowed = array_map(strtoupper(...), $attribute->methods);
1✔
125
                $method = $this->presenter->getHttpRequest()->getMethod();
1✔
126

127
                if ($allowed !== ['*'] && !in_array($method, $allowed, strict: true)) {
1✔
128
                        $this->presenter->getHttpResponse()->setHeader('Allow', implode(',', $allowed));
1✔
129
                        $this->presenter->error(
1✔
130
                                "Method $method is not allowed by " . Reflection::toString($this->element),
1✔
131
                                Nette\Http\IResponse::S405_MethodNotAllowed,
1✔
132
                        );
133
                }
134
        }
1✔
135
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc