Passed
Push — master ( 8a607f...f6d1a8 )
by Simon
13:02 queued 08:56
created

RoleConfigurationBase::constructDenyOnlyRole()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 21
rs 9.2222
cc 6
nc 3
nop 1
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
abstract class RoleConfigurationBase
12
{
13
    const ACCESS_ALLOW = 1;
14
    const ACCESS_DENY = -1;
15
    const ACCESS_DEFAULT = 0;
16
    const MAIN = 'main';
17
    const ALL = '*';
18
19
    protected array $roleConfig;
20
    protected array $identificationExempt;
21
    private array $globallyDenied;
22
23
    protected function __construct(array $roleConfig, array $identificationExempt, array $globallyDenied)
24
    {
25
        $this->roleConfig = $roleConfig;
26
        $this->identificationExempt = $identificationExempt;
27
28
        $this->globallyDenied = self::constructDenyOnlyRole($globallyDenied);
29
    }
30
31
    /**
32
     * Takes an array of role names and flattens the values to a single
33
     * resultant role configuration.
34
     *
35
     * @param string[] $activeRoles
36
     * @category Security-Critical
37
     */
38
    public function getResultantRole(array $activeRoles): array
39
    {
40
        $result = array();
41
42
        $roleConfig = $this->getApplicableRoles($activeRoles);
43
44
        // Iterate over every page in every role
45
        foreach ($roleConfig as $role) {
46
            foreach ($role as $page => $pageRights) {
47
                // Create holder in result for this page
48
                if (!isset($result[$page])) {
49
                    $result[$page] = array();
50
                }
51
52
                foreach ($pageRights as $action => $permission) {
53
                    // Deny takes precedence, so if it's set, don't change it.
54
                    if (isset($result[$page][$action])) {
55
                        if ($result[$page][$action] === RoleConfigurationBase::ACCESS_DENY) {
56
                            continue;
57
                        }
58
                    }
59
60
                    if ($permission === RoleConfigurationBase::ACCESS_DEFAULT) {
61
                        // Configured to do precisely nothing.
62
                        continue;
63
                    }
64
65
                    $result[$page][$action] = $permission;
66
                }
67
            }
68
        }
69
70
        return $result;
71
    }
72
73
    /**
74
     * Returns a set of all roles which are available to be set.
75
     *
76
     * Hidden roles and implicit roles are excluded.
77
     */
78
    public function getAvailableRoles(): array
79
    {
80
        // remove the implicit roles
81
        $possible = array_diff(array_keys($this->roleConfig), array('public', 'loggedIn', 'user'));
82
83
        $actual = array();
84
85
        foreach ($possible as $role) {
86
            if (!isset($this->roleConfig[$role]['_hidden'])) {
87
                $actual[$role] = array(
88
                    'description' => $this->roleConfig[$role]['_description'],
89
                    'editableBy'  => $this->roleConfig[$role]['_editableBy'],
90
                    'globalOnly'  => isset($this->roleConfig[$role]['_globalOnly']) && $this->roleConfig[$role]['_globalOnly'],
91
                );
92
            }
93
        }
94
95
        return $actual;
96
    }
97
98
    /**
99
     * Returns a boolean for whether the provided role requires identification
100
     * before being used by a user.
101
     *
102
     * @category Security-Critical
103
     */
104
    public function roleNeedsIdentification(string $role): bool
105
    {
106
        if (in_array($role, $this->identificationExempt)) {
107
            return false;
108
        }
109
110
        return true;
111
    }
112
113
    /**
114
     * Takes an array of role names, and returns all the relevant roles for that
115
     * set, including any child roles found recursively.
116
     *
117
     * @param array $roles The names of roles to start searching with
118
     */
119
    private function getApplicableRoles(array $roles): array
120
    {
121
        $available = [];
122
123
        $available['_GLOBAL_DENY'] = $this->globallyDenied;
124
125
        foreach ($roles as $role) {
126
            if (!isset($this->roleConfig[$role])) {
127
                // wat
128
                continue;
129
            }
130
131
            $available[$role] = $this->roleConfig[$role];
132
133
            if (isset($available[$role]['_childRoles'])) {
134
                $childRoles = $this->getApplicableRoles($available[$role]['_childRoles']);
135
                $available = array_merge($available, $childRoles);
136
137
                unset($available[$role]['_childRoles']);
138
            }
139
140
            foreach (array('_hidden', '_editableBy', '_description', '_globalOnly') as $item) {
141
                if (isset($available[$role][$item])) {
142
                    unset($available[$role][$item]);
143
                }
144
            }
145
        }
146
147
        return $available;
148
    }
149
150
    /**
151
     * Takes a role definition and filters it to just the deny actions.
152
     *
153
     * @param array $globallyDenied
154
     * @return array
155
     */
156
    public static function constructDenyOnlyRole(array $globallyDenied): array
157
    {
158
        $result = [];
159
160
        foreach ($globallyDenied as $page => $pageRights) {
161
            if (in_array($page, ['_hidden', '_editableBy', '_description', '_globalOnly', '_childRoles'])) {
162
                continue;
163
            }
164
165
            foreach ($pageRights as $action => $permission) {
166
                if ($permission === RoleConfigurationBase::ACCESS_DENY) {
167
                    if (!array_key_exists($page, $result)) {
168
                        $result[$page] = [];
169
                    }
170
171
                    $result[$page][$action] = RoleConfigurationBase::ACCESS_DENY;
172
                }
173
            }
174
        }
175
176
        return $result;
177
    }
178
}