Passed
Push — master ( 5c9096...cd0441 )
by Gabor
03:00
created

AclMiddleware::checkPolicy()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
rs 6.7272
cc 7
eloc 12
nc 8
nop 3
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @copyright 2012 - 2016 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
13
namespace WebHemi\Middleware\Security;
14
15
use Exception;
16
use WebHemi\Adapter\Auth\AuthAdapterInterface;
17
use WebHemi\Adapter\Http\ResponseInterface;
18
use WebHemi\Adapter\Http\ServerRequestInterface;
19
use WebHemi\Application\EnvironmentManager;
20
use WebHemi\Auth\Result;
21
use WebHemi\Data\Coupler\UserToGroupCoupler;
22
use WebHemi\Data\Coupler\UserToPolicyCoupler;
23
use WebHemi\Data\Coupler\UserGroupToPolicyCoupler;
24
use WebHemi\Data\Entity\ApplicationEntity;
25
use WebHemi\Data\Entity\AccessManagement\PolicyEntity;
26
use WebHemi\Data\Entity\AccessManagement\ResourceEntity;
27
use WebHemi\Data\Entity\User\UserEntity;
28
use WebHemi\Data\Storage\AccessManagement\ResourceStorage;
29
use WebHemi\Data\Storage\ApplicationStorage;
30
use WebHemi\Middleware\MiddlewareInterface;
31
use WebHemi\Middleware\Action;
32
33
/**
34
 * Class AclMiddleware.
35
 */
36
class AclMiddleware implements MiddlewareInterface
37
{
38
    /** @var EnvironmentManager */
39
    private $environmentManager;
40
    /** @var AuthAdapterInterface */
41
    private $authAdapter;
42
    /** @var UserToPolicyCoupler */
43
    private $userToPolicyCoupler;
44
    /** @var UserToGroupCoupler */
45
    private $userToGroupCoupler;
46
    /** @var UserGroupToPolicyCoupler */
47
    private $userGroupToPolicyCoupler;
48
    private $applicationStorage;
49
    private $resourceStorage;
50
    /** @var array */
51
    private $middlewareWhiteList = [
52
        Action\Auth\LoginAction::class,
53
        Action\Auth\LogoutAction::class,
54
    ];
55
56
    /**
57
     * AclMiddleware constructor.
58
     * @param AuthAdapterInterface     $authAdapter
59
     * @param EnvironmentManager       $environmentManager
60
     * @param UserToPolicyCoupler      $userToPolicyCoupler
61
     * @param UserToGroupCoupler       $userToGroupCoupler
62
     * @param UserGroupToPolicyCoupler $userGroupToPolicyCoupler
63
     * @param ApplicationStorage       $applicationStorage
64
     * @param ResourceStorage          $resourceStorage
65
     */
66
    public function __construct(
67
        AuthAdapterInterface $authAdapter,
68
        EnvironmentManager $environmentManager,
69
        UserToPolicyCoupler $userToPolicyCoupler,
70
        UserToGroupCoupler $userToGroupCoupler,
71
        UserGroupToPolicyCoupler $userGroupToPolicyCoupler,
72
        ApplicationStorage $applicationStorage,
73
        ResourceStorage $resourceStorage
74
    ) {
75
        $this->authAdapter = $authAdapter;
76
        $this->environmentManager = $environmentManager;
77
        $this->userToPolicyCoupler = $userToPolicyCoupler;
78
        $this->userToGroupCoupler = $userToGroupCoupler;
79
        $this->userGroupToPolicyCoupler = $userGroupToPolicyCoupler;
80
        $this->applicationStorage = $applicationStorage;
81
        $this->resourceStorage = $resourceStorage;
82
    }
83
84
    /**
85
     * A middleware is a callable. It can do whatever is appropriate with the Request and Response objects.
86
     * The only hard requirement is that a middleware MUST return an instance of \Psr\Http\Message\ResponseInterface.
87
     * Each middleware SHOULD invoke the next middleware and pass it Request and Response objects as arguments.
88
     *
89
     * @param ServerRequestInterface $request
90
     * @param ResponseInterface      $response
91
     * @throws Exception
92
     * @return ResponseInterface
93
     */
94
    public function __invoke(ServerRequestInterface &$request, ResponseInterface $response)
95
    {
96
        $actionMiddleware = $request->getAttribute(ServerRequestInterface::REQUEST_ATTR_RESOLVED_ACTION_CLASS);
97
        $identity = false;
98
99
        if (in_array($actionMiddleware, $this->middlewareWhiteList)) {
100
            return $response;
101
        }
102
103
        if ($this->authAdapter->hasIdentity()) {
104
            $identity = $this->authAdapter->getIdentity();
105
        }
106
107
        if ($identity instanceof UserEntity) {
108
            $selectedApplication = $this->environmentManager->getSelectedApplication();
109
            /** @var ApplicationEntity $applicationEntity */
110
            $applicationEntity = $this->applicationStorage->getApplicationByName($selectedApplication);
111
            /** @var ResourceEntity $resourceEntity */
112
            $resourceEntity = $this->resourceStorage->getResourceByName($actionMiddleware);
113
114
            // First we check the group policies
115
            /** @var array<UserGroupEntity> $userGroups */
116
            $userGroups = $this->userToGroupCoupler->getEntityDependencies($identity);
117
            /** @var array<PolicyEntity> $userGroupPolicies */
118
            $userGroupPolicies = [];
119
            foreach ($userGroups as $userGroupEntity) {
120
                $groupPolicies = $this->userGroupToPolicyCoupler->getEntityDependencies($userGroupEntity);
121
                $userGroupPolicies = array_merge($userGroupPolicies, $groupPolicies);
122
            }
123
            $hasAccess = $this->checkPolicies($userGroupPolicies, $applicationEntity, $resourceEntity);
0 ignored issues
show
Documentation introduced by
$userGroupPolicies is of type array<integer,object<Web...y\DataEntityInterface>>, but the function expects a array<integer,object<Web...nagement\PolicyEntity>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
125
            // Then we check the personal policies
126
            /** @var array<PolicyEntity> $userPolicies */
127
            $userPolicies = $this->userToPolicyCoupler->getEntityDependencies($identity);
128
            $hasAccess = $hasAccess && $this->checkPolicies($userPolicies, $applicationEntity, $resourceEntity);
129
130
            if (!$hasAccess) {
131
                $response = $response->withStatus(ResponseInterface::STATUS_FORBIDDEN, 'Forbidden');
132
            }
133
        } else {
134
            $appUri = rtrim($this->environmentManager->getSelectedApplicationUri(), '/');
135
            $response = $response->withStatus(ResponseInterface::STATUS_REDIRECT, 'Found')
136
                ->withHeader('Location', $appUri.'/auth/login');
137
        }
138
139
        return $response;
140
    }
141
142
    /**
143
     * Checks policies for application and resource
144
     *
145
     * @param array<PolicyEntity>      $policies
146
     * @param ApplicationEntity|null   $applicationEntity
147
     * @param ResourceEntity|null      $resourceEntity
148
     * @return bool
149
     */
150
    private function checkPolicies(
151
        array $policies,
152
        ApplicationEntity $applicationEntity = null,
153
        ResourceEntity $resourceEntity = null
154
    ) {
155
        // We assume the best case: the user has access
156
        $hasAccess = true;
157
158
        /** @var PolicyEntity $policyEntity */
159
        foreach ($policies as $policyEntity) {
160
            $hasAccess = $hasAccess && $this->checkPolicy($policyEntity, $applicationEntity, $resourceEntity);
161
        }
162
163
        return $hasAccess;
164
    }
165
166
    /**
167
     * @param PolicyEntity           $policyEntity
168
     * @param ApplicationEntity|null $applicationEntity
169
     * @param ResourceEntity|null    $resourceEntity
170
     * @return bool
171
     */
172
    private function checkPolicy(
173
        PolicyEntity $policyEntity,
174
        ApplicationEntity $applicationEntity = null,
175
        ResourceEntity $resourceEntity = null
176
    ) {
177
        $applicationId = $applicationEntity ? $applicationEntity->getApplicationId() : null;
178
        $resourceId = $resourceEntity ? $resourceEntity->getResourceId() : null;
179
        $policyApplicationId = $policyEntity->getApplicationId();
180
        $policyResourceId = $policyEntity->getResourceId();
181
182
        // The user has access when:
183
        // - user has a policy that connected to the current application OR any application AND
184
        // - user has a policy that connected to the current resource OR any resource
185
        if (($policyApplicationId == null || $policyApplicationId == $applicationId)
186
            && ($policyResourceId == null || $policyResourceId == $resourceId)
187
        ) {
188
            return $policyEntity->getAllowed();
189
        }
190
191
        // At this point we know that the current policy doesn't belong to this application or resource, so no need
192
        // to block the user.
193
        return true;
194
    }
195
}
196