Failed Conditions
Push — rbac ( 1ec5d5 )
by Simon
18:18 queued 05:15
created

SecurityManager::findResult()   D

Complexity

Conditions 9
Paths 18

Size

Total Lines 32
Code Lines 14

Duplication

Lines 10
Ratio 31.25 %

Code Coverage

Tests 9
CRAP Score 12.6885

Importance

Changes 0
Metric Value
cc 9
eloc 14
nc 18
nop 3
dl 10
loc 32
ccs 9
cts 14
cp 0.6429
crap 12.6885
rs 4.909
c 0
b 0
f 0
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Security;
10
11
use Waca\DataObjects\User;
12
use Waca\DataObjects\UserRole;
13
use Waca\IdentificationVerifier;
14
15
final class SecurityManager
16
{
17
    const ALLOWED = 1;
18
    const ERROR_NOT_IDENTIFIED = 2;
19
    const ERROR_DENIED = 3;
20
    /** @var IdentificationVerifier */
21
    private $identificationVerifier;
22
    /**
23
     * @var RoleConfiguration
24
     */
25
    private $roleConfiguration;
26
27
    /**
28
     * SecurityManager constructor.
29
     *
30
     * @param IdentificationVerifier $identificationVerifier
31
     * @param RoleConfiguration      $roleConfiguration
32
     */
33 2
    public function __construct(
34
        IdentificationVerifier $identificationVerifier,
35
        RoleConfiguration $roleConfiguration
36
    ) {
37 2
        $this->identificationVerifier = $identificationVerifier;
38 2
        $this->roleConfiguration = $roleConfiguration;
39 2
    }
40
41
    /**
42
     * Tests if a user is allowed to perform an action.
43
     *
44
     * This method should form a hard, deterministic security barrier, and only return true if it is absolutely sure
45
     * that a user should have access to something.
46
     *
47
     * @param string $page
48
     * @param string $route
49
     * @param User   $user
50
     *
51
     * @return int
52
     *
53
     * @category Security-Critical
54
     */
55 2
    public function allows($page, $route, User $user)
56
    {
57 2
        $this->getActiveRoles($user, $activeRoles, $inactiveRoles);
58
59 2
        $availableRights = $this->flattenRoles($activeRoles);
60 2
        $testResult = $this->findResult($availableRights, $page, $route);
61
62 2
        if ($testResult !== null) {
63
            // We got a firm result here, so just return it.
64 2
            return $testResult;
65
        }
66
67
        // No firm result yet, so continue testing the inactive roles so we can give a better error.
68
        $inactiveRights = $this->flattenRoles($inactiveRoles);
69
        $testResult = $this->findResult($inactiveRights, $page, $route);
70
71
        if ($testResult === self::ALLOWED) {
72
            // The user is allowed to access this, but their role is inactive.
73
            return self::ERROR_NOT_IDENTIFIED;
74
        }
75
76
        // Other options from the secondary test are denied and inconclusive, which at this point defaults to denied.
77
        return self::ERROR_DENIED;
78
    }
79
80
    /**
81
     * @param array  $pseudoRole The role (flattened) to check
82
     * @param string $page       The page class to check
83
     * @param string $route      The page route to check
84
     *
85
     * @return int|null
86
     */
87 2
    private function findResult($pseudoRole, $page, $route)
88
    {
89 2
        if (isset($pseudoRole[$page])) {
90
            // check for deny on catch-all route
91 2 View Code Duplication
            if (isset($pseudoRole[$page][RoleConfiguration::ALL])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92 2
                if ($pseudoRole[$page][RoleConfiguration::ALL] === RoleConfiguration::ACCESS_DENY) {
93 1
                    return self::ERROR_DENIED;
94
                }
95
            }
96
97
            // check normal route
98 1
            if (isset($pseudoRole[$page][$route])) {
99
                if ($pseudoRole[$page][$route] === RoleConfiguration::ACCESS_DENY) {
100
                    return self::ERROR_DENIED;
101
                }
102
103
                if ($pseudoRole[$page][$route] === RoleConfiguration::ACCESS_ALLOW) {
104
                    return self::ALLOWED;
105
                }
106
            }
107
108
            // check for allowed on catch-all route
109 1 View Code Duplication
            if (isset($pseudoRole[$page][RoleConfiguration::ALL])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110 1
                if ($pseudoRole[$page][RoleConfiguration::ALL] === RoleConfiguration::ACCESS_ALLOW) {
111 1
                    return self::ALLOWED;
112
                }
113
            }
114
        }
115
116
        // return indeterminate result
117
        return null;
118
    }
119
120
    /**
121
     * Takes an array of roles and flattens the values to a single set.
122
     *
123
     * @param array $activeRoles
124
     *
125
     * @return array
126
     */
127 2
    private function flattenRoles($activeRoles)
128
    {
129 2
        $result = array();
130
131 2
        $roleConfig = $this->roleConfiguration->getApplicableRoles($activeRoles);
132
133
        // Iterate over every page in every role
134 2
        foreach ($roleConfig as $role) {
135 2
            foreach ($role as $page => $pageRights) {
136
                // Create holder in result for this page
137 2
                if (!isset($result[$page])) {
138 2
                    $result[$page] = array();
139
                }
140
141 2
                foreach ($pageRights as $action => $permission) {
142
                    // Deny takes precedence, so if it's set, don't change it.
143 2
                    if (isset($result[$page][$action])) {
144
                        if ($result[$page][$action] === RoleConfiguration::ACCESS_DENY) {
145
                            continue;
146
                        }
147
                    }
148
149 2
                    if ($permission === RoleConfiguration::ACCESS_DEFAULT) {
150
                        // Configured to do precisely nothing.
151
                        continue;
152
                    }
153
154 2
                    $result[$page][$action] = $permission;
155
                }
156
            }
157
        }
158
159 2
        return $result;
160
    }
161
162
    /**
163
     * @param User  $user
164
     * @param array $activeRoles
165
     * @param array $inactiveRoles
166
     */
167 2
    public function getActiveRoles(User $user, &$activeRoles, &$inactiveRoles)
168
    {
169
        // Default to the community user here, because the main user is logged out
170 2
        $identified = false;
171 2
        $userRoles = array('public');
172
173
        // if we're not the community user, get our real rights.
174 2
        if (!$user->isCommunityUser()) {
175
            // Check the user's status - only active users are allowed the effects of roles
176
177
            $userRoles[] = 'loggedIn';
178
179
            if ($user->isActive()) {
180
                $ur = UserRole::getForUser($user->getId(), $user->getDatabase());
181
182
                // NOTE: public is still in this array.
183
                foreach ($ur as $r) {
184
                    $userRoles[] = $r->getRole();
185
                }
186
187
                $identified = $user->isIdentified($this->identificationVerifier);
188
            }
189
        }
190
191 2
        $activeRoles = array();
192 2
        $inactiveRoles = array();
193
194
        /** @var string $v */
195 2
        foreach ($userRoles as $v) {
196 2
            if ($this->roleConfiguration->roleNeedsIdentification($v)) {
197
                if ($identified) {
198
                    $activeRoles[] = $v;
199
                }
200
                else {
201
                    $inactiveRoles[] = $v;
202
                }
203
            }
204
            else {
205 2
                $activeRoles[] = $v;
206
            }
207
        }
208 2
    }
209
210
    public function getRoleConfiguration(){
211
        return $this->roleConfiguration;
212
    }
213
}
214