AnnotationChecker   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 231
Duplicated Lines 56.28 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 72.21%

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 2
dl 130
loc 231
ccs 52
cts 72
cp 0.7221
rs 8.8
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B isAllowed() 17 17 7
B checkUser() 28 28 8
B checkResources() 14 32 8
B checkPrivileges() 26 26 7
A checkPermission() 29 29 5
A checkRoles() 16 22 5
A getElementAttribute() 0 10 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AnnotationChecker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationChecker, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * AnnotationChecker.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        https://www.ipublikuj.eu
7
 * @author         Adam Kadlec <[email protected]>
8
 * @package        iPublikuj:Permissions!
9
 * @subpackage     Access
10
 * @since          1.0.0
11
 *
12
 * @date           13.10.14
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\Permissions\Access;
18
19
use Nette;
20
use Nette\Application\UI;
21
use Nette\Utils;
22
use Nette\Security as NS;
23
24
use IPub\Permissions\Entities;
25
use IPub\Permissions\Exceptions;
26
27
/**
28
 * Presenter & component annotation access checker
29
 *
30
 * @package        iPublikuj:Permissions!
31
 * @subpackage     Access
32
 *
33
 * @author         Adam Kadlec <[email protected]>
34
 */
35 1
final class AnnotationChecker implements IChecker, ICheckRequirements
36
{
37
	/**
38
	 * Implement nette smart magic
39
	 */
40 1
	use Nette\SmartObject;
41
42
	/**
43
	 * @var NS\User
44
	 */
45
	private $user;
46
47
	/**
48
	 * @param NS\User $user
49
	 */
50
	public function __construct(NS\User $user)
51
	{
52 1
		$this->user = $user;
53 1
	}
54
55
	/**
56
	 * {@inheritdoc}
57
	 */
58 View Code Duplication
	public function isAllowed($element) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
59
	{
60
		// Check annotations only if element have to be secured
61
		if (
62 1
			$element instanceof \Reflector
63 1
			&& $element->hasAnnotation('Secured')
64
		) {
65 1
			return $this->checkUser($element)
66 1
			&& $this->checkResources($element)
67 1
			&& $this->checkPrivileges($element)
68 1
			&& $this->checkPermission($element)
69 1
			&& $this->checkRoles($element);
70
71
		} else {
72 1
			return TRUE;
73
		}
74
	}
75
76
	/**
77
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
78
	 *
79
	 * @return bool
80
	 *
81
	 * @throws Exceptions\InvalidArgumentException
82
	 */
83 View Code Duplication
	private function checkUser($element) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
84
	{
85
		// Check if element has @Secured\User annotation
86 1
		if ($element->hasAnnotation('Secured\User')) {
87
			// Get user annotation
88 1
			$user = $element->getAnnotation('Secured\User');
89
90
			// Annotation is single string
91 1
			if (is_string($user) && in_array($user, ['loggedIn', 'guest'], TRUE)) {
92
				// User have to be logged in and is not
93 1
				if ($user === 'loggedIn' && $this->user->isLoggedIn() === FALSE) {
94
					return FALSE;
95
96
				// User have to be logged out and is logged in
97 1
				} elseif ($user === 'guest' && $this->user->isLoggedIn() === TRUE) {
98 1
					return FALSE;
99
				}
100
101
			// Annotation have wrong definition
102
			} else {
103
				throw new Exceptions\InvalidArgumentException('In @Security\User annotation is allowed only one from two strings: \'loggedIn\' & \'guest\'');
104
			}
105
106 1
			return TRUE;
107
		}
108
109 1
		return TRUE;
110
	}
111
112
	/**
113
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
114
	 *
115
	 * @return bool
116
	 *
117
	 * @throws Exceptions\InvalidStateException
118
	 */
119
	private function checkResources($element) : bool
120
	{
121
		// Check if element has @Security\Resource annotation & @Secured\Privilege annotation
122 1
		if ($element->hasAnnotation('Secured\Resource')) {
123 1
			$resources = $this->getElementAttribute($element, 'Secured\Resource');
124
125 1
			if (count($resources) != 1) {
126
				throw new Exceptions\InvalidStateException('Invalid resources count in @Security\Resource annotation!');
127
			}
128
129 1
			$privileges = $this->getElementAttribute($element, 'Secured\Privilege');
130
131 1 View Code Duplication
			foreach ($resources as $resource) {
0 ignored issues
show
Bug introduced by
The expression $resources of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132 1
				if ($privileges !== FALSE) {
133 1
					foreach ($privileges as $privilege) {
134 1
						if ($this->user->isAllowed($resource, $privilege)) {
135 1
							return TRUE;
136
						}
137
					}
138
139
				} else {
140
					if ($this->user->isAllowed($resource)) {
141
						return TRUE;
142
					}
143
				}
144
			}
145
146
			return FALSE;
147
		}
148
149 1
		return TRUE;
150
	}
151
152
	/**
153
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
154
	 *
155
	 * @return bool
156
	 *
157
	 * @throws Exceptions\InvalidStateException
158
	 */
159 View Code Duplication
	private function checkPrivileges($element) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
	{
161
		// Check if element has @Secured\Privilege annotation & hasn't @Secured\Resource annotation
162 1
		if (!$element->hasAnnotation('Secured\Resource') && $element->hasAnnotation('Secured\Privilege')) {
163
			$privileges = $this->getElementAttribute($element, 'Secured\Privilege');
164
165
			if (count($privileges) != 1) {
166
				throw new Exceptions\InvalidStateException('Invalid privileges count in @Security\Privilege annotation!');
167
			}
168
169
			foreach ($privileges as $privilege) {
0 ignored issues
show
Bug introduced by
The expression $privileges of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
170
				// Check if privilege name is defined
171
				if ($privilege === TRUE) {
172
					continue;
173
				}
174
175
				if ($this->user->isAllowed(NS\IAuthorizator::ALL, $privilege)) {
176
					return TRUE;
177
				}
178
			}
179
180
			return FALSE;
181
		}
182
183 1
		return TRUE;
184
	}
185
186
	/**
187
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
188
	 *
189
	 * @return bool
190
	 */
191 View Code Duplication
	private function checkPermission($element) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192
	{
193
		// Check if element has @Secured\Permission annotation
194 1
		if ($element->hasAnnotation('Secured\Permission')) {
195 1
			$permissions = $this->getElementAttribute($element, 'Secured\Permission');
196
197 1
			foreach ($permissions as $permission) {
0 ignored issues
show
Bug introduced by
The expression $permissions of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
198
				// Check if parameters are defined
199 1
				if ($permission === TRUE) {
200
					continue;
201
				}
202
203
				// Parse resource & privilege from permission
204 1
				list($resource, $privilege) = explode(Entities\IPermission::DELIMITER, $permission);
205
206
				// Remove white spaces
207 1
				$resource = Utils\Strings::trim($resource);
208 1
				$privilege = Utils\Strings::trim($privilege);
209
210 1
				if ($this->user->isAllowed($resource, $privilege)) {
211 1
					return TRUE;
212
				}
213
			}
214
215
			return FALSE;
216
		}
217
218 1
		return TRUE;
219
	}
220
221
	/**
222
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
223
	 *
224
	 * @return bool
225
	 */
226
	private function checkRoles($element) : bool
227
	{
228
		// Check if element has @Secured\Role annotation
229 1 View Code Duplication
		if ($element->hasAnnotation('Secured\Role')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230 1
			$roles = $this->getElementAttribute($element, 'Secured\Role');
231
232 1
			foreach ($roles as $role) {
0 ignored issues
show
Bug introduced by
The expression $roles of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
233
				// Check if role name is defined
234 1
				if ($role === TRUE) {
235
					continue;
236
				}
237
238 1
				if ($this->user->isInRole($role)) {
239 1
					return TRUE;
240
				}
241
			}
242
243 1
			return FALSE;
244
		}
245
246 1
		return TRUE;
247
	}
248
249
	/**
250
	 * @param UI\ComponentReflection|UI\MethodReflection|Nette\Reflection\ClassType|Nette\Reflection\Method|\Reflector $element
251
	 * @param string $attribute
252
	 *
253
	 * @return array|FALSE
254
	 */
255
	private function getElementAttribute($element, string $attribute)
256
	{
257 1
		if (class_exists('Nette\Application\UI\ComponentReflection')) {
258 1
			return UI\ComponentReflection::parseAnnotation($element, $attribute);
259
		}
260
261
		$values = (array) $element->getAnnotation($attribute);
262
263
		return is_array($values) ? $values : ($values ? [$values] : FALSE);
264
	}
265
}
266