|
3 | 3 |
|
4 | 4 | namespace Icinga\Authentication;
|
5 | 5 |
|
| 6 | +use Exception; |
6 | 7 | use Generator;
|
7 | 8 | use Icinga\Application\Config;
|
8 | 9 | use Icinga\Application\Logger;
|
| 10 | +use Icinga\Common\Database; |
9 | 11 | use Icinga\Exception\ConfigurationError;
|
10 |
| -use Icinga\Exception\NotReadableError; |
11 | 12 | use Icinga\Data\ConfigObject;
|
| 13 | +use Icinga\Model\Role as RoleModel; |
| 14 | +use Icinga\Model\RolePermission; |
| 15 | +use Icinga\Model\RoleRestriction; |
12 | 16 | use Icinga\User;
|
13 | 17 | use Icinga\Util\StringHelper;
|
| 18 | +use ipl\Sql\Connection; |
| 19 | +use ipl\Sql\Select; |
| 20 | +use ipl\Stdlib\Filter; |
14 | 21 |
|
15 | 22 | /**
|
16 | 23 | * Retrieve restrictions and permissions for users
|
17 | 24 | */
|
18 | 25 | class AdmissionLoader
|
19 | 26 | {
|
| 27 | + use Database; |
| 28 | + |
20 | 29 | const LEGACY_PERMISSIONS = [
|
21 | 30 | 'admin' => 'application/announcements',
|
22 | 31 | 'application/stacktraces' => 'user/application/stacktraces',
|
@@ -52,12 +61,27 @@ class AdmissionLoader
|
52 | 61 | /** @var ConfigObject */
|
53 | 62 | protected $roleConfig;
|
54 | 63 |
|
| 64 | + /** |
| 65 | + * Database where the roles are stored |
| 66 | + * |
| 67 | + * @var ?Connection |
| 68 | + */ |
| 69 | + protected $rolesDb = null; |
| 70 | + |
55 | 71 | public function __construct()
|
56 | 72 | {
|
57 | 73 | try {
|
58 |
| - $this->roleConfig = Config::app('roles'); |
59 |
| - } catch (NotReadableError $e) { |
60 |
| - Logger::error('Can\'t access roles configuration. An exception was thrown:', $e); |
| 74 | + if (Config::app()->get('global', 'store_roles_in_db')) { |
| 75 | + $db = $this->getDb(); |
| 76 | + |
| 77 | + RoleModel::on($db)->limit(1)->columns('id')->first(); |
| 78 | + |
| 79 | + $this->rolesDb = $db; |
| 80 | + } else { |
| 81 | + $this->roleConfig = Config::app('roles'); |
| 82 | + } |
| 83 | + } catch (Exception $e) { |
| 84 | + Logger::error('Can\'t access roles storage. An exception was thrown:', $e); |
61 | 85 | }
|
62 | 86 | }
|
63 | 87 |
|
@@ -170,6 +194,10 @@ protected function loadRole($name, ConfigObject $section)
|
170 | 194 | */
|
171 | 195 | public function applyRoles(User $user)
|
172 | 196 | {
|
| 197 | + if ($this->rolesDb !== null) { |
| 198 | + $this->applyDbRoles($user); |
| 199 | + } |
| 200 | + |
173 | 201 | if ($this->roleConfig === null) {
|
174 | 202 | return;
|
175 | 203 | }
|
@@ -229,6 +257,138 @@ public function applyRoles(User $user)
|
229 | 257 | $user->setRoles(array_values($roles));
|
230 | 258 | }
|
231 | 259 |
|
| 260 | + /** |
| 261 | + * Apply permissions, restrictions and roles from the database to the given user |
| 262 | + * |
| 263 | + * @param User $user |
| 264 | + */ |
| 265 | + private function applyDbRoles(User $user): void |
| 266 | + { |
| 267 | + $direct = (new Select()) |
| 268 | + ->from('icingaweb_role') |
| 269 | + ->where([ |
| 270 | + 'id IN ?' => (new Select()) |
| 271 | + ->from('icingaweb_role_user') |
| 272 | + ->where(['user_name IN (?)' => [$user->getUsername(), '*']]) |
| 273 | + ->columns('role_id') |
| 274 | + ]) |
| 275 | + ->columns(['id', 'parent_id', 'name', 'unrestricted', 'direct' => '1']); |
| 276 | + |
| 277 | + $userGroups = $user->getGroups(); |
| 278 | + $roleData = []; |
| 279 | + $roles = []; |
| 280 | + $assignedRoles = []; |
| 281 | + $unrestricted = false; |
| 282 | + |
| 283 | + if ($userGroups) { |
| 284 | + $userGroups = array_values($userGroups); |
| 285 | + |
| 286 | + $direct->orWhere([ |
| 287 | + 'id IN ?' => (new Select()) |
| 288 | + ->from('icingaweb_role_group') |
| 289 | + ->where(['group_name IN (?)' => $userGroups]) |
| 290 | + ->columns('role_id') |
| 291 | + ]); |
| 292 | + } |
| 293 | + |
| 294 | + // Not a UNION ALL to handle circular relationships. |
| 295 | + // Due to the "direct" column such may still appear twice. |
| 296 | + // Hence ORDER BY direct, so that the last one (direct=1) wins. |
| 297 | + $query = (new Select()) |
| 298 | + ->with( |
| 299 | + $direct->union( |
| 300 | + (new Select()) |
| 301 | + ->from(['r' => 'icingaweb_role']) |
| 302 | + ->join('rl', 'rl.parent_id = r.id') |
| 303 | + ->columns(['r.id', 'r.parent_id', 'r.name', 'r.unrestricted', 'direct' => '0']) |
| 304 | + ), |
| 305 | + 'rl', |
| 306 | + true |
| 307 | + ) |
| 308 | + ->from('rl') |
| 309 | + ->orderBy('direct') |
| 310 | + ->columns(['id', 'parent_id', 'name', 'unrestricted', 'direct']); |
| 311 | + |
| 312 | + foreach ($this->rolesDb->select($query) as $row) { |
| 313 | + $roleData[$row->id] = $row; |
| 314 | + } |
| 315 | + |
| 316 | + foreach ($roleData as $row) { |
| 317 | + $roles[$row->id] = (new Role()) |
| 318 | + ->setName($row->name) |
| 319 | + ->setIsUnrestricted($row->unrestricted); |
| 320 | + |
| 321 | + if ($row->direct) { |
| 322 | + $assignedRoles[] = $row->name; |
| 323 | + } |
| 324 | + |
| 325 | + if ($row->unrestricted) { |
| 326 | + $unrestricted = true; |
| 327 | + } |
| 328 | + } |
| 329 | + |
| 330 | + foreach ($roleData as $row) { |
| 331 | + if ($row->parent_id) { |
| 332 | + $parent = $roles[$row->parent_id]; |
| 333 | + $child = $roles[$row->id]; |
| 334 | + |
| 335 | + $child->setParent($parent); |
| 336 | + $parent->addChild($child); |
| 337 | + } |
| 338 | + } |
| 339 | + |
| 340 | + $filter = Filter::equal('role_id', array_keys($roles)); |
| 341 | + $permissions = []; |
| 342 | + $allPermissions = []; |
| 343 | + $refusals = []; |
| 344 | + $restrictions = []; |
| 345 | + $allRestrictions = []; |
| 346 | + |
| 347 | + foreach (RolePermission::on($this->rolesDb)->filter($filter) as $row) { |
| 348 | + if ($row->allowed) { |
| 349 | + $permissions[$row->role_id][] = $row->permission; |
| 350 | + } |
| 351 | + |
| 352 | + if ($row->denied) { |
| 353 | + $refusals[$row->role_id][] = $row->permission; |
| 354 | + } |
| 355 | + } |
| 356 | + |
| 357 | + foreach ($permissions as $roleId => & $rolePermissions) { |
| 358 | + list($rolePermissions, $newRefusals) = $this->migrateLegacyPermissions($rolePermissions); |
| 359 | + |
| 360 | + if ($newRefusals) { |
| 361 | + array_push($refusals[$roleId], ...$newRefusals); |
| 362 | + } |
| 363 | + |
| 364 | + $roles[$roleId]->setPermissions($rolePermissions); |
| 365 | + array_push($allPermissions, ...$rolePermissions); |
| 366 | + } |
| 367 | + |
| 368 | + foreach ($refusals as $roleId => $roleRefusals) { |
| 369 | + $roles[$roleId]->setRefusals($roleRefusals); |
| 370 | + } |
| 371 | + |
| 372 | + foreach (RoleRestriction::on($this->rolesDb)->filter($filter) as $row) { |
| 373 | + $restrictions[$row->role_id][$row->restriction] = $row->filter; |
| 374 | + } |
| 375 | + |
| 376 | + foreach ($restrictions as $roleId => & $roleRestrictions) { |
| 377 | + foreach ($roleRestrictions as $name => & $restriction) { |
| 378 | + $restriction = str_replace('$user.local_name$', $user->getLocalUsername(), $restriction); |
| 379 | + $allRestrictions[$name][] = $restriction; |
| 380 | + } |
| 381 | + |
| 382 | + $roles[$roleId]->setRestrictions($roleRestrictions); |
| 383 | + } |
| 384 | + |
| 385 | + $user->setAdditional('assigned_roles', $assignedRoles); |
| 386 | + $user->setIsUnrestricted($unrestricted); |
| 387 | + $user->setRestrictions($unrestricted ? [] : $allRestrictions); |
| 388 | + $user->setPermissions(array_values(array_unique($allPermissions))); |
| 389 | + $user->setRoles(array_values($roles)); |
| 390 | + } |
| 391 | + |
232 | 392 | public static function migrateLegacyPermissions(array $permissions)
|
233 | 393 | {
|
234 | 394 | $migratedGrants = [];
|
|
0 commit comments