Passed
Push — php8 ( bd75dc...d8afb4 )
by Fabio
08:15
created

TPermissionsBehavior::setManager()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 10
1
<?php
2
/**
3
 * TPermissionsBehavior class file.
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Security\Permissions;
11
12
use Prado\Prado;
13
use Prado\TApplicationMode;
14
use Prado\Util\IDynamicMethods;
15
use Prado\Util\TBehavior;
16
use Prado\Util\TLogger;
17
18
/**
19
 * TPermissionsBehavior class.
20
 *
21
 * TPermissionsBehavior class is a class behavior attached to {@link IPermissions}.
22
 * This class calls getPermissions to get an array of {@link TPermissionEvent}
23
 * and/or to have the implementation register their own permissions.
24
 * Any returned TPermissionEvents will have their permission registered for rules.
25
 *
26
 * This class also handles all dynamic events and when a listed Event from a
27
 * TPermissionEvent is raised, this code checks if the current application
28
 * user permission is checked.
29
 *
30
 * Example getPermissions method:
31
 * <code>
32
 *	public function getPermissions($manager) {
33
 * 		$manager->registerPermission('module_perm_edit', 'Short Description');
34
 *		return [ new TPermissionEvent('module_perm_name', 'Short Description.', ['dyPermissionAction', 'dyOtherAction']) ];
35
 *	}
36
 * </code>
37
 *
38
 * In this example, the methods dyPermissionAction and dyOtherAction would have an
39
 * authorization check on the given permission.
40
 *
41
 * The way to implement a dynamic event is like this, from the example above:
42
 * the first return value parameter is always false.
43
 * <code>
44
 *	public function myFunctionToAuth($param1, $param2) {
45
 *		if ($this->dyPermissionAction(false, $param1, $param2) === true)
46
 *			return false;
47
 *		....
48
 *		return true;
49
 *	}
50
 *	</code>
51
 * Together, TPermissionsBehavior will check the user for the 'module_perm_name'
52
 * permission.
53
 *
54
 * This can be alternatively implemented as a call to the user::can, eg
55
 * <code>
56
 *  	if(!Prado::getApplication()->getUser()->can('module_perm_name'))
57
 *			return false;
58
 * </code>
59
 *
60
 * The application user is available on and after the onAuthenticationComplete
61
 * in the application stack.
62
 *
63
 * The default is to allow without any rules in place.  To automatically
64
 * block functionality, there needs to be a (final) Permission Rule to deny all.
65
 * The TPermissionsManager, by default, adds a final rule to deny all on all
66
 * permissions via {@link TPermissionsManager::setAutoDenyAll}.
67
 *
68
 * The {@link TUserPermissionsBehavior} attaches to {@link TUser} to
69
 * provide {@link TUserPermissionsBehavior::can}, whether or note a user has authorization for a
70
 * permission.
71
 *
72
 * @author Brad Anderson <[email protected]>
73
 * @since 4.2.0
74
 */
75
class TPermissionsBehavior extends TBehavior implements IDynamicMethods
76
{
77
	/** @var TPermissionsManager manager object for the behavior */
78
	private $_manager;
79
80
	/** @var array<string, string[]> key is the dynamic event, values are the permission names to check */
81
	private $_permissionEvents;
82
83
	/** @var \Prado\Security\Permissions\TPermissionEvent[] */
84
	private $_events;
85
86
	/**
87
	 * @param null|\Prado\Security\Permissions\TPermissionsManager $manager
88
	 */
89
	public function __construct($manager = null)
90
	{
91
		if ($manager) {
92
			$this->setPermissionsManager($manager);
93
		}
94
		parent::__construct();
95
	}
96
97
	/**
98
	 * @param \Prado\TComponent $owner the object being attached to
99
	 */
100
	public function attach($owner)
101
	{
102
		parent::attach($owner);
103
		if (method_exists($owner, 'getPermissions')) {
104
			$manager = $this->getPermissionsManager();
105
			$this->_permissionEvents = [];
106
			$this->_events = $owner->getPermissions($manager) ?? [];
0 ignored issues
show
Bug introduced by
The method getPermissions() does not exist on Prado\TComponent. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

106
			$this->_events = $owner->/** @scrutinizer ignore-call */ getPermissions($manager) ?? [];
Loading history...
107
			foreach ($this->_events as $permEvent) {
108
				$perm = $permEvent->getName();
109
				foreach ($permEvent->getEvents() as $event) {
110
					$this->_permissionEvents[$event][] = $perm;
111
				}
112
				$manager->registerPermission($perm, $permEvent->getDescription(), $permEvent->getRules());
113
			}
114
		}
115
	}
116
117
	/**
118
	 * If in a proper dynamic event, checks if the application user
119
	 * can perform a permission, if it can't, flag as handled.
120
	 * @param string $method the dynamic event method being called
121
	 * @param array $args the arguments to the method
122
	 * @return bool|mixed if the $method is handled (where appropriate)
123
	 */
124
	public function __dycall($method, $args)
125
	{
126
		$callchain = array_pop($args);
127
		if (!$callchain instanceof \Prado\Util\TCallChain) {
128
			array_push($args, $callchain);
129
			return $args[0] ?? null;
130
		}
131
132
		$event = strtolower($method);
133
		/** @var TUserPermissionsBehavior $user */
134
		$user = Prado::getApplication()->getUser();
135
		if (isset($this->_permissionEvents[$event]) && $user) {
136
			$can = true;
137
			$extra = array_slice($args, -1)[0] ?? null;
138
			if (is_array($extra) && isset($extra['extra'])) {
139
				$extra = $extra['extra'];
140
			} else {
141
				$extra = null;
142
			}
143
			foreach ($this->_permissionEvents[$event] as $permission) {
144
				$can &= $user->can($permission, $extra);
145
			}
146
			return call_user_func_array([$callchain, $method], $args) === true || !$can;
147
		}
148
		return call_user_func_array([$callchain, $method], $args);
149
	}
150
151
	/**
152
	 * @return \Prado\Security\Permissions\TPermissionEvent[]
153
	 */
154
	public function getPermissionEvents()
155
	{
156
		return $this->_events ?? [];
157
	}
158
159
	/**
160
	 * Gets the TPermissionsManager for the behavior
161
	 * @return \Prado\Security\Permissions\TPermissionsManager manages application permissions
162
	 */
163
	public function getPermissionsManager()
164
	{
165
		return $this->_manager;
166
	}
167
168
	/**
169
	 * Sets the TPermissionsManager for the behavior
170
	 * @param \Prado\Security\Permissions\TPermissionsManager|\WeakReference $manager manages application permissions
171
	 */
172
	public function setPermissionsManager($manager)
173
	{
174
		if (class_exists('\WeakReference', false) && $manager instanceof \WeakReference) {
175
			$manager = $manager->get();
176
		}
177
		$this->_manager = $manager;
0 ignored issues
show
Documentation Bug introduced by
It seems like $manager can also be of type WeakReference. However, the property $_manager is declared as type Prado\Security\Permissions\TPermissionsManager. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
178
	}
179
180
	/**
181
	 * This logs permissions failures in the Prado::log and with the shell when
182
	 * the Application is a TShellApplication.  When the Application is in Debug
183
	 * mode, the failed permission is written to shell-cli, otherwise the exact
184
	 * permission is keep hidden from the shell user for security purposes.
185
	 * @param array|string $permission the permission(s) that failed.
186
	 * @param string $action short description of the action that failed.
187
	 * @param \Prado\Util\TCallChain $callchain the series of dynamic events being raised.
188
	 * @param mixed $permission
189
	 */
190
	public function dyLogPermissionFailed($permission, $action, $callchain)
191
	{
192
		$app = Prado::getApplication();
193
		$user = $app->getUser();
194
		$name = '(undefined)';
195
		if ($user) {
0 ignored issues
show
introduced by
$user is of type Prado\Security\IUser, thus it always evaluated to true.
Loading history...
196
			$name = $user->getName();
197
		}
198
		$permission = ($a = is_array($permission)) ? implode('", "', $permission) : $permission;
199
		$s = $a ? 's' : '';
200
		$permission = $s . ' ("' . $permission . '")';
201
202
		Prado::log('@' . $name . ' failed permission' . $permission . ' when "' . $action . '"', TLogger::WARNING, 'Prado.Permission.TPermissionsBehavior');
203
204
		$permission = ($app->getMode() == TApplicationMode::Debug) ? $permission : 's';
205
		if ($app instanceof \Prado\Shell\TShellApplication) {
206
			$writer = $app->getWriter();
207
			$writer->writeError('@' . $name . ' failed permission' . $permission . ' when "' . $action . '"');
208
		}
209
		return $callchain->dyLogPermissionFailed($permission, $action);
0 ignored issues
show
Bug introduced by
The method dyLogPermissionFailed() does not exist on Prado\Util\TCallChain. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
		return $callchain->/** @scrutinizer ignore-call */ dyLogPermissionFailed($permission, $action);
Loading history...
210
	}
211
}
212