Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/Remote/Api.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace dokuwiki\Remote;
4
5
use dokuwiki\Extension\Event;
6
use dokuwiki\Extension\RemotePlugin;
7
8
/**
9
 * This class provides information about remote access to the wiki.
10
 *
11
 * == Types of methods ==
12
 * There are two types of remote methods. The first is the core methods.
13
 * These are always available and provided by dokuwiki.
14
 * The other is plugin methods. These are provided by remote plugins.
15
 *
16
 * == Information structure ==
17
 * The information about methods will be given in an array with the following structure:
18
 * array(
19
 *     'method.remoteName' => array(
20
 *          'args' => array(
21
 *              'type eg. string|int|...|date|file',
22
 *          )
23
 *          'name' => 'method name in class',
24
 *          'return' => 'type',
25
 *          'public' => 1/0 - method bypass default group check (used by login)
26
 *          ['doc' = 'method documentation'],
27
 *     )
28
 * )
29
 *
30
 * plugin names are formed the following:
31
 *   core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
32
 *   i.e.: dokuwiki.version or wiki.getPage
33
 *
34
 * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
35
 * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
36
 */
37
class Api
38
{
39
40
    /**
41
     * @var ApiCore
42
     */
43
    private $coreMethods = null;
44
45
    /**
46
     * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
47
     * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods}
48
     */
49
    private $pluginMethods = null;
50
51
    /**
52
     * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event.
53
     * The data inside is 'custom.call.something' => array('plugin name', 'remote method name')
54
     *
55
     * The remote method name is the same as in the remote name returned by _getMethods().
56
     */
57
    private $pluginCustomCalls = null;
58
59
    private $dateTransformation;
60
    private $fileTransformation;
61
62
    /**
63
     * constructor
64
     */
65
    public function __construct()
66
    {
67
        $this->dateTransformation = array($this, 'dummyTransformation');
68
        $this->fileTransformation = array($this, 'dummyTransformation');
69
    }
70
71
    /**
72
     * Get all available methods with remote access.
73
     *
74
     * @return array with information to all available methods
75
     * @throws RemoteException
76
     */
77
    public function getMethods()
78
    {
79
        return array_merge($this->getCoreMethods(), $this->getPluginMethods());
80
    }
81
82
    /**
83
     * Call a method via remote api.
84
     *
85
     * @param string $method name of the method to call.
86
     * @param array $args arguments to pass to the given method
87
     * @return mixed result of method call, must be a primitive type.
88
     * @throws RemoteException
89
     */
90
    public function call($method, $args = array())
91
    {
92
        if ($args === null) {
93
            $args = array();
94
        }
95
        // Ensure we have at least one '.' in $method
96
        list($type, $pluginName, /* $call */) = explode('.', $method . '.', 3);
97
        if ($type === 'plugin') {
98
            return $this->callPlugin($pluginName, $method, $args);
99
        }
100
        if ($this->coreMethodExist($method)) {
101
            return $this->callCoreMethod($method, $args);
102
        }
103
        return $this->callCustomCallPlugin($method, $args);
104
    }
105
106
    /**
107
     * Check existance of core methods
108
     *
109
     * @param string $name name of the method
110
     * @return bool if method exists
111
     */
112
    private function coreMethodExist($name)
113
    {
114
        $coreMethods = $this->getCoreMethods();
115
        return array_key_exists($name, $coreMethods);
116
    }
117
118
    /**
119
     * Try to call custom methods provided by plugins
120
     *
121
     * @param string $method name of method
122
     * @param array $args
123
     * @return mixed
124
     * @throws RemoteException if method not exists
125
     */
126
    private function callCustomCallPlugin($method, $args)
127
    {
128
        $customCalls = $this->getCustomCallPlugins();
129
        if (!array_key_exists($method, $customCalls)) {
130
            throw new RemoteException('Method does not exist', -32603);
131
        }
132
        list($plugin, $method) = $customCalls[$method];
133
        $fullMethod = "plugin.$plugin.$method";
134
        return $this->callPlugin($plugin, $fullMethod, $args);
135
    }
136
137
    /**
138
     * Returns plugin calls that are registered via RPC_CALL_ADD action
139
     *
140
     * @return array with pairs of custom plugin calls
141
     * @triggers RPC_CALL_ADD
142
     */
143
    private function getCustomCallPlugins()
144
    {
145
        if ($this->pluginCustomCalls === null) {
146
            $data = array();
147
            Event::createAndTrigger('RPC_CALL_ADD', $data);
148
            $this->pluginCustomCalls = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $pluginCustomCalls.

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...
149
        }
150
        return $this->pluginCustomCalls;
151
    }
152
153
    /**
154
     * Call a plugin method
155
     *
156
     * @param string $pluginName
157
     * @param string $method method name
158
     * @param array $args
159
     * @return mixed return of custom method
160
     * @throws RemoteException
161
     */
162
    private function callPlugin($pluginName, $method, $args)
163
    {
164
        $plugin = plugin_load('remote', $pluginName);
165
        $methods = $this->getPluginMethods();
166
        if (!$plugin) {
167
            throw new RemoteException('Method does not exist', -32603);
168
        }
169
        $this->checkAccess($methods[$method]);
170
        $name = $this->getMethodName($methods, $method);
171
        try {
172
            set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1
173
            return call_user_func_array(array($plugin, $name), $args);
174
        } catch (\ArgumentCountError $th) {
0 ignored issues
show
The class ArgumentCountError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
175
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
176
        } finally {
177
            restore_error_handler();
178
        }
179
    }
180
181
    /**
182
     * Call a core method
183
     *
184
     * @param string $method name of method
185
     * @param array $args
186
     * @return mixed
187
     * @throws RemoteException if method not exist
188
     */
189
    private function callCoreMethod($method, $args)
190
    {
191
        $coreMethods = $this->getCoreMethods();
192
        $this->checkAccess($coreMethods[$method]);
193
        if (!isset($coreMethods[$method])) {
194
            throw new RemoteException('Method does not exist', -32603);
195
        }
196
        $this->checkArgumentLength($coreMethods[$method], $args);
197
        try {
198
            set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1
199
            return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args);
200
        } catch (\ArgumentCountError $th) {
0 ignored issues
show
The class ArgumentCountError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
201
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
202
        } finally {
203
            restore_error_handler();
204
        }
205
    }
206
207
    /**
208
     * Check if access should be checked
209
     *
210
     * @param array $methodMeta data about the method
211
     * @throws AccessDeniedException
212
     */
213
    private function checkAccess($methodMeta)
214
    {
215
        if (!isset($methodMeta['public'])) {
216
            $this->forceAccess();
217
        } else {
218
            if ($methodMeta['public'] == '0') {
219
                $this->forceAccess();
220
            }
221
        }
222
    }
223
224
    /**
225
     * Check the number of parameters
226
     *
227
     * @param array $methodMeta data about the method
228
     * @param array $args
229
     * @throws RemoteException if wrong parameter count
230
     */
231
    private function checkArgumentLength($methodMeta, $args)
232
    {
233
        if (count($methodMeta['args']) < count($args)) {
234
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
235
        }
236
    }
237
238
    /**
239
     * Determine the name of the real method
240
     *
241
     * @param array $methodMeta list of data of the methods
242
     * @param string $method name of method
243
     * @return string
244
     */
245
    private function getMethodName($methodMeta, $method)
246
    {
247
        if (isset($methodMeta[$method]['name'])) {
248
            return $methodMeta[$method]['name'];
249
        }
250
        $method = explode('.', $method);
251
        return $method[count($method) - 1];
252
    }
253
254
    /**
255
     * Perform access check for current user
256
     *
257
     * @return bool true if the current user has access to remote api.
258
     * @throws AccessDeniedException If remote access disabled
259
     */
260
    public function hasAccess()
261
    {
262
        global $conf;
263
        global $USERINFO;
264
        /** @var \dokuwiki\Input\Input $INPUT */
265
        global $INPUT;
266
267
        if (!$conf['remote']) {
268
            throw new AccessDeniedException('server error. RPC server not enabled.', -32604);
269
        }
270
        if (trim($conf['remoteuser']) == '!!not set!!') {
271
            return false;
272
        }
273
        if (!$conf['useacl']) {
274
            return true;
275
        }
276
        if (trim($conf['remoteuser']) == '') {
277
            return true;
278
        }
279
280
        return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']);
281
    }
282
283
    /**
284
     * Requests access
285
     *
286
     * @return void
287
     * @throws AccessDeniedException On denied access.
288
     */
289
    public function forceAccess()
290
    {
291
        if (!$this->hasAccess()) {
292
            throw new AccessDeniedException('server error. not authorized to call method', -32604);
293
        }
294
    }
295
296
    /**
297
     * Collects all the methods of the enabled Remote Plugins
298
     *
299
     * @return array all plugin methods.
300
     * @throws RemoteException if not implemented
301
     */
302
    public function getPluginMethods()
303
    {
304
        if ($this->pluginMethods === null) {
305
            $this->pluginMethods = array();
306
            $plugins = plugin_list('remote');
307
308
            foreach ($plugins as $pluginName) {
309
                /** @var RemotePlugin $plugin */
310
                $plugin = plugin_load('remote', $pluginName);
311
                if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) {
312
                    throw new RemoteException(
313
                        "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin"
314
                    );
315
                }
316
317
                try {
318
                    $methods = $plugin->_getMethods();
319
                } catch (\ReflectionException $e) {
320
                    throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e);
321
                }
322
323
                foreach ($methods as $method => $meta) {
324
                    $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
325
                }
326
            }
327
        }
328
        return $this->pluginMethods;
329
    }
330
331
    /**
332
     * Collects all the core methods
333
     *
334
     * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
335
     *                         instance. (for mocking)
336
     * @return array all core methods.
337
     */
338
    public function getCoreMethods($apiCore = null)
339
    {
340
        if ($this->coreMethods === null) {
341
            if ($apiCore === null) {
342
                $this->coreMethods = new ApiCore($this);
343
            } else {
344
                $this->coreMethods = $apiCore;
345
            }
346
        }
347
        return $this->coreMethods->__getRemoteInfo();
348
    }
349
350
    /**
351
     * Transform file to xml
352
     *
353
     * @param mixed $data
354
     * @return mixed
355
     */
356
    public function toFile($data)
357
    {
358
        return call_user_func($this->fileTransformation, $data);
359
    }
360
361
    /**
362
     * Transform date to xml
363
     *
364
     * @param mixed $data
365
     * @return mixed
366
     */
367
    public function toDate($data)
368
    {
369
        return call_user_func($this->dateTransformation, $data);
370
    }
371
372
    /**
373
     * A simple transformation
374
     *
375
     * @param mixed $data
376
     * @return mixed
377
     */
378
    public function dummyTransformation($data)
379
    {
380
        return $data;
381
    }
382
383
    /**
384
     * Set the transformer function
385
     *
386
     * @param callback $dateTransformation
387
     */
388
    public function setDateTransformation($dateTransformation)
389
    {
390
        $this->dateTransformation = $dateTransformation;
391
    }
392
393
    /**
394
     * Set the transformer function
395
     *
396
     * @param callback $fileTransformation
397
     */
398
    public function setFileTransformation($fileTransformation)
399
    {
400
        $this->fileTransformation = $fileTransformation;
401
    }
402
403
    /**
404
     * The error handler that catches argument-related warnings
405
     */
406
    public function argumentWarningHandler($errno, $errstr)
407
    {
408
        if (substr($errstr, 0, 17) == 'Missing argument ') {
409
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
410
        }
411
    }
412
}
413