API: ApiCore tests fixed and extended
[dokuwiki.git] / inc / Remote / Api.php
blob39b92b4f4a3a3e674e164d8c88bf363643c2e585
1 <?php
3 namespace dokuwiki\Remote;
5 use dokuwiki\Extension\RemotePlugin;
6 use dokuwiki\Logger;
7 use dokuwiki\test\Remote\MockApiCore;
9 /**
10 * This class provides information about remote access to the wiki.
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.
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 * )
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
35 * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
36 * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
38 class Api
40 /** @var ApiCall[] core methods provided by dokuwiki */
41 protected $coreMethods;
43 /** @var ApiCall[] remote methods provided by dokuwiki plugins */
44 protected $pluginMethods;
46 /**
47 * Get all available methods with remote access.
49 * @return ApiCall[] with information to all available methods
51 public function getMethods()
53 return array_merge($this->getCoreMethods(), $this->getPluginMethods());
56 /**
57 * Collects all the core methods
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.
63 public function getCoreMethods($apiCore = null)
65 if (!$this->coreMethods) {
66 if ($apiCore === null) {
67 $this->coreMethods = (new ApiCore())->getMethods();
68 } else {
69 $this->coreMethods = $apiCore->getMethods();
72 return $this->coreMethods;
75 /**
76 * Collects all the methods of the enabled Remote Plugins
78 * @return ApiCall[] all plugin methods.
80 public function getPluginMethods()
82 if ($this->pluginMethods) return $this->pluginMethods;
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;
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()
102 continue;
105 foreach ($methods as $method => $call) {
106 $this->pluginMethods["plugin.$pluginName.$method"] = $call;
110 return $this->pluginMethods;
114 * Call a method via remote api.
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
121 public function call($method, $args = [])
123 if ($args === null) {
124 $args = [];
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);
133 $this->ensureAccessIsAllowed($methods[$method]);
135 // invoke the ApiCall
136 try {
137 return $methods[$method]($args);
138 } catch (\InvalidArgumentException $e) {
139 throw new RemoteException($e->getMessage(), -32602);
140 } catch (\ArgumentCountError $e) {
141 throw new RemoteException($e->getMessage(), -32602);
146 * Check that the API is generally enabled
148 * @return void
149 * @throws RemoteException thrown when the API is disabled
151 public function ensureApiIsEnabled()
153 global $conf;
154 if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') {
155 throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604);
160 * Check if the current user is allowed to call the given method
162 * @param ApiCall $method
163 * @return void
164 * @throws AccessDeniedException Thrown when the user is not allowed to call the method
166 public function ensureAccessIsAllowed(ApiCall $method)
168 global $conf;
169 global $INPUT;
170 global $USERINFO;
172 if ($method->isPublic()) return; // public methods are always allowed
173 if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users
174 if (trim($conf['remoteuser']) === '') return; // all users are allowed
175 if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) {
176 return; // user is allowed
179 // still here? no can do
180 throw new AccessDeniedException('server error. not authorized to call method', -32604);