[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Remote/ -> Api.php (source)

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