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..'. * 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); } }