Test Failed
Pull Request — master (#80)
by Maximo
05:58
created

Manager::isAllowed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 42
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 4
dl 0
loc 42
ccs 21
cts 21
cp 1
crap 2
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gewaer\Acl;
6
7
use Phalcon\Db;
8
use Phalcon\Db\AdapterInterface as DbAdapter;
9
use Phalcon\Acl\Exception;
10
use Phalcon\Acl\Resource;
11
use Phalcon\Acl;
12
use Phalcon\Acl\Role;
13
use Phalcon\Acl\RoleInterface;
14
use Gewaer\Models\Companies;
15
use Gewaer\Models\Apps;
16
use Gewaer\Models\Roles as RolesDB;
17
use Gewaer\Models\AccessList as AccessListDB;
18
use Gewaer\Models\Resources as ResourcesDB;
19
use Phalcon\Acl\Adapter;
20
use BadMethodCallException;
21
use Gewaer\Exception\ModelException;
22
use Gewaer\Models\ResourcesAccesses;
23
24
/**
25
 * Class Manager
26
 *
27
 * Manages Geweaer Multi tenant ACL lists in database
28
 *
29
 * @package Gewaer\Acl
30
 *
31
 * @property Users $userData
32
 * @property Request $request
33
 */
34
class Manager extends Adapter
35
{
36
    /**
37
     * @var DbAdapter
38
     */
39
    protected $connection;
40
41
    /**
42
     * Roles table
43
     * @var string
44
     */
45
    protected $roles;
46
47
    /**
48
     * Resources table
49
     * @var string
50
     */
51
    protected $resources;
52
53
    /**
54
     * Resources Accesses table
55
     * @var string
56
     */
57
    protected $resourcesAccesses;
58
59
    /**
60
     * Access List table
61
     * @var string
62
     */
63
    protected $accessList;
64
65
    /**
66
     * Roles Inherits table
67
     * @var string
68
     */
69
    protected $rolesInherits;
70
71
    /**
72
     * Default action for no arguments is allow
73
     * @var int
74
     */
75
    protected $noArgumentsDefaultAction = Acl::ALLOW;
76
77
    /**
78
     * Company Object
79
     *
80
     * @var Companies
81
     */
82
    protected $company;
83
84
    /**
85
     * App Objc
86
     *
87
     * @var Apps
88
     */
89
    protected $app;
90
91
    /**
92
     * Class constructor.
93
     *
94
     * @param  array $options Adapter config
95
     * @throws Exception
96
     */
97 78
    public function __construct(array $options)
98
    {
99 78
        $this->connection = $options['db'];
100 78
    }
101
102
    /**
103
     * Set current user Company
104
     *
105
     * @param Companies $company
106
     * @return void
107
     */
108 2
    public function setCompany(Companies $company) : void
109
    {
110 2
        $this->company = $company;
111 2
    }
112
113
    /**
114
     * Set current user app
115
     *
116
     * @param Apps $app
117
     * @return void
118
     */
119 8
    public function setApp(Apps $app) : void
120
    {
121 8
        $this->app = $app;
122 8
    }
123
124
    /**
125
     * Get the current App
126
     *
127
     * @return void
128
     */
129 74
    public function getApp() : Apps
130
    {
131 74
        if (!is_object($this->app)) {
132 74
            $this->app = new Apps();
133 74
            $this->app->id = 1;
134 74
            $this->app->name = 'Canvas';
135
        }
136
137 74
        return $this->app;
138
    }
139
140
    /**
141
     * Get the current App
142
     *
143
     * @return void
144
     */
145 73
    public function getCompany() : Companies
146
    {
147 73
        if (!is_object($this->company)) {
148 73
            $this->company = new Companies();
149 73
            $this->company->id = 1;
150 73
            $this->company->name = 'Canvas';
151
        }
152
153 73
        return $this->company;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     *
159
     * Example:
160
     * <code>
161
     * $acl->addRole(new Phalcon\Acl\Role('administrator'), 'consultor');
162
     * $acl->addRole('administrator', 'consultor');
163
     * </code>
164
     *
165
     * @param  \Phalcon\Acl\Role|string $role
166
     * @param  int   $scope
167
     * @param  string                   $accessInherits
168
     * @return boolean
169
     * @throws \Phalcon\Acl\Exception
170
     */
171 3
    public function addRole($role, $scope = 0, $accessInherits = null) : bool
172
    {
173 3
        if (is_string($role)) {
174 1
            $role = $this->setAppByRole($role);
175
176 1
            $role = new Role($role, ucwords($role) . ' Role');
177
        }
178
179 3
        if (!$role instanceof RoleInterface) {
180
            throw new Exception('Role must be either an string or implement RoleInterface');
181
        }
182
183 3
        if (!RolesDB::exist($role)) {
184 1
            $rolesDB = new RolesDB();
185 1
            $rolesDB->name = $role->getName();
186 1
            $rolesDB->description = $role->getDescription() ?? $role->getName();
187 1
            $rolesDB->companies_id = $this->getCompany()->getId();
188 1
            $rolesDB->apps_id = $this->getApp()->getId();
189 1
            $rolesDB->scope = $scope;
190 1
            if (!$rolesDB->save()) {
191
                throw new ModelException((string)current($rolesDB->getMessages()));
192
            }
193
194 1
            $accessListDB = new AccessListDB();
195 1
            $accessListDB->roles_name = $role->getName();
196 1
            $accessListDB->roles_id = $rolesDB->getId();
197 1
            $accessListDB->resources_name = '*';
198 1
            $accessListDB->access_name = '*';
199 1
            $accessListDB->allowed = $this->_defaultAccess;
200 1
            $accessListDB->apps_id = $this->getApp()->getId();
201
202 1
            if (!$accessListDB->save()) {
203
                throw new ModelException((string)current($rolesDB->getMessages()));
204
            }
205
        }
206
207 3
        if ($accessInherits) {
208
            return $this->addInherit($role->getName(), $accessInherits);
209
        }
210
211 3
        return true;
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     *
217
     * @param  string  $roleName
218
     * @return boolean
219
     */
220
    public function isRole($roleName) : bool
221
    {
222
        return RolesDB::isRole($roleName);
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     *
228
     * @param  string  $resourceName
229
     * @return boolean
230
     */
231
    public function isResource($resourceName) : bool
232
    {
233
        return ResourcesDB::isResource($resourceName);
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     *
239
     * @param  string $roleName
240
     * @param  string $roleToInherit
241
     * @throws \Phalcon\Acl\Exception
242
     */
243
    public function addInherit($roleName, $roleToInherit) : bool
244
    {
245
        return RolesDB::addInherit($roleName, $roleToInherit);
246
    }
247
248
    /**
249
     * Given a resource with a dot CRM.Leads , it will set the app
250
     *
251
     * @param string $resource
252
     * @return void
253
     */
254 72
    protected function setAppByResource(string $resource) : string
255
    {
256
        //echeck if we have a dot , taht means we are sending the specific app to use
257 72
        if (strpos($resource, '.') !== false) {
258 5
            $appResource = explode('.', $resource);
259 5
            $resource = $appResource[1];
260 5
            $appName = $appResource[0];
261
262
            //look for the app and set it
263 5
            if ($app = Apps::getACLApp($appName)) {
264 5
                $this->setApp($app);
265
            }
266
        }
267
268 72
        return $resource;
269
    }
270
271
    /**
272
     * Given a resource with a dot CRM.Leads , it will set the app
273
     *
274
     * @param string $resource
275
     * @return void
276
     */
277 70
    protected function setAppByRole(string $role) : string
278
    {
279
        //echeck if we have a dot , taht means we are sending the specific app to use
280 70
        if (strpos($role, '.') !== false) {
281 1
            $appRole = explode('.', $role);
282 1
            $role = $appRole[1];
283 1
            $appName = $appRole[0];
284
285
            //look for the app and set it
286 1
            if ($app = Apps::getACLApp($appName)) {
287 1
                $this->setApp($app);
288
            }
289
        }
290
291 70
        return $role;
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     * Example:
297
     * <code>
298
     * //Add a resource to the the list allowing access to an action
299
     * $acl->addResource(new Phalcon\Acl\Resource('customers'), 'search');
300
     * $acl->addResource('customers', 'search');
301
     * //Add a resource  with an access list
302
     * $acl->addResource(new Phalcon\Acl\Resource('customers'), ['create', 'search']);
303
     * $acl->addResource('customers', ['create', 'search']);
304
     * $acl->addResource('App.customers', ['create', 'search']);
305
     * </code>
306
     *
307
     * @param  \Phalcon\Acl\Resource|string $resource
308
     * @param  array|string                 $accessList
309
     * @return boolean
310
     */
311 1
    public function addResource($resource, $accessList = null) : bool
312
    {
313 1
        if (!is_object($resource)) {
314
            //echeck if we have a dot , taht means we are sending the specific app to use
315 1
            $resource = $this->setAppByResource($resource);
316
317 1
            $resource = new Resource($resource);
318
        }
319
320 1
        if (!ResourcesDB::isResource($resource->getName())) {
321
            $resourceDB = new ResourcesDB();
322
            $resourceDB->name = $resource->getName();
323
            $resourceDB->description = $resource->getDescription();
324
            $resourceDB->apps_id = $this->getApp()->getId();
325
326
            if (!$resourceDB->save()) {
327
                throw new ModelException((string)current($resourceDB->getMessages()));
328
            }
329
        }
330
331 1
        if ($accessList) {
332 1
            return $this->addResourceAccess($resource->getName(), $accessList);
333
        }
334
335
        return true;
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     *
341
     * @param  string       $resourceName
342
     * @param  array|string $accessList
343
     * @return boolean
344
     * @throws \Phalcon\Acl\Exception
345
     */
346 1
    public function addResourceAccess($resourceName, $accessList) : bool
347
    {
348 1
        if (!ResourcesDB::isResource($resourceName)) {
349
            throw new Exception("Resource '{$resourceName}' does not exist in ACL");
350
        }
351
352 1
        $resource = ResourcesDB::getByName($resourceName);
353
354 1
        if (!is_array($accessList)) {
355
            $accessList = [$accessList];
356
        }
357
358 1
        foreach ($accessList as $accessName) {
359 1
            if (!ResourcesAccesses::exist($resource, $accessName)) {
360
                $resourceAccesses = new ResourcesAccesses();
361
                $resourceAccesses->beforeCreate(); //wtf?
362
                $resourceAccesses->resources_name = $resourceName;
363
                $resourceAccesses->access_name = $accessName;
364
                $resourceAccesses->apps_id = $this->getApp()->getId();
365
                $resourceAccesses->resources_id = $resource->getId();
366
367
                if (!$resourceAccesses->save()) {
368 1
                    throw new ModelException((string)current($resourceAccesses->getMessages()));
369
                }
370
            }
371
        }
372 1
        return true;
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     *
378
     * @return \Phalcon\Acl\Resource[]
379
     */
380
    public function getResources() : \Phalcon\Acl\ResourceInterface
381
    {
382
        $resources = [];
383
384
        foreach (ResourcesDB::find() as $row) {
385
            $resources[] = new Resource($row->name, $row->description);
386
        }
387
        return $resources;
388
    }
389
390
    /**
391
     * {@inheritdoc}
392
     *
393
     * @return RoleInterface[]
394
     */
395
    public function getRoles() : \Phalcon\Acl\RoleInterface
396
    {
397
        $roles = [];
398
399
        foreach (RolesDB::find() as $row) {
400
            $roles[] = new Role($row->name, $row->description);
401
        }
402
        return $roles;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     *
408
     * @param string       $resourceName
409
     * @param array|string $accessList
410
     */
411
    public function dropResourceAccess($resourceName, $accessList)
412
    {
413
        throw new BadMethodCallException('Not implemented yet.');
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     * You can use '*' as wildcard
419
     * Example:
420
     * <code>
421
     * //Allow access to guests to search on customers
422
     * $acl->allow('guests', 'customers', 'search');
423
     * //Allow access to guests to search or create on customers
424
     * $acl->allow('guests', 'customers', ['search', 'create']);
425
     * //Allow access to any role to browse on products
426
     * $acl->allow('*', 'products', 'browse');
427
     * //Allow access to any role to browse on any resource
428
     * $acl->allow('*', '*', 'browse');
429
     * </code>
430
     *
431
     * @param string       $roleName
432
     * @param string       $resourceName
433
     * @param array|string $access
434
     * @param mixed $func
435
     */
436 1
    public function allow($roleName, $resourceName, $access, $func = null)
437
    {
438 1
        return $this->allowOrDeny($roleName, $resourceName, $access, Acl::ALLOW);
439
    }
440
441
    /**
442
     * {@inheritdoc}
443
     * You can use '*' as wildcard
444
     * Example:
445
     * <code>
446
     * //Deny access to guests to search on customers
447
     * $acl->deny('guests', 'customers', 'search');
448
     * //Deny access to guests to search or create on customers
449
     * $acl->deny('guests', 'customers', ['search', 'create']);
450
     * //Deny access to any role to browse on products
451
     * $acl->deny('*', 'products', 'browse');
452
     * //Deny access to any role to browse on any resource
453
     * $acl->deny('*', '*', 'browse');
454
     * </code>
455
     *
456
     * @param  string       $roleName
457
     * @param  string       $resourceName
458
     * @param  array|string $access
459
     * @param  mixed $func
460
     * @return boolean
461
     */
462 3
    public function deny($roleName, $resourceName, $access, $func = null)
463
    {
464 3
        return $this->allowOrDeny($roleName, $resourceName, $access, Acl::DENY);
465
    }
466
467
    /**
468
     * {@inheritdoc}
469
     * Example:
470
     * <code>
471
     * //Does Andres have access to the customers resource to create?
472
     * $acl->isAllowed('Andres', 'Products', 'create');
473
     * //Do guests have access to any resource to edit?
474
     * $acl->isAllowed('guests', '*', 'edit');
475
     * </code>
476
     *
477
     * @param string $role
478
     * @param string $resource
479
     * @param string $access
480
     * @param array  $parameters
481
     * @return bool
482
     */
483 69
    public function isAllowed($role, $resource, $access, array $parameters = null) : bool
484
    {
485 69
        $role = $this->setAppByRole($role);
486
        //resoure always overwrites the role app?
487 69
        $resource = $this->setAppByResource($resource);
488 69
        $roleObj = RolesDB::getByName(($role));
489
490 69
        $sql = implode(' ', [
491 69
            'SELECT ' . $this->connection->escapeIdentifier('allowed') . ' FROM access_list AS a',
492
            // role_name in:
493 69
            'WHERE roles_id IN (',
494
                // given 'role'-parameter
495 69
            'SELECT roles_id ',
496
                // inherited role_names
497 69
            'UNION SELECT roles_inherit FROM roles_inherits WHERE roles_id = ?',
498
                // or 'any'
499 69
            "UNION SELECT '*'",
500 69
            ')',
501
            // resources_name should be given one or 'any'
502 69
            "AND resources_name IN (?, '*')",
503
            // access_name should be given one or 'any'
504
            //"AND access_name IN (?, '*')", you need to specify * , we are forcing to check always for permisions
505 69
            'AND access_name IN (?)',
506 69
            'AND apps_id = ? ',
507 69
            'AND roles_id = ? ',
508
            // order be the sum of bools for 'literals' before 'any'
509 69
            'ORDER BY ' . $this->connection->escapeIdentifier('allowed') . ' DESC',
510
            // get only one...
511 69
            'LIMIT 1'
512
        ]);
513
514
        // fetch one entry...
515 69
        $allowed = $this->connection->fetchOne($sql, Db::FETCH_NUM, [$roleObj->getId(), $resource, $access, $this->getApp()->getId(), $roleObj->getId()]);
516
517 69
        if (is_array($allowed)) {
518 11
            return (bool) $allowed[0];
519
        }
520
521
        /**
522
         * Return the default access action
523
         */
524 61
        return (bool) $this->_defaultAccess;
525
    }
526
527
    /**
528
     * Returns the default ACL access level for no arguments provided
529
     * in isAllowed action if there exists func for accessKey
530
     *
531
     * @return int
532
     */
533
    public function getNoArgumentsDefaultAction() : int
534
    {
535
        return $this->noArgumentsDefaultAction;
536
    }
537
538
    /**
539
     * Sets the default access level for no arguments provided
540
     * in isAllowed action if there exists func for accessKey
541
     *
542
     * @param int $defaultAccess Phalcon\Acl::ALLOW or Phalcon\Acl::DENY
543
     */
544
    public function setNoArgumentsDefaultAction($defaultAccess)
545
    {
546
        $this->noArgumentsDefaultAction = intval($defaultAccess);
547
    }
548
549
    /**
550
     * Inserts/Updates a permission in the access list
551
     *
552
     * @param  string  $roleName
553
     * @param  string  $resourceName
554
     * @param  string  $accessName
555
     * @param  integer $action
556
     * @return boolean
557
     * @throws \Phalcon\Acl\Exception
558
     */
559 4
    protected function insertOrUpdateAccess($roleName, $resourceName, $accessName, $action)
560
    {
561 4
        $resourceName = $this->setAppByResource($resourceName);
562
563
        /**
564
         * Check if the access is valid in the resource unless wildcard
565
         */
566 4
        if ($resourceName !== '*' && $accessName !== '*') {
567 4
            $resource = ResourcesDB::getByName($resourceName);
568
569 4
            if (!ResourcesAccesses::exist($resource, $accessName)) {
570
                throw new Exception(
571
                    "Access '{$accessName}' does not exist in resource '{$resourceName}' ({$resource->getId()}) in ACL"
572
                );
573
            }
574
        }
575
576
        /**
577
         * Update the access in access_list
578
         */
579
580 4
        $role = RolesDB::getByName($roleName);
581
582 4
        if (!AccessListDB::exist($role, $resourceName, $accessName)) {
583 2
            $accessListDB = new AccessListDB();
584 2
            $accessListDB->roles_id = $role->getId();
585 2
            $accessListDB->roles_name = $roleName;
586 2
            $accessListDB->resources_name = $resourceName;
587 2
            $accessListDB->access_name = $accessName;
588 2
            $accessListDB->allowed = $action;
0 ignored issues
show
Documentation Bug introduced by
The property $allowed was declared of type boolean, but $action is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
589 2
            $accessListDB->apps_id = $this->getApp()->getId();
590
591 2
            if (!$accessListDB->save()) {
592 2
                throw new ModelException((string)current($accessListDB->getMessages()));
593
            }
594
        } else {
595 2
            $accessListDB = accessListDB::getBy($role, $resourceName, $accessName);
596 2
            $accessListDB->allowed = $action;
597 2
            $accessListDB->update();
598
        }
599
600
        /**
601
         * Update the access '*' in access_list
602
         */
603 4
        if (!AccessListDB::exist($role, $resourceName, '*')) {
604 2
            $accessListDB = new AccessListDB();
605 2
            $accessListDB->roles_id = $role->getId();
606 2
            $accessListDB->roles_name = $roleName;
607 2
            $accessListDB->resources_name = $resourceName;
608 2
            $accessListDB->access_name = '*';
609 2
            $accessListDB->allowed = $this->_defaultAccess;
610 2
            $accessListDB->apps_id = $this->getApp()->getId();
611
612 2
            if (!$accessListDB->save()) {
613
                throw new ModelException((string)current($accessListDB->getMessages()));
614
            }
615
        }
616
617 4
        return true;
618
    }
619
620
    /**
621
     * Inserts/Updates a permission in the access list
622
     *
623
     * @param  string       $roleName
624
     * @param  string       $resourceName
625
     * @param  array|string $access
626
     * @param  integer      $action
627
     * @throws \Phalcon\Acl\Exception
628
     */
629 4
    protected function allowOrDeny($roleName, $resourceName, $access, $action)
630
    {
631 4
        if (!RolesDB::isRole($roleName)) {
632
            throw new Exception("Role '{$roleName}' does not exist in the list");
633
        }
634 4
        if (!is_array($access)) {
635 2
            $access = [$access];
636
        }
637 4
        foreach ($access as $accessName) {
638 4
            $this->insertOrUpdateAccess($roleName, $resourceName, $accessName, $action);
639
        }
640
641 4
        return true;
642
    }
643
}
644