1dd87735dSAndreas Gohr<?php 2dd87735dSAndreas Gohr 3dd87735dSAndreas Gohrnamespace dokuwiki\Remote; 4dd87735dSAndreas Gohr 5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\RemotePlugin; 642e66c7aSAndreas Gohruse dokuwiki\Logger; 7dd87735dSAndreas Gohr 8dd87735dSAndreas Gohr/** 9dd87735dSAndreas Gohr * This class provides information about remote access to the wiki. 10dd87735dSAndreas Gohr * 11dd87735dSAndreas Gohr * == Types of methods == 12dd87735dSAndreas Gohr * There are two types of remote methods. The first is the core methods. 13dd87735dSAndreas Gohr * These are always available and provided by dokuwiki. 14dd87735dSAndreas Gohr * The other is plugin methods. These are provided by remote plugins. 15dd87735dSAndreas Gohr * 16dd87735dSAndreas Gohr * == Information structure == 17dd87735dSAndreas Gohr * The information about methods will be given in an array with the following structure: 18dd87735dSAndreas Gohr * array( 19dd87735dSAndreas Gohr * 'method.remoteName' => array( 20dd87735dSAndreas Gohr * 'args' => array( 21dd87735dSAndreas Gohr * 'type eg. string|int|...|date|file', 22dd87735dSAndreas Gohr * ) 23dd87735dSAndreas Gohr * 'name' => 'method name in class', 24dd87735dSAndreas Gohr * 'return' => 'type', 25dd87735dSAndreas Gohr * 'public' => 1/0 - method bypass default group check (used by login) 26dd87735dSAndreas Gohr * ['doc' = 'method documentation'], 27dd87735dSAndreas Gohr * ) 28dd87735dSAndreas Gohr * ) 29dd87735dSAndreas Gohr * 30dd87735dSAndreas Gohr * plugin names are formed the following: 31dd87735dSAndreas Gohr * core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself. 32dd87735dSAndreas Gohr * i.e.: dokuwiki.version or wiki.getPage 33dd87735dSAndreas Gohr * 34dd87735dSAndreas Gohr * plugin methods are formed like 'plugin.<plugin name>.<method name>'. 35dd87735dSAndreas Gohr * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime 36dd87735dSAndreas Gohr */ 37dd87735dSAndreas Gohrclass Api 38dd87735dSAndreas Gohr{ 3942e66c7aSAndreas Gohr /** @var ApiCall[] core methods provided by dokuwiki */ 4042e66c7aSAndreas Gohr protected $coreMethods; 41dd87735dSAndreas Gohr 4242e66c7aSAndreas Gohr /** @var ApiCall[] remote methods provided by dokuwiki plugins */ 4342e66c7aSAndreas Gohr protected $pluginMethods; 44dd87735dSAndreas Gohr 45dd87735dSAndreas Gohr private $dateTransformation; 46dd87735dSAndreas Gohr private $fileTransformation; 47dd87735dSAndreas Gohr 48dd87735dSAndreas Gohr /** 49dd87735dSAndreas Gohr * constructor 50dd87735dSAndreas Gohr */ 51dd87735dSAndreas Gohr public function __construct() 52dd87735dSAndreas Gohr { 53104a3b7cSAndreas Gohr $this->dateTransformation = [$this, 'dummyTransformation']; 54104a3b7cSAndreas Gohr $this->fileTransformation = [$this, 'dummyTransformation']; 55dd87735dSAndreas Gohr } 56dd87735dSAndreas Gohr 57dd87735dSAndreas Gohr /** 58dd87735dSAndreas Gohr * Get all available methods with remote access. 59dd87735dSAndreas Gohr * 6042e66c7aSAndreas Gohr * @return ApiCall[] with information to all available methods 61dd87735dSAndreas Gohr */ 62dd87735dSAndreas Gohr public function getMethods() 63dd87735dSAndreas Gohr { 64dd87735dSAndreas Gohr return array_merge($this->getCoreMethods(), $this->getPluginMethods()); 65dd87735dSAndreas Gohr } 66dd87735dSAndreas Gohr 67dd87735dSAndreas Gohr /** 6842e66c7aSAndreas Gohr * Collects all the core methods 6942e66c7aSAndreas Gohr * 7042e66c7aSAndreas Gohr * @param ApiCore|\RemoteAPICoreTest $apiCore this parameter is used for testing. 7142e66c7aSAndreas Gohr * Here you can pass a non-default RemoteAPICore instance. (for mocking) 7242e66c7aSAndreas Gohr * @return ApiCall[] all core methods. 7342e66c7aSAndreas Gohr */ 7442e66c7aSAndreas Gohr public function getCoreMethods($apiCore = null) 7542e66c7aSAndreas Gohr { 7642e66c7aSAndreas Gohr if (!$this->coreMethods) { 7742e66c7aSAndreas Gohr if ($apiCore === null) { 78*e4e3d439SAndreas Gohr $this->coreMethods = (new ApiCore())->getMethods(); 7942e66c7aSAndreas Gohr } else { 806cce3332SAndreas Gohr $this->coreMethods = $apiCore->getMethods(); 8142e66c7aSAndreas Gohr } 8242e66c7aSAndreas Gohr } 8342e66c7aSAndreas Gohr return $this->coreMethods; 8442e66c7aSAndreas Gohr } 8542e66c7aSAndreas Gohr 8642e66c7aSAndreas Gohr /** 8742e66c7aSAndreas Gohr * Collects all the methods of the enabled Remote Plugins 8842e66c7aSAndreas Gohr * 8942e66c7aSAndreas Gohr * @return ApiCall[] all plugin methods. 9042e66c7aSAndreas Gohr */ 9142e66c7aSAndreas Gohr public function getPluginMethods() 9242e66c7aSAndreas Gohr { 9342e66c7aSAndreas Gohr if ($this->pluginMethods) return $this->pluginMethods; 9442e66c7aSAndreas Gohr 9542e66c7aSAndreas Gohr $plugins = plugin_list('remote'); 9642e66c7aSAndreas Gohr foreach ($plugins as $pluginName) { 9742e66c7aSAndreas Gohr /** @var RemotePlugin $plugin */ 9842e66c7aSAndreas Gohr $plugin = plugin_load('remote', $pluginName); 9942e66c7aSAndreas Gohr if (!is_subclass_of($plugin, RemotePlugin::class)) { 10042e66c7aSAndreas Gohr Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin"); 10142e66c7aSAndreas Gohr continue; 10242e66c7aSAndreas Gohr } 10342e66c7aSAndreas Gohr 10442e66c7aSAndreas Gohr try { 10542e66c7aSAndreas Gohr $methods = $plugin->getMethods(); 10642e66c7aSAndreas Gohr } catch (\ReflectionException $e) { 10742e66c7aSAndreas Gohr Logger::error( 10842e66c7aSAndreas Gohr "Remote Plugin $pluginName failed to return methods", 10942e66c7aSAndreas Gohr $e->getMessage(), 11042e66c7aSAndreas Gohr $e->getFile(), 11142e66c7aSAndreas Gohr $e->getLine() 11242e66c7aSAndreas Gohr ); 11342e66c7aSAndreas Gohr continue; 11442e66c7aSAndreas Gohr } 11542e66c7aSAndreas Gohr 11642e66c7aSAndreas Gohr foreach ($methods as $method => $call) { 11742e66c7aSAndreas Gohr $this->pluginMethods["plugin.$pluginName.$method"] = $call; 11842e66c7aSAndreas Gohr } 11942e66c7aSAndreas Gohr } 12042e66c7aSAndreas Gohr 12142e66c7aSAndreas Gohr return $this->pluginMethods; 12242e66c7aSAndreas Gohr } 12342e66c7aSAndreas Gohr 12442e66c7aSAndreas Gohr /** 125dd87735dSAndreas Gohr * Call a method via remote api. 126dd87735dSAndreas Gohr * 127dd87735dSAndreas Gohr * @param string $method name of the method to call. 128dd87735dSAndreas Gohr * @param array $args arguments to pass to the given method 129dd87735dSAndreas Gohr * @return mixed result of method call, must be a primitive type. 130dd87735dSAndreas Gohr * @throws RemoteException 131dd87735dSAndreas Gohr */ 132104a3b7cSAndreas Gohr public function call($method, $args = []) 133dd87735dSAndreas Gohr { 134dd87735dSAndreas Gohr if ($args === null) { 135104a3b7cSAndreas Gohr $args = []; 136dd87735dSAndreas Gohr } 137dd87735dSAndreas Gohr 13842e66c7aSAndreas Gohr // pre-flight checks 13942e66c7aSAndreas Gohr $this->ensureApiIsEnabled(); 14042e66c7aSAndreas Gohr $methods = $this->getMethods(); 14142e66c7aSAndreas Gohr if (!isset($methods[$method])) { 1421cdd0090SAndreas Gohr throw new RemoteException('Method does not exist', -32603); 143dd87735dSAndreas Gohr } 14442e66c7aSAndreas Gohr $this->ensureAccessIsAllowed($methods[$method]); 145dd87735dSAndreas Gohr 14642e66c7aSAndreas Gohr // invoke the ApiCall 147e1215f13SPhy try { 14842e66c7aSAndreas Gohr return $methods[$method]($args); 149e1215f13SPhy } catch (\ArgumentCountError $th) { 150e1215f13SPhy throw new RemoteException('Method does not exist - wrong parameter count.', -32603); 151e1215f13SPhy } 152dd87735dSAndreas Gohr } 153dd87735dSAndreas Gohr 154dd87735dSAndreas Gohr /** 15542e66c7aSAndreas Gohr * Check that the API is generally enabled 156dd87735dSAndreas Gohr * 157dd87735dSAndreas Gohr * @return void 15842e66c7aSAndreas Gohr * @throws RemoteException thrown when the API is disabled 159dd87735dSAndreas Gohr */ 1601468a128SAndreas Gohr public function ensureApiIsEnabled() 161dd87735dSAndreas Gohr { 16242e66c7aSAndreas Gohr global $conf; 16342e66c7aSAndreas Gohr if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') { 1641468a128SAndreas Gohr throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604); 16542e66c7aSAndreas Gohr } 16642e66c7aSAndreas Gohr } 16742e66c7aSAndreas Gohr 16842e66c7aSAndreas Gohr /** 16942e66c7aSAndreas Gohr * Check if the current user is allowed to call the given method 17042e66c7aSAndreas Gohr * 17142e66c7aSAndreas Gohr * @param ApiCall $method 17242e66c7aSAndreas Gohr * @return void 17342e66c7aSAndreas Gohr * @throws AccessDeniedException Thrown when the user is not allowed to call the method 17442e66c7aSAndreas Gohr */ 1751468a128SAndreas Gohr public function ensureAccessIsAllowed(ApiCall $method) 17642e66c7aSAndreas Gohr { 17742e66c7aSAndreas Gohr global $conf; 17842e66c7aSAndreas Gohr global $INPUT; 17942e66c7aSAndreas Gohr global $USERINFO; 18042e66c7aSAndreas Gohr 18142e66c7aSAndreas Gohr if ($method->isPublic()) return; // public methods are always allowed 18242e66c7aSAndreas Gohr if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users 18342e66c7aSAndreas Gohr if (trim($conf['remoteuser']) === '') return; // all users are allowed 18442e66c7aSAndreas Gohr if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) { 18542e66c7aSAndreas Gohr return; // user is allowed 18642e66c7aSAndreas Gohr } 18742e66c7aSAndreas Gohr 18842e66c7aSAndreas Gohr // still here? no can do 189dd87735dSAndreas Gohr throw new AccessDeniedException('server error. not authorized to call method', -32604); 190dd87735dSAndreas Gohr } 191dd87735dSAndreas Gohr 192dd87735dSAndreas Gohr /** 193dd87735dSAndreas Gohr * Transform file to xml 194dd87735dSAndreas Gohr * 195dd87735dSAndreas Gohr * @param mixed $data 196dd87735dSAndreas Gohr * @return mixed 197dd87735dSAndreas Gohr */ 198dd87735dSAndreas Gohr public function toFile($data) 199dd87735dSAndreas Gohr { 200dd87735dSAndreas Gohr return call_user_func($this->fileTransformation, $data); 201dd87735dSAndreas Gohr } 202dd87735dSAndreas Gohr 203dd87735dSAndreas Gohr /** 204dd87735dSAndreas Gohr * Transform date to xml 205dd87735dSAndreas Gohr * 206dd87735dSAndreas Gohr * @param mixed $data 207dd87735dSAndreas Gohr * @return mixed 208dd87735dSAndreas Gohr */ 209dd87735dSAndreas Gohr public function toDate($data) 210dd87735dSAndreas Gohr { 211dd87735dSAndreas Gohr return call_user_func($this->dateTransformation, $data); 212dd87735dSAndreas Gohr } 213dd87735dSAndreas Gohr 214dd87735dSAndreas Gohr /** 215dd87735dSAndreas Gohr * A simple transformation 216dd87735dSAndreas Gohr * 217dd87735dSAndreas Gohr * @param mixed $data 218dd87735dSAndreas Gohr * @return mixed 219dd87735dSAndreas Gohr */ 220dd87735dSAndreas Gohr public function dummyTransformation($data) 221dd87735dSAndreas Gohr { 222dd87735dSAndreas Gohr return $data; 223dd87735dSAndreas Gohr } 224dd87735dSAndreas Gohr 225dd87735dSAndreas Gohr /** 226dd87735dSAndreas Gohr * Set the transformer function 227dd87735dSAndreas Gohr * 228dd87735dSAndreas Gohr * @param callback $dateTransformation 229dd87735dSAndreas Gohr */ 230dd87735dSAndreas Gohr public function setDateTransformation($dateTransformation) 231dd87735dSAndreas Gohr { 232dd87735dSAndreas Gohr $this->dateTransformation = $dateTransformation; 233dd87735dSAndreas Gohr } 234dd87735dSAndreas Gohr 235dd87735dSAndreas Gohr /** 236dd87735dSAndreas Gohr * Set the transformer function 237dd87735dSAndreas Gohr * 238dd87735dSAndreas Gohr * @param callback $fileTransformation 239dd87735dSAndreas Gohr */ 240dd87735dSAndreas Gohr public function setFileTransformation($fileTransformation) 241dd87735dSAndreas Gohr { 242dd87735dSAndreas Gohr $this->fileTransformation = $fileTransformation; 243dd87735dSAndreas Gohr } 2446d7829a7SPhy 245dd87735dSAndreas Gohr} 246