1dd87735dSAndreas Gohr<?php 2dd87735dSAndreas Gohr 3dd87735dSAndreas Gohrnamespace dokuwiki\Remote; 4dd87735dSAndreas Gohr 5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\RemotePlugin; 6*42e66c7aSAndreas 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{ 39*42e66c7aSAndreas Gohr /** @var ApiCall[] core methods provided by dokuwiki */ 40*42e66c7aSAndreas Gohr protected $coreMethods; 41dd87735dSAndreas Gohr 42*42e66c7aSAndreas Gohr /** @var ApiCall[] remote methods provided by dokuwiki plugins */ 43*42e66c7aSAndreas 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 * 60*42e66c7aSAndreas 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 /** 69*42e66c7aSAndreas Gohr * Collects all the core methods 70*42e66c7aSAndreas Gohr * 71*42e66c7aSAndreas Gohr * @param ApiCore|\RemoteAPICoreTest $apiCore this parameter is used for testing. 72*42e66c7aSAndreas Gohr * Here you can pass a non-default RemoteAPICore instance. (for mocking) 73*42e66c7aSAndreas Gohr * @return ApiCall[] all core methods. 74*42e66c7aSAndreas Gohr */ 75*42e66c7aSAndreas Gohr public function getCoreMethods($apiCore = null) 76*42e66c7aSAndreas Gohr { 77*42e66c7aSAndreas Gohr if (!$this->coreMethods) { 78*42e66c7aSAndreas Gohr if ($apiCore === null) { 79*42e66c7aSAndreas Gohr $this->coreMethods = (new ApiCore($this))->getRemoteInfo(); 80*42e66c7aSAndreas Gohr } else { 81*42e66c7aSAndreas Gohr $this->coreMethods = $apiCore->getRemoteInfo(); 82*42e66c7aSAndreas Gohr } 83*42e66c7aSAndreas Gohr } 84*42e66c7aSAndreas Gohr return $this->coreMethods; 85*42e66c7aSAndreas Gohr } 86*42e66c7aSAndreas Gohr 87*42e66c7aSAndreas Gohr /** 88*42e66c7aSAndreas Gohr * Collects all the methods of the enabled Remote Plugins 89*42e66c7aSAndreas Gohr * 90*42e66c7aSAndreas Gohr * @return ApiCall[] all plugin methods. 91*42e66c7aSAndreas Gohr */ 92*42e66c7aSAndreas Gohr public function getPluginMethods() 93*42e66c7aSAndreas Gohr { 94*42e66c7aSAndreas Gohr if ($this->pluginMethods) return $this->pluginMethods; 95*42e66c7aSAndreas Gohr 96*42e66c7aSAndreas Gohr $plugins = plugin_list('remote'); 97*42e66c7aSAndreas Gohr foreach ($plugins as $pluginName) { 98*42e66c7aSAndreas Gohr /** @var RemotePlugin $plugin */ 99*42e66c7aSAndreas Gohr $plugin = plugin_load('remote', $pluginName); 100*42e66c7aSAndreas Gohr if (!is_subclass_of($plugin, RemotePlugin::class)) { 101*42e66c7aSAndreas Gohr Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin"); 102*42e66c7aSAndreas Gohr continue; 103*42e66c7aSAndreas Gohr } 104*42e66c7aSAndreas Gohr 105*42e66c7aSAndreas Gohr try { 106*42e66c7aSAndreas Gohr $methods = $plugin->getMethods(); 107*42e66c7aSAndreas Gohr } catch (\ReflectionException $e) { 108*42e66c7aSAndreas Gohr Logger::error( 109*42e66c7aSAndreas Gohr "Remote Plugin $pluginName failed to return methods", 110*42e66c7aSAndreas Gohr $e->getMessage(), 111*42e66c7aSAndreas Gohr $e->getFile(), 112*42e66c7aSAndreas Gohr $e->getLine() 113*42e66c7aSAndreas Gohr ); 114*42e66c7aSAndreas Gohr continue; 115*42e66c7aSAndreas Gohr } 116*42e66c7aSAndreas Gohr 117*42e66c7aSAndreas Gohr foreach ($methods as $method => $call) { 118*42e66c7aSAndreas Gohr $this->pluginMethods["plugin.$pluginName.$method"] = $call; 119*42e66c7aSAndreas Gohr } 120*42e66c7aSAndreas Gohr } 121*42e66c7aSAndreas Gohr 122*42e66c7aSAndreas Gohr return $this->pluginMethods; 123*42e66c7aSAndreas Gohr } 124*42e66c7aSAndreas Gohr 125*42e66c7aSAndreas 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 139*42e66c7aSAndreas Gohr // pre-flight checks 140*42e66c7aSAndreas Gohr $this->ensureApiIsEnabled(); 141*42e66c7aSAndreas Gohr $methods = $this->getMethods(); 142*42e66c7aSAndreas Gohr if (!isset($methods[$method])) { 1431cdd0090SAndreas Gohr throw new RemoteException('Method does not exist', -32603); 144dd87735dSAndreas Gohr } 145*42e66c7aSAndreas Gohr $this->ensureAccessIsAllowed($methods[$method]); 146dd87735dSAndreas Gohr 147*42e66c7aSAndreas Gohr // invoke the ApiCall 148e1215f13SPhy try { 149*42e66c7aSAndreas 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 /** 156*42e66c7aSAndreas Gohr * Check that the API is generally enabled 157dd87735dSAndreas Gohr * 158dd87735dSAndreas Gohr * @return void 159*42e66c7aSAndreas Gohr * @throws RemoteException thrown when the API is disabled 160dd87735dSAndreas Gohr */ 161*42e66c7aSAndreas Gohr protected function ensureApiIsEnabled() 162dd87735dSAndreas Gohr { 163*42e66c7aSAndreas Gohr global $conf; 164*42e66c7aSAndreas Gohr if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') { 165*42e66c7aSAndreas Gohr throw new RemoteException('Server Error. API is not enabled in config.', -32604); 166*42e66c7aSAndreas Gohr } 167*42e66c7aSAndreas Gohr } 168*42e66c7aSAndreas Gohr 169*42e66c7aSAndreas Gohr /** 170*42e66c7aSAndreas Gohr * Check if the current user is allowed to call the given method 171*42e66c7aSAndreas Gohr * 172*42e66c7aSAndreas Gohr * @param ApiCall $method 173*42e66c7aSAndreas Gohr * @return void 174*42e66c7aSAndreas Gohr * @throws AccessDeniedException Thrown when the user is not allowed to call the method 175*42e66c7aSAndreas Gohr */ 176*42e66c7aSAndreas Gohr protected function ensureAccessIsAllowed(ApiCall $method) 177*42e66c7aSAndreas Gohr { 178*42e66c7aSAndreas Gohr global $conf; 179*42e66c7aSAndreas Gohr global $INPUT; 180*42e66c7aSAndreas Gohr global $USERINFO; 181*42e66c7aSAndreas Gohr 182*42e66c7aSAndreas Gohr if ($method->isPublic()) return; // public methods are always allowed 183*42e66c7aSAndreas Gohr if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users 184*42e66c7aSAndreas Gohr if (trim($conf['remoteuser']) === '') return; // all users are allowed 185*42e66c7aSAndreas Gohr if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) { 186*42e66c7aSAndreas Gohr return; // user is allowed 187*42e66c7aSAndreas Gohr } 188*42e66c7aSAndreas Gohr 189*42e66c7aSAndreas 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