|
- <?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);
- }
- }
|