181 lines
5.7 KiB
PHP
181 lines
5.7 KiB
PHP
<?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);
|
|
}
|
|
}
|