Completed
Push — master ( 2a5b9e...5fec60 )
by rugk
02:33
created

ThreemaGateway_Handler_Permissions::isLimited()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * Manages permissions of all actions.
4
 *
5
 * It is designed as a singleton, as there should only be one user whose
6
 * permissions are checked and this is the current user.
7
 * Thus it is not allowed to check the permissions of third-party users as
8
 * it does not make any sense and only introduces potential vulnerabilities as
9
 * it might allow to take over permissions from other users.
10
 *
11
 * @package ThreemaGateway
12
 * @author rugk
13
 * @copyright Copyright (c) 2015-2016 rugk
14
 * @license MIT
15
 */
16
17
/**
18
 * This manages the permissions of all actions.
19
 */
20
class ThreemaGateway_Handler_Permissions
21
{
22
    /**
23
     * @var Singleton
24
     */
25
    protected static $instance = null;
26
27
    /**
28
     * @var string identifier of the permission group of this addon
29
     */
30
    const PERMISSION_GROUP = 'threemagw';
31
32
    /**
33
     * @var array all possible permissions of this add-on
34
     */
35
    const PERMISSION_LIST = [
36
        ['id' => 'use'],
37
        ['id' => 'send'],
38
        ['id' => 'receive'],
39
        ['id' => 'fetch'],
40
        ['id' => 'lookup'],
41
        ['id' => 'tfa'],
42
        // 2FA fast mode
43
        ['id' => 'blockedNotification'],
44
        ['id' => 'blockTfaMode'],
45
        ['id' => 'blockUser'],
46
        ['id' => 'blockIp'],
47
        // admin
48
        [
49
            'id' => 'credits',
50
            'adminOnly' => true,
51
            'adminName' => 'showcredits'
52
        ]
53
    ];
54
55
    /**
56
     * @var array|null User who is using the Threema Gateway
57
     */
58
    protected $user = null;
59
60
    /**
61
     * @var array Permissions cache
62
     */
63
    protected $permissions;
64
65
    /**
66
     * Private constructor so nobody else can instance it.
67
     * Use {@link getInstance()} instead.
68
     *
69
     * @return true
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
70
     */
71
    protected function __construct()
72
    {
73
        // do nothing
74
    }
75
76
    /**
77
     * Prevent cloning for Singleton.
78
     */
79
    protected function __clone()
80
    {
81
        // I smash clones!
82
    }
83
84
    /**
85
     * SDK startup as a Singleton.
86
     *
87
     * @throws XenForo_Exception
88
     * @return ThreemaGateway_Handler_Permissions
89
     */
90
    public static function getInstance()
91
    {
92
        if (!isset(self::$instance)) {
93
            self::$instance = new self;
0 ignored issues
show
Documentation Bug introduced by
It seems like new self() of type object<ThreemaGateway_Handler_Permissions> is incompatible with the declared type object<Singleton> of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94
        }
95
96
        return self::$instance;
97
    }
98
99
    /**
100
     * Sets/Changes the user id of the user using the Threema Gateway. This
101
     * also forces a reload of the permission cache. (See {@link hasPermission()}).
102
     *
103
     * Returns true when value was changed. If false is returned the user is
104
     * already the visitor or the user id is already set or and the value was
105
     * not changed.
106
     *
107
     * @param  array|null $newUser (optional) User array
108
     * @return bool
109
     */
110
    public function setUserId($newUser = null)
111
    {
112
        // get user ids (or null)
113
        if ($this->user === null) {
114
            /** @var int|null $oldUserId User id of user (from class) */
115
            $oldUserId = null;
116
        } else {
117
            /** @var int|null $oldUserId User id of user (from class) */
118
            $oldUserId = $this->user['user_id'];
119
        }
120
        if ($newUser === null) {
121
            /** @var int|null $newUserId User id of new user (from param) */
122
            $newUserId = null;
123
        } else {
124
            /** @var int|null $newUserId User id of new user (from param) */
125
            $newUserId = $newUser['user_id'];
126
        }
127
128
        // check whether visitor is user
129
        /**
130
         * @var bool $userIsAlreadyVisitor Whether the new user is already the
131
         *           default user and the old user is the
132
         *           default user too.
133
         */
134
        $userIsAlreadyVisitor = $this->userIsDefault($newUserId) && $this->userIsDefault($oldUserId);
135
        // prevent unnecessary changes
136
        if ($oldUserId == $newUserId || $userIsAlreadyVisitor) {
137
            return false;
138
        }
139
140
        //change user Id
141
        $this->user = $newUser;
142
        $this->renewCache($newUserId);
143
        return true;
144
    }
145
146
    /**
147
     * Checks whether the user has the permission to do something.
148
     *
149
     * This uses the user e.g. set by {@link setUserId()}. If no user is set it
150
     * uses the current visitor/user.
151
     * The currently avaliable actions are: use, send, receive, fetch, lookup,
152
     * tfa and credits
153
     * If you do not specify an action an array of all possible actions is
154
     * returned.
155
     * Note that "credits" is an admin permission and is therefore only avaliable
156
     * to admins.
157
     *
158
     * @param  string     $action  (optional) The action you want to do
159
     * @param  bool       $noCache (optional) Forces the cache to reload
160
     * @return bool|array
161
     */
162
    public function hasPermission($action = null, $noCache = false)
163
    {
164
        /** @var int|null $userId User id of user */
165
        $userId = $this->userGetId(false);
166
167
        // check permission cache
168
        if ($noCache || !is_array($this->permissions)) {
169
            // (need to) renew cache
170
            $this->renewCache($userId);
171
        }
172
173
        // return permission
174
        if ($action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
175
            if (!array_key_exists($action, $this->permissions)) {
176
                // invalid action
177
                return false;
178
            }
179
            return $this->permissions[$action];
180
        }
181
182
        return $this->permissions;
183
    }
184
185
    /**
186
     * Logs an action for checking it via {@link isLimited()} later.
187
     *
188
     * This uses the user e.g. set by {@link setUserId()}. If no user is set it
189
     * uses the current visitor/user.
190
     *
191
     * @param  string     $action  The action the user has done
192
     * @return bool|array
193
     */
194
    public function logAction($action)
195
    {
196
        /** @var int|null $userId User id of user */
197
        $userId = $this->userGetId();
198
199
        return (new ThreemaGateway_Model_ActionThrottle)->logAction($userId, $action);
200
    }
201
202
    /**
203
     * Checks whether the user has the permission to do something.
204
     *
205
     * This uses the user e.g. set by {@link setUserId()}. If no user is set it
206
     * uses the current visitor/user.
207
     *
208
     * @param  string     $action  The action you want to do
209
     * @return bool|array
210
     */
211
    public function isLimited($action)
212
    {
213
        /** @var int|null $userId User id of user */
214
        $userId = $this->userGetId();
215
216
        return (new ThreemaGateway_Model_ActionThrottle)->isLimited($userId, $action);
217
    }
218
219
    /**
220
     * Reload the permission cache.
221
     *
222
     * @param string $userId the ID of the user
223
     */
224
    protected function renewCache($userId)
225
    {
226
        /** @var array $permissions Temporary variable for permissions */
227
        $permissions = [];
228
229
        if ($this->userIsDefault($userId)) {
230
            /** @var XenForo_Visitor $visitor */
231
            $visitor = XenForo_Visitor::getInstance();
232
233
            //normal visitor, use simple methods
234
            foreach (self::PERMISSION_LIST as $testPerm) {
235
                if (!empty($testPerm['adminOnly'])) {
236
                    $permissions[$testPerm['id']] = $visitor->hasAdminPermission(self::PERMISSION_GROUP . '_' . $testPerm['adminName']);
237
                } else {
238
                    $permissions[$testPerm['id']] = $visitor->hasPermission(self::PERMISSION_GROUP, $testPerm['id']);
239
                }
240
            }
241
        } else {
242
            // fetch permissions (from DB) if needed
243
            if (!array_key_exists('permissions', $this->user)) {
244
                if (!array_key_exists('global_permission_cache', $this->user) || !$this->user['global_permission_cache']) {
245
                    // used code by XenForo_Visitor::setup()
246
                    // get permissions from cache
247
                    $perms = XenForo_Model::create('XenForo_Model_Permission')->rebuildPermissionCombinationById(
248
                        $this->user['permission_combination_id']
249
                    );
250
                    $this->user['permissions'] = $perms ? $perms : [];
251
                } else {
252
                    $this->user['permissions'] = XenForo_Permission::unserializePermissions($this->user['global_permission_cache']);
253
                }
254
            }
255
256
            //get permissions
257
            foreach (self::PERMISSION_LIST as $testPerm) {
258
                if (!empty($testPerm['adminOnly'])) {
259
                    // Getting admin permission would be extensive and admins should
260
                    // also only have special permissions if they are really logged in.
261
                    // Therefore all admin permissions are set to false
262
                    $permissions[$testPerm['id']] = false;
263
                } else {
264
                    $permissions[$testPerm['id']] = XenForo_Permission::hasPermission($this->user['permissions'], self::PERMISSION_GROUP, $testPerm['id']);
265
                }
266
            }
267
        }
268
269
        $this->permissions = $permissions;
270
    }
271
272
    /**
273
     * Returns the current user ID.
274
     *
275
     * @param  bool $visitorFallback Optional - If set to false, this does not
276
     *                                          fallback to the current user.
277
     *
278
     * @return int|null
279
     */
280
    protected function userGetId($visitorFallback = true)
281
    {
282
        if ($this->user !== null) {
283
            return $this->user['user_id'];
284
        }
285
        if ($visitorFallback) {
286
            return $this->getVisitorUserId();
287
        }
288
289
        return null;
290
    }
291
292
    /**
293
     * Checks whether a user is the default user/the current "visitor".
294
     *
295
     * @param  int|null $userId A user id or null
296
     * @return bool
297
     */
298
    protected function userIsDefault($userId)
299
    {
300
        return $userId === $this->getVisitorUserId() || $userId === null;
301
    }
302
303
    /**
304
     * Returns the user ID of the current visitor.
305
     *
306
     * @return int
307
     */
308
    protected function getVisitorUserId()
309
    {
310
        /** @var XenForo_Visitor $visitor */
311
        $visitor = XenForo_Visitor::getInstance();
312
        return $visitor->getUserId();
313
    }
314
}
315