You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

181 lines
5.7 KiB

  1. <?php
  2. namespace dokuwiki\Remote;
  3. use dokuwiki\Extension\RemotePlugin;
  4. use dokuwiki\Logger;
  5. use dokuwiki\test\Remote\Mock\ApiCore as MockApiCore;
  6. /**
  7. * This class provides information about remote access to the wiki.
  8. *
  9. * == Types of methods ==
  10. * There are two types of remote methods. The first is the core methods.
  11. * These are always available and provided by dokuwiki.
  12. * The other is plugin methods. These are provided by remote plugins.
  13. *
  14. * == Information structure ==
  15. * The information about methods will be given in an array with the following structure:
  16. * array(
  17. * 'method.remoteName' => array(
  18. * 'args' => array(
  19. * 'type eg. string|int|...|date|file',
  20. * )
  21. * 'name' => 'method name in class',
  22. * 'return' => 'type',
  23. * 'public' => 1/0 - method bypass default group check (used by login)
  24. * ['doc' = 'method documentation'],
  25. * )
  26. * )
  27. *
  28. * plugin names are formed the following:
  29. * core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
  30. * i.e.: dokuwiki.version or wiki.getPage
  31. *
  32. * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
  33. * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
  34. */
  35. class Api
  36. {
  37. /** @var ApiCall[] core methods provided by dokuwiki */
  38. protected $coreMethods;
  39. /** @var ApiCall[] remote methods provided by dokuwiki plugins */
  40. protected $pluginMethods;
  41. /**
  42. * Get all available methods with remote access.
  43. *
  44. * @return ApiCall[] with information to all available methods
  45. */
  46. public function getMethods()
  47. {
  48. return array_merge($this->getCoreMethods(), $this->getPluginMethods());
  49. }
  50. /**
  51. * Collects all the core methods
  52. *
  53. * @param ApiCore|MockApiCore $apiCore this parameter is used for testing.
  54. * Here you can pass a non-default RemoteAPICore instance. (for mocking)
  55. * @return ApiCall[] all core methods.
  56. */
  57. public function getCoreMethods($apiCore = null)
  58. {
  59. if (!$this->coreMethods) {
  60. if ($apiCore === null) {
  61. $this->coreMethods = (new LegacyApiCore())->getMethods();
  62. } else {
  63. $this->coreMethods = $apiCore->getMethods();
  64. }
  65. }
  66. return $this->coreMethods;
  67. }
  68. /**
  69. * Collects all the methods of the enabled Remote Plugins
  70. *
  71. * @return ApiCall[] all plugin methods.
  72. */
  73. public function getPluginMethods()
  74. {
  75. if ($this->pluginMethods) return $this->pluginMethods;
  76. $plugins = plugin_list('remote');
  77. foreach ($plugins as $pluginName) {
  78. /** @var RemotePlugin $plugin */
  79. $plugin = plugin_load('remote', $pluginName);
  80. if (!is_subclass_of($plugin, RemotePlugin::class)) {
  81. Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin");
  82. continue;
  83. }
  84. try {
  85. $methods = $plugin->getMethods();
  86. } catch (\ReflectionException $e) {
  87. Logger::error(
  88. "Remote Plugin $pluginName failed to return methods",
  89. $e->getMessage(),
  90. $e->getFile(),
  91. $e->getLine()
  92. );
  93. continue;
  94. }
  95. foreach ($methods as $method => $call) {
  96. $this->pluginMethods["plugin.$pluginName.$method"] = $call;
  97. }
  98. }
  99. return $this->pluginMethods;
  100. }
  101. /**
  102. * Call a method via remote api.
  103. *
  104. * @param string $method name of the method to call.
  105. * @param array $args arguments to pass to the given method
  106. * @return mixed result of method call, must be a primitive type.
  107. * @throws RemoteException
  108. */
  109. public function call($method, $args = [])
  110. {
  111. if ($args === null) {
  112. $args = [];
  113. }
  114. // pre-flight checks
  115. $this->ensureApiIsEnabled();
  116. $methods = $this->getMethods();
  117. if (!isset($methods[$method])) {
  118. throw new RemoteException('Method does not exist', -32603);
  119. }
  120. $this->ensureAccessIsAllowed($methods[$method]);
  121. // invoke the ApiCall
  122. try {
  123. return $methods[$method]($args);
  124. } catch (\InvalidArgumentException | \ArgumentCountError $e) {
  125. throw new RemoteException($e->getMessage(), -32602);
  126. }
  127. }
  128. /**
  129. * Check that the API is generally enabled
  130. *
  131. * @return void
  132. * @throws RemoteException thrown when the API is disabled
  133. */
  134. public function ensureApiIsEnabled()
  135. {
  136. global $conf;
  137. if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') {
  138. throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604);
  139. }
  140. }
  141. /**
  142. * Check if the current user is allowed to call the given method
  143. *
  144. * @param ApiCall $method
  145. * @return void
  146. * @throws AccessDeniedException Thrown when the user is not allowed to call the method
  147. */
  148. public function ensureAccessIsAllowed(ApiCall $method)
  149. {
  150. global $conf;
  151. global $INPUT;
  152. global $USERINFO;
  153. if ($method->isPublic()) return; // public methods are always allowed
  154. if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users
  155. if (trim($conf['remoteuser']) === '') return; // all users are allowed
  156. if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) {
  157. return; // user is allowed
  158. }
  159. // still here? no can do
  160. throw new AccessDeniedException('server error. not authorized to call method', -32604);
  161. }
  162. }