Completed
Push — master ( 7dd2dd...7e98b2 )
by Adam
03:24 queued 01:04
created

LinkChecker::isAllowed()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 0
cts 8
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 3
nop 1
crap 20
1
<?php
2
/**
3
 * LinkChecker.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;
21
use Nette\Application\UI;
22
use Nette\Security as NS;
23
24
/**
25
 * Create link access checker
26
 *
27
 * @package        iPublikuj:Permissions!
28
 * @subpackage     Access
29
 *
30
 * @author         Adam Kadlec <[email protected]>
31
 */
32 1
final class LinkChecker extends Nette\Object implements IChecker
33
{
34
	/**
35
	 * @var Application\IPresenterFactory
36
	 */
37
	private $presenterFactory;
38
39
	/**
40
	 * @var Application\Application
41
	 */
42
	private $application;
43
44
	/**
45
	 * @var ICheckRequirements
46
	 */
47
	private $requirementsChecker;
48
49
	/**
50
	 * @param Application\IPresenterFactory $presenterFactory
51
	 * @param Application\Application $application
52
	 * @param ICheckRequirements $requirementsChecker
53
	 */
54
	function __construct(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
55
		Application\IPresenterFactory $presenterFactory,
56
		Application\Application $application,
57
		ICheckRequirements $requirementsChecker
58
	) {
59 1
		$this->presenterFactory = $presenterFactory;
60 1
		$this->application = $application;
61
62
		// Permission annotation access checker
63 1
		$this->requirementsChecker = $requirementsChecker;
64 1
	}
65
66
	/**
67
	 * Check whenever current user is allowed to use given link
68
	 *
69
	 * @param string $element etc "this", ":Admin:Show:default"
70
	 *
71
	 * @return bool
72
	 */
73
	public function isAllowed($element) : bool
74
	{
75
		list($presenter, $action) = $this->formatLink($element);
76
77
		$presenterReflection = new UI\ComponentReflection($this->presenterFactory->getPresenterClass($presenter));
78
79
		if (!$this->requirementsChecker->isAllowed($presenterReflection)) {
80
			return FALSE;
81
		}
82
83
		$actionKey = UI\Presenter::ACTION_KEY . ucfirst($action);
84
85
		if ($presenterReflection->hasMethod($actionKey) && !$this->requirementsChecker->isAllowed($presenterReflection->getMethod($actionKey))) {
86
			return FALSE;
87
		}
88
89
		return TRUE;
90
	}
91
92
	/**
93
	 * Format link to format array('module:submodule:presenter', 'action')
94
	 *
95
	 * @param string $destination
96
	 *
97
	 * @return array(presenter, action)
0 ignored issues
show
Documentation introduced by
The doc-type array(presenter, could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
98
	 */
99
	public function formatLink(string $destination) : array
100
	{
101
		if ($destination === 'this') {
102
			return [$this->application->getPresenter()->getName(), $this->application->getPresenter()->getAction()];
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\Application\IPresenter as the method getName() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

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...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\Application\IPresenter as the method getAction() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

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...
103
		}
104
105
		$parts = explode(':', $destination);
106
107
		if ($destination[0] != ':') {
108
			$current = explode(':', $this->application->getPresenter()->getName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\Application\IPresenter as the method getName() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

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...
109
110
			if (strpos($destination, ':') !== FALSE) {
111
				// Remove presenter
112
				array_pop($current);
113
			}
114
115
			$parts = array_merge($current, $parts);
116
117
		} else {
118
			// Remove empty
119
			array_shift($parts);
120
		}
121
122
		if ($destination[strlen($destination) - 1] == ':') {
123
			// Remove empty
124
			array_pop($parts);
125
126
			$action = UI\Presenter::DEFAULT_ACTION;
127
128
		} else {
129
			$action = array_pop($parts);
130
		}
131
132
		return [implode(':', $parts), $action];
133
	}
134
}
135