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 * @throws RemoteException 62dd87735dSAndreas Gohr */ 63dd87735dSAndreas Gohr public function getMethods() 64dd87735dSAndreas Gohr { 65dd87735dSAndreas Gohr return array_merge($this->getCoreMethods(), $this->getPluginMethods()); 66dd87735dSAndreas Gohr } 67dd87735dSAndreas Gohr 68dd87735dSAndreas Gohr /** 6942e66c7aSAndreas Gohr * Collects all the core methods 7042e66c7aSAndreas Gohr * 7142e66c7aSAndreas Gohr * @param ApiCore|\RemoteAPICoreTest $apiCore this parameter is used for testing. 7242e66c7aSAndreas Gohr * Here you can pass a non-default RemoteAPICore instance. (for mocking) 7342e66c7aSAndreas Gohr * @return ApiCall[] all core methods. 7442e66c7aSAndreas Gohr */ 7542e66c7aSAndreas Gohr public function getCoreMethods($apiCore = null) 7642e66c7aSAndreas Gohr { 7742e66c7aSAndreas Gohr if (!$this->coreMethods) { 7842e66c7aSAndreas Gohr if ($apiCore === null) { 79*6cce3332SAndreas Gohr $this->coreMethods = (new ApiCore($this))->getMethods(); 8042e66c7aSAndreas Gohr } else { 81*6cce3332SAndreas Gohr $this->coreMethods = $apiCore->getMethods(); 8242e66c7aSAndreas Gohr } 8342e66c7aSAndreas Gohr } 8442e66c7aSAndreas Gohr return $this->coreMethods; 8542e66c7aSAndreas Gohr } 8642e66c7aSAndreas Gohr 8742e66c7aSAndreas Gohr /** 8842e66c7aSAndreas Gohr * Collects all the methods of the enabled Remote Plugins 8942e66c7aSAndreas Gohr * 9042e66c7aSAndreas Gohr * @return ApiCall[] all plugin methods. 9142e66c7aSAndreas Gohr */ 9242e66c7aSAndreas Gohr public function getPluginMethods() 9342e66c7aSAndreas Gohr { 9442e66c7aSAndreas Gohr if ($this->pluginMethods) return $this->pluginMethods; 9542e66c7aSAndreas Gohr 9642e66c7aSAndreas Gohr $plugins = plugin_list('remote'); 9742e66c7aSAndreas Gohr foreach ($plugins as $pluginName) { 9842e66c7aSAndreas Gohr /** @var RemotePlugin $plugin */ 9942e66c7aSAndreas Gohr $plugin = plugin_load('remote', $pluginName); 10042e66c7aSAndreas Gohr if (!is_subclass_of($plugin, RemotePlugin::class)) { 10142e66c7aSAndreas Gohr Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin"); 10242e66c7aSAndreas Gohr continue; 10342e66c7aSAndreas Gohr } 10442e66c7aSAndreas Gohr 10542e66c7aSAndreas Gohr try { 10642e66c7aSAndreas Gohr $methods = $plugin->getMethods(); 10742e66c7aSAndreas Gohr } catch (\ReflectionException $e) { 10842e66c7aSAndreas Gohr Logger::error( 10942e66c7aSAndreas Gohr "Remote Plugin $pluginName failed to return methods", 11042e66c7aSAndreas Gohr $e->getMessage(), 11142e66c7aSAndreas Gohr $e->getFile(), 11242e66c7aSAndreas Gohr $e->getLine() 11342e66c7aSAndreas Gohr ); 11442e66c7aSAndreas Gohr continue; 11542e66c7aSAndreas Gohr } 11642e66c7aSAndreas Gohr 11742e66c7aSAndreas Gohr foreach ($methods as $method => $call) { 11842e66c7aSAndreas Gohr $this->pluginMethods["plugin.$pluginName.$method"] = $call; 11942e66c7aSAndreas Gohr } 12042e66c7aSAndreas Gohr } 12142e66c7aSAndreas Gohr 12242e66c7aSAndreas Gohr return $this->pluginMethods; 12342e66c7aSAndreas Gohr } 12442e66c7aSAndreas Gohr 12542e66c7aSAndreas Gohr /** 126dd87735dSAndreas Gohr * Call a method via remote api. 127dd87735dSAndreas Gohr * 128dd87735dSAndreas Gohr * @param string $method name of the method to call. 129dd87735dSAndreas Gohr * @param array $args arguments to pass to the given method 130dd87735dSAndreas Gohr * @return mixed result of method call, must be a primitive type. 131dd87735dSAndreas Gohr * @throws RemoteException 132dd87735dSAndreas Gohr */ 133104a3b7cSAndreas Gohr public function call($method, $args = []) 134dd87735dSAndreas Gohr { 135dd87735dSAndreas Gohr if ($args === null) { 136104a3b7cSAndreas Gohr $args = []; 137dd87735dSAndreas Gohr } 138dd87735dSAndreas Gohr 13942e66c7aSAndreas Gohr // pre-flight checks 14042e66c7aSAndreas Gohr $this->ensureApiIsEnabled(); 14142e66c7aSAndreas Gohr $methods = $this->getMethods(); 14242e66c7aSAndreas Gohr if (!isset($methods[$method])) { 1431cdd0090SAndreas Gohr throw new RemoteException('Method does not exist', -32603); 144dd87735dSAndreas Gohr } 14542e66c7aSAndreas Gohr $this->ensureAccessIsAllowed($methods[$method]); 146dd87735dSAndreas Gohr 14742e66c7aSAndreas Gohr // invoke the ApiCall 148e1215f13SPhy try { 14942e66c7aSAndreas Gohr return $methods[$method]($args); 150e1215f13SPhy } catch (\ArgumentCountError $th) { 151e1215f13SPhy throw new RemoteException('Method does not exist - wrong parameter count.', -32603); 152e1215f13SPhy } 153dd87735dSAndreas Gohr } 154dd87735dSAndreas Gohr 155dd87735dSAndreas Gohr /** 15642e66c7aSAndreas Gohr * Check that the API is generally enabled 157dd87735dSAndreas Gohr * 158dd87735dSAndreas Gohr * @return void 15942e66c7aSAndreas Gohr * @throws RemoteException thrown when the API is disabled 160dd87735dSAndreas Gohr */ 1611468a128SAndreas Gohr public function ensureApiIsEnabled() 162dd87735dSAndreas Gohr { 16342e66c7aSAndreas Gohr global $conf; 16442e66c7aSAndreas Gohr if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') { 1651468a128SAndreas Gohr throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604); 16642e66c7aSAndreas Gohr } 16742e66c7aSAndreas Gohr } 16842e66c7aSAndreas Gohr 16942e66c7aSAndreas Gohr /** 17042e66c7aSAndreas Gohr * Check if the current user is allowed to call the given method 17142e66c7aSAndreas Gohr * 17242e66c7aSAndreas Gohr * @param ApiCall $method 17342e66c7aSAndreas Gohr * @return void 17442e66c7aSAndreas Gohr * @throws AccessDeniedException Thrown when the user is not allowed to call the method 17542e66c7aSAndreas Gohr */ 1761468a128SAndreas Gohr public function ensureAccessIsAllowed(ApiCall $method) 17742e66c7aSAndreas Gohr { 17842e66c7aSAndreas Gohr global $conf; 17942e66c7aSAndreas Gohr global $INPUT; 18042e66c7aSAndreas Gohr global $USERINFO; 18142e66c7aSAndreas Gohr 18242e66c7aSAndreas Gohr if ($method->isPublic()) return; // public methods are always allowed 18342e66c7aSAndreas Gohr if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users 18442e66c7aSAndreas Gohr if (trim($conf['remoteuser']) === '') return; // all users are allowed 18542e66c7aSAndreas Gohr if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) { 18642e66c7aSAndreas Gohr return; // user is allowed 18742e66c7aSAndreas Gohr } 18842e66c7aSAndreas Gohr 18942e66c7aSAndreas Gohr // still here? no can do 190dd87735dSAndreas Gohr throw new AccessDeniedException('server error. not authorized to call method', -32604); 191dd87735dSAndreas Gohr } 192dd87735dSAndreas Gohr 193dd87735dSAndreas Gohr /** 194dd87735dSAndreas Gohr * Transform file to xml 195dd87735dSAndreas Gohr * 196dd87735dSAndreas Gohr * @param mixed $data 197dd87735dSAndreas Gohr * @return mixed 198dd87735dSAndreas Gohr */ 199dd87735dSAndreas Gohr public function toFile($data) 200dd87735dSAndreas Gohr { 201dd87735dSAndreas Gohr return call_user_func($this->fileTransformation, $data); 202dd87735dSAndreas Gohr } 203dd87735dSAndreas Gohr 204dd87735dSAndreas Gohr /** 205dd87735dSAndreas Gohr * Transform date to xml 206dd87735dSAndreas Gohr * 207dd87735dSAndreas Gohr * @param mixed $data 208dd87735dSAndreas Gohr * @return mixed 209dd87735dSAndreas Gohr */ 210dd87735dSAndreas Gohr public function toDate($data) 211dd87735dSAndreas Gohr { 212dd87735dSAndreas Gohr return call_user_func($this->dateTransformation, $data); 213dd87735dSAndreas Gohr } 214dd87735dSAndreas Gohr 215dd87735dSAndreas Gohr /** 216dd87735dSAndreas Gohr * A simple transformation 217dd87735dSAndreas Gohr * 218dd87735dSAndreas Gohr * @param mixed $data 219dd87735dSAndreas Gohr * @return mixed 220dd87735dSAndreas Gohr */ 221dd87735dSAndreas Gohr public function dummyTransformation($data) 222dd87735dSAndreas Gohr { 223dd87735dSAndreas Gohr return $data; 224dd87735dSAndreas Gohr } 225dd87735dSAndreas Gohr 226dd87735dSAndreas Gohr /** 227dd87735dSAndreas Gohr * Set the transformer function 228dd87735dSAndreas Gohr * 229dd87735dSAndreas Gohr * @param callback $dateTransformation 230dd87735dSAndreas Gohr */ 231dd87735dSAndreas Gohr public function setDateTransformation($dateTransformation) 232dd87735dSAndreas Gohr { 233dd87735dSAndreas Gohr $this->dateTransformation = $dateTransformation; 234dd87735dSAndreas Gohr } 235dd87735dSAndreas Gohr 236dd87735dSAndreas Gohr /** 237dd87735dSAndreas Gohr * Set the transformer function 238dd87735dSAndreas Gohr * 239dd87735dSAndreas Gohr * @param callback $fileTransformation 240dd87735dSAndreas Gohr */ 241dd87735dSAndreas Gohr public function setFileTransformation($fileTransformation) 242dd87735dSAndreas Gohr { 243dd87735dSAndreas Gohr $this->fileTransformation = $fileTransformation; 244dd87735dSAndreas Gohr } 2456d7829a7SPhy 246dd87735dSAndreas Gohr} 247