Completed
Push — master ( 1932ea...b49884 )
by Adam
02:19
created

AnnotationChecker::getElementAttribute()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 2
cts 4
cp 0.5
rs 9.2
c 0
b 0
f 0
cc 4
eloc 5
nc 5
nop 2
crap 6
1
<?php
2
/**
3
 * AnnotationChecker.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
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;
25
use IPub\Permissions\Entities;
26
use IPub\Permissions\Exceptions;
27
use IPub\Permissions\Security;
28
29
/**
30
 * Presenter & component annotation access checker
31
 *
32
 * @package        iPublikuj:Permissions!
33
 * @subpackage     Access
34
 *
35
 * @author         Adam Kadlec <[email protected]>
36
 */
37 1
final class AnnotationChecker extends Nette\Object implements IChecker, ICheckRequirements
38
{
39
	/**
40
	 * @var NS\User
41
	 */
42
	private $user;
43
44
	/**
45
	 * @param NS\User $user
46
	 */
47
	public function __construct(NS\User $user)
48
	{
49 1
		$this->user = $user;
50 1
	}
51
52
	/**
53
	 * {@inheritdoc}
54
	 */
55 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...
56
	{
57
		// Check annotations only if element have to be secured
58
		if (
59 1
			$element instanceof \Reflector
60 1
			&& $element->hasAnnotation('Secured')
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method hasAnnotation() does only exist in the following implementations of said interface: Nette\Application\UI\ComponentReflection, Nette\Application\UI\MethodReflection, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, Nette\Reflection\Property.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
61
		) {
62 1
			return $this->checkUser($element)
0 ignored issues
show
Documentation introduced by
$element is of type object<Reflector>, but the function expects a object<Nette\Application...on\UI\MethodReflection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
63 1
			&& $this->checkResources($element)
0 ignored issues
show
Documentation introduced by
$element is of type object<Reflector>, but the function expects a object<Nette\Application...on\UI\MethodReflection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
64 1
			&& $this->checkPrivileges($element)
0 ignored issues
show
Documentation introduced by
$element is of type object<Reflector>, but the function expects a object<Nette\Application...on\UI\MethodReflection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
65 1
			&& $this->checkPermission($element)
0 ignored issues
show
Documentation introduced by
$element is of type object<Reflector>, but the function expects a object<Nette\Application...on\UI\MethodReflection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
66 1
			&& $this->checkRoles($element);
0 ignored issues
show
Documentation introduced by
$element is of type object<Reflector>, but the function expects a object<Nette\Application...on\UI\MethodReflection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
67
68
		} else {
69 1
			return TRUE;
70
		}
71
	}
72
73
	/**
74
	 * @param UI\ComponentReflection|UI\MethodReflection $element
75
	 *
76
	 * @return bool
77
	 *
78
	 * @throws Exceptions\InvalidArgumentException
79
	 */
80 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...
81
	{
82
		// Check if element has @Secured\User annotation
83 1
		if ($element->hasAnnotation('Secured\User')) {
84
			// Get user annotation
85 1
			$user = $element->getAnnotation('Secured\User');
86
87
			// Annotation is single string
88 1
			if (is_string($user) && in_array($user, ['loggedIn', 'guest'], TRUE)) {
89
				// User have to be logged in and is not
90 1
				if ($user === 'loggedIn' && $this->user->isLoggedIn() === FALSE) {
91
					return FALSE;
92
93
				// User have to be logged out and is logged in
94 1
				} elseif ($user === 'guest' && $this->user->isLoggedIn() === TRUE) {
95 1
					return FALSE;
96
				}
97
98
			// Annotation have wrong definition
99
			} else {
100
				throw new Exceptions\InvalidArgumentException('In @Security\User annotation is allowed only one from two strings: \'loggedIn\' & \'guest\'');
101
			}
102
103 1
			return TRUE;
104
		}
105
106 1
		return TRUE;
107
	}
108
109
	/**
110
	 * @param UI\ComponentReflection|UI\MethodReflection $element
111
	 *
112
	 * @return bool
113
	 *
114
	 * @throws Exceptions\InvalidStateException
115
	 */
116
	private function checkResources($element) : bool
117
	{
118
		// Check if element has @Security\Resource annotation & @Secured\Privilege annotation
119 1
		if ($element->hasAnnotation('Secured\Resource')) {
120 1
			$resources = $this->getElementAttribute($element, 'Secured\Resource');
121
122 1
			if (count($resources) != 1) {
123
				throw new Exceptions\InvalidStateException('Invalid resources count in @Security\Resource annotation!');
124
			}
125
126 1
			$privileges = $this->getElementAttribute($element, 'Secured\Privilege');
127
128 1 View Code Duplication
			foreach ($resources as $resource) {
0 ignored issues
show
Bug introduced by
The expression $resources of type false|array 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...
129 1
				if ($privileges !== FALSE) {
130 1
					foreach ($privileges as $privilege) {
131 1
						if ($this->user->isAllowed($resource, $privilege)) {
132 1
							return TRUE;
133
						}
134
					}
135
136
				} else {
137
					if ($this->user->isAllowed($resource)) {
138
						return TRUE;
139
					}
140
				}
141
			}
142
143
			return FALSE;
144
		}
145
146 1
		return TRUE;
147
	}
148
149
	/**
150
	 * @param UI\ComponentReflection|UI\MethodReflection $element
151
	 *
152
	 * @return bool
153
	 *
154
	 * @throws Exceptions\InvalidStateException
155
	 */
156 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...
157
	{
158
		// Check if element has @Secured\Privilege annotation & hasn't @Secured\Resource annotation
159 1
		if (!$element->hasAnnotation('Secured\Resource') && $element->hasAnnotation('Secured\Privilege')) {
160
			$privileges = $this->getElementAttribute($element, 'Secured\Privilege');
161
162
			if (count($privileges) != 1) {
163
				throw new Exceptions\InvalidStateException('Invalid privileges count in @Security\Privilege annotation!');
164
			}
165
166
			foreach ($privileges as $privilege) {
0 ignored issues
show
Bug introduced by
The expression $privileges of type false|array 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...
167
				// Check if privilege name is defined
168
				if ($privilege === TRUE) {
169
					continue;
170
				}
171
172
				if ($this->user->isAllowed(NS\IAuthorizator::ALL, $privilege)) {
173
					return TRUE;
174
				}
175
			}
176
177
			return FALSE;
178
		}
179
180 1
		return TRUE;
181
	}
182
183
	/**
184
	 * @param UI\ComponentReflection|UI\MethodReflection $element
185
	 *
186
	 * @return bool
187
	 */
188 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...
189
	{
190
		// Check if element has @Secured\Permission annotation
191 1
		if ($element->hasAnnotation('Secured\Permission')) {
192 1
			$permissions = $this->getElementAttribute($element, 'Secured\Permission');
193
194 1
			foreach ($permissions as $permission) {
0 ignored issues
show
Bug introduced by
The expression $permissions of type false|array 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...
195
				// Check if parameters are defined
196 1
				if ($permission === TRUE) {
197
					continue;
198
				}
199
200
				// Parse resource & privilege from permission
201 1
				list($resource, $privilege) = explode(Entities\IPermission::DELIMITER, $permission);
202
203
				// Remove white spaces
204 1
				$resource = Utils\Strings::trim($resource);
205 1
				$privilege = Utils\Strings::trim($privilege);
206
207 1
				if ($this->user->isAllowed($resource, $privilege)) {
208 1
					return TRUE;
209
				}
210
			}
211
212
			return FALSE;
213
		}
214
215 1
		return TRUE;
216
	}
217
218
	/**
219
	 * @param UI\ComponentReflection|UI\MethodReflection $element
220
	 *
221
	 * @return bool
222
	 */
223
	private function checkRoles($element) : bool
224
	{
225
		// Check if element has @Secured\Role annotation
226 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...
227 1
			$roles = $this->getElementAttribute($element, 'Secured\Role');
228
229 1
			foreach ($roles as $role) {
0 ignored issues
show
Bug introduced by
The expression $roles of type false|array 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...
230
				// Check if role name is defined
231 1
				if ($role === TRUE) {
232
					continue;
233
				}
234
235 1
				if ($this->user->isInRole($role)) {
236 1
					return TRUE;
237
				}
238
			}
239
240 1
			return FALSE;
241
		}
242
243 1
		return TRUE;
244
	}
245
246
	/**
247
	 * @param \Reflector|UI\ComponentReflection|UI\MethodReflection $element
248
	 * @param string $attribute
249
	 *
250
	 * @return array|FALSE
251
	 */
252
	private function getElementAttribute($element, string $attribute)
253
	{
254 1
		if (class_exists(UI\ComponentReflection::class)) {
255 1
			return UI\ComponentReflection::parseAnnotation($element, $attribute);
256
		}
257
258
		$values = (array) $element->getAnnotation($attribute);
259
260
		return is_array($values) ? $values : ($values ? [$values] : FALSE);
261
	}
262
}
263