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

nette / application / 27919019709

21 Jun 2026 10:08PM UTC coverage: 84.111% (+0.05%) from 84.059%
27919019709

push

github

dg
phpstan fix

2038 of 2423 relevant lines covered (84.11%)

0.84 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 declare(strict_types=1);
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
namespace Nette\Application\UI;
9

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

15

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

24

25
        /** @param  \ReflectionClass<object>|\ReflectionMethod  $element */
26
        public function __construct(
1✔
27
                private readonly \ReflectionClass|\ReflectionMethod $element,
28
        ) {
29
        }
1✔
30

31

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

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

44

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

51

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

63

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

81

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

88
                if ($attribute->actions !== null) {
1✔
89
                        $this->checkActions($attribute->actions);
1✔
90
                }
91

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

96
                if ($attribute->sameOrigin && !$this->presenter->getHttpRequest()->isSameSite()) {
1✔
97
                        $this->presenter->detectedCsrf();
1✔
98
                }
99

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

105

106
        /** @param  list<string>  $actions */
107
        private function checkActions(array $actions): void
1✔
108
        {
109
                if (
110
                        $this->element instanceof \ReflectionMethod
1✔
111
                        && !$this->element->getDeclaringClass()->isSubclassOf(Presenter::class)
1✔
112
                ) {
113
                        throw new \LogicException('Requires(actions) used by ' . Reflection::toString($this->element) . ' is allowed only in presenter.');
1✔
114
                }
115

116
                if (!in_array($this->presenter->getAction(), $actions, strict: true)) {
1✔
117
                        $this->presenter->error("Action '{$this->presenter->getAction()}' is not allowed by " . Reflection::toString($this->element));
1✔
118
                }
119
        }
1✔
120

121

122
        /** @param  list<string>  $methods */
123
        private function checkHttpMethod(array $methods): void
1✔
124
        {
125
                if ($this->element instanceof \ReflectionClass) {
1✔
126
                        $this->presenter->allowedMethods = []; // bypass Presenter::checkHttpMethod()
1✔
127
                }
128

129
                $allowed = array_map(strtoupper(...), $methods);
1✔
130
                $method = $this->presenter->getHttpRequest()->getMethod();
1✔
131

132
                if ($allowed !== ['*'] && !in_array($method, $allowed, strict: true)) {
1✔
133
                        $this->presenter->getHttpResponse()->setHeader('Allow', implode(',', $allowed));
1✔
134
                        $this->presenter->error(
1✔
135
                                "Method $method is not allowed by " . Reflection::toString($this->element),
1✔
136
                                Nette\Http\IResponse::S405_MethodNotAllowed,
1✔
137
                        );
138
                }
139
        }
1✔
140
}
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