1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
<?php
namespace dokuwiki\Remote;
use dokuwiki\Extension\RemotePlugin;
use dokuwiki\Logger;
use dokuwiki\test\Remote\Mock\ApiCore as MockApiCore;
/**
* This class provides information about remote access to the wiki.
*
* == Types of methods ==
* There are two types of remote methods. The first is the core methods.
* These are always available and provided by dokuwiki.
* The other is plugin methods. These are provided by remote plugins.
*
* == Information structure ==
* The information about methods will be given in an array with the following structure:
* array(
* 'method.remoteName' => array(
* 'args' => array(
* 'type eg. string|int|...|date|file',
* )
* 'name' => 'method name in class',
* 'return' => 'type',
* 'public' => 1/0 - method bypass default group check (used by login)
* ['doc' = 'method documentation'],
* )
* )
*
* plugin names are formed the following:
* core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
* i.e.: dokuwiki.version or wiki.getPage
*
* plugin methods are formed like 'plugin.<plugin name>.<method name>'.
* i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
*/
class Api
{
/** @var ApiCall[] core methods provided by dokuwiki */
protected $coreMethods;
/** @var ApiCall[] remote methods provided by dokuwiki plugins */
protected $pluginMethods;
/**
* Get all available methods with remote access.
*
* @return ApiCall[] with information to all available methods
*/
public function getMethods()
{
return array_merge($this->getCoreMethods(), $this->getPluginMethods());
}
/**
* Collects all the core methods
*
* @param ApiCore|MockApiCore $apiCore this parameter is used for testing.
* Here you can pass a non-default RemoteAPICore instance. (for mocking)
* @return ApiCall[] all core methods.
*/
public function getCoreMethods($apiCore = null)
{
if (!$this->coreMethods) {
if ($apiCore === null) {
$this->coreMethods = (new LegacyApiCore())->getMethods();
} else {
$this->coreMethods = $apiCore->getMethods();
}
}
return $this->coreMethods;
}
/**
* Collects all the methods of the enabled Remote Plugins
*
* @return ApiCall[] all plugin methods.
*/
public function getPluginMethods()
{
if ($this->pluginMethods) return $this->pluginMethods;
$plugins = plugin_list('remote');
foreach ($plugins as $pluginName) {
/** @var RemotePlugin $plugin */
$plugin = plugin_load('remote', $pluginName);
if (!is_subclass_of($plugin, RemotePlugin::class)) {
Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin");
continue;
}
try {
$methods = $plugin->getMethods();
} catch (\ReflectionException $e) {
Logger::error(
"Remote Plugin $pluginName failed to return methods",
$e->getMessage(),
$e->getFile(),
$e->getLine()
);
continue;
}
foreach ($methods as $method => $call) {
$this->pluginMethods["plugin.$pluginName.$method"] = $call;
}
}
return $this->pluginMethods;
}
/**
* Call a method via remote api.
*
* @param string $method name of the method to call.
* @param array $args arguments to pass to the given method
* @return mixed result of method call, must be a primitive type.
* @throws RemoteException
*/
public function call($method, $args = [])
{
if ($args === null) {
$args = [];
}
// pre-flight checks
$this->ensureApiIsEnabled();
$methods = $this->getMethods();
if (!isset($methods[$method])) {
throw new RemoteException('Method does not exist', -32603);
}
$this->ensureAccessIsAllowed($methods[$method]);
// invoke the ApiCall
try {
return $methods[$method]($args);
} catch (\InvalidArgumentException | \ArgumentCountError $e) {
throw new RemoteException($e->getMessage(), -32602);
}
}
/**
* Check that the API is generally enabled
*
* @return void
* @throws RemoteException thrown when the API is disabled
*/
public function ensureApiIsEnabled()
{
global $conf;
if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') {
throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604);
}
}
/**
* Check if the current user is allowed to call the given method
*
* @param ApiCall $method
* @return void
* @throws AccessDeniedException Thrown when the user is not allowed to call the method
*/
public function ensureAccessIsAllowed(ApiCall $method)
{
global $conf;
global $INPUT;
global $USERINFO;
if ($method->isPublic()) return; // public methods are always allowed
if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users
if (trim($conf['remoteuser']) === '') return; // all users are allowed
if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) {
return; // user is allowed
}
// still here? no can do
throw new AccessDeniedException('server error. not authorized to call method', -32604);
}
}
|