Passed
Push — master ( f5ec54...94ecb9 )
by Fabio
05:41
created

TPermissionsBehavior::setPermissionsManager()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
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
	use TPermissionsManagerPropertyTrait;
78
79
	/** @var array<string, string[]> key is the dynamic event, values are the permission names to check */
80
	private $_permissionEvents;
81
82
	/** @var \Prado\Security\Permissions\TPermissionEvent[] */
83
	private $_events;
84
85
	/**
86
	 * @param \Prado\TComponent $owner the object being attached to
87
	 */
88
	public function attach($owner)
89
	{
90
		parent::attach($owner);
91
		if (method_exists($owner, 'getPermissions')) {
92
			$manager = $this->getPermissionsManager();
93
			if (!$manager) {
0 ignored issues
show
introduced by
$manager is of type Prado\Security\Permissions\TPermissionsManager, thus it always evaluated to true.
Loading history...
94
				return;
95
			}
96
			$this->_permissionEvents = [];
97
			$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

97
			$this->_events = $owner->/** @scrutinizer ignore-call */ getPermissions($manager) ?? [];
Loading history...
98
			foreach ($this->_events as $permEvent) {
99
				$perm = $permEvent->getName();
100
				foreach ($permEvent->getEvents() as $event) {
101
					$this->_permissionEvents[$event][] = $perm;
102
				}
103
				$manager->registerPermission($perm, $permEvent->getDescription(), $permEvent->getRules());
104
			}
105
		}
106
	}
107
108
	/**
109
	 * If in a proper dynamic event, checks if the application user
110
	 * can perform a permission, if it can't, flag as handled.
111
	 * @param string $method the dynamic event method being called
112
	 * @param array $args the arguments to the method
113
	 * @return bool|mixed if the $method is handled (where appropriate)
114
	 */
115
	public function __dycall($method, $args)
116
	{
117
		$callchain = array_pop($args);
118
		if (!($callchain instanceof \Prado\Util\TCallChain)) {
119
			array_push($args, $callchain);
120
			return $args[0] ?? null;
121
		}
122
123
		$event = strtolower($method);
124
		/** @var TUserPermissionsBehavior $user */
125
		$user = Prado::getApplication()->getUser();
126
		if (isset($this->_permissionEvents[$event]) && $user) {
127
			$can = true;
128
			$extra = array_slice($args, -1)[0] ?? null;
129
			if (is_array($extra) && isset($extra['extra'])) {
130
				$extra = $extra['extra'];
131
			} else {
132
				$extra = null;
133
			}
134
			foreach ($this->_permissionEvents[$event] as $permission) {
135
				$can &= $user->can($permission, $extra);
136
			}
137
			return call_user_func_array([$callchain, $method], $args) === true || !$can;
138
		}
139
		return call_user_func_array([$callchain, $method], $args);
140
	}
141
142
	/**
143
	 * @return \Prado\Security\Permissions\TPermissionEvent[]
144
	 */
145
	public function getPermissionEvents()
146
	{
147
		return $this->_events ?? [];
148
	}
149
150
	/**
151
	 * This logs permissions failures in the Prado::log and with the shell when
152
	 * the Application is a TShellApplication.  When the Application is in Debug
153
	 * mode, the failed permission is written to shell-cli, otherwise the exact
154
	 * permission is keep hidden from the shell user for security purposes.
155
	 * @param array|string $permission the permission(s) that failed.
156
	 * @param string $action short description of the action that failed.
157
	 * @param \Prado\Util\TCallChain $callchain the series of dynamic events being raised.
158
	 * @param mixed $permission
159
	 */
160
	public function dyLogPermissionFailed($permission, $action, $callchain)
161
	{
162
		$app = Prado::getApplication();
163
		$user = $app->getUser();
164
		$name = '(undefined)';
165
		if ($user) {
0 ignored issues
show
introduced by
$user is of type Prado\Security\IUser, thus it always evaluated to true.
Loading history...
166
			$name = $user->getName();
167
		}
168
		$permission = ($a = is_array($permission)) ? implode('", "', $permission) : $permission;
169
		$s = $a ? 's' : '';
170
		$permission = $s . ' ("' . $permission . '")';
171
172
		Prado::log('@' . $name . ' failed permission' . $permission . ' when "' . $action . '"', TLogger::WARNING, TPermissionsBehavior::class);
173
174
		$permission = ($app->getMode() == TApplicationMode::Debug) ? $permission : 's';
175
		if ($app instanceof \Prado\Shell\TShellApplication) {
176
			$writer = $app->getWriter();
177
			$writer->writeError('@' . $name . ' failed permission' . $permission . ' when "' . $action . '"');
178
		}
179
		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

179
		return $callchain->/** @scrutinizer ignore-call */ dyLogPermissionFailed($permission, $action);
Loading history...
180
	}
181
}
182