dokuwiki/inc/Remote/Api.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);
}
}