weekly release 3.1dev
[moodle.git] / webservice / lib.php
CommitLineData
06e7fadc 1<?php
cc93c7da 2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
a0a07014 17
06e7fadc 18/**
cc93c7da 19 * Web services utility functions and classes
06e7fadc 20 *
a0a07014
JM
21 * @package core_webservice
22 * @copyright 2009 Jerome Mouneyrac <jerome@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
06e7fadc 24 */
25
cc93c7da 26require_once($CFG->libdir.'/externallib.php');
893d7f0f 27
a0a07014
JM
28/**
29 * WEBSERVICE_AUTHMETHOD_USERNAME - username/password authentication (also called simple authentication)
30 */
2d0acbd5 31define('WEBSERVICE_AUTHMETHOD_USERNAME', 0);
a0a07014
JM
32
33/**
34 * WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN - most common token authentication (external app, mobile app...)
35 */
2d0acbd5 36define('WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN', 1);
a0a07014
JM
37
38/**
39 * WEBSERVICE_AUTHMETHOD_SESSION_TOKEN - token for embedded application (requires Moodle session)
40 */
2d0acbd5
JP
41define('WEBSERVICE_AUTHMETHOD_SESSION_TOKEN', 2);
42
229a7099 43/**
44 * General web service library
a0a07014
JM
45 *
46 * @package core_webservice
47 * @copyright 2010 Jerome Mouneyrac <jerome@moodle.com>
48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
229a7099 49 */
50class webservice {
51
07cc3d11
JM
52 /**
53 * Authenticate user (used by download/upload file scripts)
a0a07014 54 *
07cc3d11
JM
55 * @param string $token
56 * @return array - contains the authenticated user, token and service objects
57 */
58 public function authenticate_user($token) {
59 global $DB, $CFG;
60
61 // web service must be enabled to use this script
62 if (!$CFG->enablewebservices) {
96d3b93b 63 throw new webservice_access_exception('Web services are not enabled in Advanced features.');
07cc3d11
JM
64 }
65
66 // Obtain token record
67 if (!$token = $DB->get_record('external_tokens', array('token' => $token))) {
96d3b93b
JM
68 //client may want to display login form => moodle_exception
69 throw new moodle_exception('invalidtoken', 'webservice');
07cc3d11
JM
70 }
71
d733a8cc
FM
72 $loginfaileddefaultparams = array(
73 'other' => array(
74 'method' => WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN,
75 'reason' => null,
76 'tokenid' => $token->id
77 )
78 );
79
07cc3d11
JM
80 // Validate token date
81 if ($token->validuntil and $token->validuntil < time()) {
d733a8cc
FM
82 $params = $loginfaileddefaultparams;
83 $params['other']['reason'] = 'token_expired';
84 $event = \core\event\webservice_login_failed::create($params);
85 $event->add_record_snapshot('external_tokens', $token);
86 $event->set_legacy_logdata(array(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '',
87 get_string('invalidtimedtoken', 'webservice'), 0));
88 $event->trigger();
07cc3d11 89 $DB->delete_records('external_tokens', array('token' => $token->token));
96d3b93b 90 throw new webservice_access_exception('Invalid token - token expired - check validuntil time for the token');
07cc3d11
JM
91 }
92
93 // Check ip
94 if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
d733a8cc
FM
95 $params = $loginfaileddefaultparams;
96 $params['other']['reason'] = 'ip_restricted';
97 $event = \core\event\webservice_login_failed::create($params);
98 $event->add_record_snapshot('external_tokens', $token);
99 $event->set_legacy_logdata(array(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '',
100 get_string('failedtolog', 'webservice') . ": " . getremoteaddr(), 0));
101 $event->trigger();
96d3b93b
JM
102 throw new webservice_access_exception('Invalid token - IP:' . getremoteaddr()
103 . ' is not supported');
07cc3d11
JM
104 }
105
106 //retrieve user link to the token
107 $user = $DB->get_record('user', array('id' => $token->userid, 'deleted' => 0), '*', MUST_EXIST);
108
109 // let enrol plugins deal with new enrolments if necessary
110 enrol_check_plugins($user);
111
112 // setup user session to check capability
d79d5ac2 113 \core\session\manager::set_user($user);
07cc3d11
JM
114
115 //assumes that if sid is set then there must be a valid associated session no matter the token type
116 if ($token->sid) {
d79d5ac2 117 if (!\core\session\manager::session_exists($token->sid)) {
07cc3d11 118 $DB->delete_records('external_tokens', array('sid' => $token->sid));
96d3b93b 119 throw new webservice_access_exception('Invalid session based token - session not found or expired');
07cc3d11
JM
120 }
121 }
122
123 //Non admin can not authenticate if maintenance mode
43731030 124 $hassiteconfig = has_capability('moodle/site:config', context_system::instance(), $user);
07cc3d11 125 if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
96d3b93b
JM
126 //this is usually temporary, client want to implement code logic => moodle_exception
127 throw new moodle_exception('sitemaintenance', 'admin');
07cc3d11
JM
128 }
129
130 //retrieve web service record
131 $service = $DB->get_record('external_services', array('id' => $token->externalserviceid, 'enabled' => 1));
132 if (empty($service)) {
133 // will throw exception if no token found
96d3b93b 134 throw new webservice_access_exception('Web service is not available (it doesn\'t exist or might be disabled)');
07cc3d11
JM
135 }
136
137 //check if there is any required system capability
43731030 138 if ($service->requiredcapability and !has_capability($service->requiredcapability, context_system::instance(), $user)) {
96d3b93b 139 throw new webservice_access_exception('The capability ' . $service->requiredcapability . ' is required.');
07cc3d11
JM
140 }
141
142 //specific checks related to user restricted service
143 if ($service->restrictedusers) {
144 $authoriseduser = $DB->get_record('external_services_users', array('externalserviceid' => $service->id, 'userid' => $user->id));
145
146 if (empty($authoriseduser)) {
96d3b93b
JM
147 throw new webservice_access_exception(
148 'The user is not allowed for this service. First you need to allow this user on the '
149 . $service->name . '\'s allowed users administration page.');
07cc3d11
JM
150 }
151
152 if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) {
96d3b93b 153 throw new webservice_access_exception('Invalid service - service expired - check validuntil time for this allowed user');
07cc3d11
JM
154 }
155
156 if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) {
96d3b93b
JM
157 throw new webservice_access_exception('Invalid service - IP:' . getremoteaddr()
158 . ' is not supported - check this allowed user');
07cc3d11
JM
159 }
160 }
161
162 //only confirmed user should be able to call web service
163 if (empty($user->confirmed)) {
d733a8cc
FM
164 $params = $loginfaileddefaultparams;
165 $params['other']['reason'] = 'user_unconfirmed';
166 $event = \core\event\webservice_login_failed::create($params);
167 $event->add_record_snapshot('external_tokens', $token);
168 $event->set_legacy_logdata(array(SITEID, 'webservice', 'user unconfirmed', '', $user->username));
169 $event->trigger();
96d3b93b 170 throw new moodle_exception('usernotconfirmed', 'moodle', '', $user->username);
07cc3d11
JM
171 }
172
173 //check the user is suspended
174 if (!empty($user->suspended)) {
d733a8cc
FM
175 $params = $loginfaileddefaultparams;
176 $params['other']['reason'] = 'user_suspended';
177 $event = \core\event\webservice_login_failed::create($params);
178 $event->add_record_snapshot('external_tokens', $token);
179 $event->set_legacy_logdata(array(SITEID, 'webservice', 'user suspended', '', $user->username));
180 $event->trigger();
96d3b93b 181 throw new webservice_access_exception('Refused web service access for suspended username: ' . $user->username);
07cc3d11
JM
182 }
183
184 //check if the auth method is nologin (in this case refuse connection)
185 if ($user->auth == 'nologin') {
d733a8cc
FM
186 $params = $loginfaileddefaultparams;
187 $params['other']['reason'] = 'nologin';
188 $event = \core\event\webservice_login_failed::create($params);
189 $event->add_record_snapshot('external_tokens', $token);
190 $event->set_legacy_logdata(array(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username));
191 $event->trigger();
96d3b93b 192 throw new webservice_access_exception('Refused web service access for nologin authentication username: ' . $user->username);
07cc3d11
JM
193 }
194
195 //Check if the user password is expired
196 $auth = get_auth_plugin($user->auth);
197 if (!empty($auth->config->expiration) and $auth->config->expiration == 1) {
198 $days2expire = $auth->password_expire($user->username);
199 if (intval($days2expire) < 0) {
d733a8cc
FM
200 $params = $loginfaileddefaultparams;
201 $params['other']['reason'] = 'password_expired';
202 $event = \core\event\webservice_login_failed::create($params);
203 $event->add_record_snapshot('external_tokens', $token);
204 $event->set_legacy_logdata(array(SITEID, 'webservice', 'expired password', '', $user->username));
205 $event->trigger();
96d3b93b 206 throw new moodle_exception('passwordisexpired', 'webservice');
07cc3d11
JM
207 }
208 }
209
210 // log token access
211 $DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id));
212
213 return array('user' => $user, 'token' => $token, 'service' => $service);
214 }
215
86dcc6f0 216 /**
a0a07014
JM
217 * Allow user to call a service
218 *
219 * @param stdClass $user a user
86dcc6f0 220 */
221 public function add_ws_authorised_user($user) {
222 global $DB;
caee6e6c 223 $user->timecreated = time();
86dcc6f0 224 $DB->insert_record('external_services_users', $user);
225 }
226
227 /**
a0a07014
JM
228 * Disallow a user to call a service
229 *
230 * @param stdClass $user a user
86dcc6f0 231 * @param int $serviceid
232 */
233 public function remove_ws_authorised_user($user, $serviceid) {
234 global $DB;
235 $DB->delete_records('external_services_users',
236 array('externalserviceid' => $serviceid, 'userid' => $user->id));
237 }
238
239 /**
a0a07014
JM
240 * Update allowed user settings (ip restriction, valid until...)
241 *
242 * @param stdClass $user
86dcc6f0 243 */
244 public function update_ws_authorised_user($user) {
245 global $DB;
246 $DB->update_record('external_services_users', $user);
247 }
248
249 /**
250 * Return list of allowed users with their options (ip/timecreated / validuntil...)
251 * for a given service
a0a07014
JM
252 *
253 * @param int $serviceid the service id to search against
86dcc6f0 254 * @return array $users
255 */
256 public function get_ws_authorised_users($serviceid) {
c924a469
PS
257 global $DB, $CFG;
258 $params = array($CFG->siteguest, $serviceid);
86dcc6f0 259 $sql = " SELECT u.id as id, esu.id as serviceuserid, u.email as email, u.firstname as firstname,
c924a469
PS
260 u.lastname as lastname,
261 esu.iprestriction as iprestriction, esu.validuntil as validuntil,
262 esu.timecreated as timecreated
263 FROM {user} u, {external_services_users} esu
264 WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
86dcc6f0 265 AND esu.userid = u.id
266 AND esu.externalserviceid = ?";
86dcc6f0 267
268 $users = $DB->get_records_sql($sql, $params);
269 return $users;
270 }
271
272 /**
a0a07014
JM
273 * Return an authorised user with their options (ip/timecreated / validuntil...)
274 *
275 * @param int $serviceid the service id to search against
276 * @param int $userid the user to search against
277 * @return stdClass
86dcc6f0 278 */
279 public function get_ws_authorised_user($serviceid, $userid) {
c924a469
PS
280 global $DB, $CFG;
281 $params = array($CFG->siteguest, $serviceid, $userid);
86dcc6f0 282 $sql = " SELECT u.id as id, esu.id as serviceuserid, u.email as email, u.firstname as firstname,
c924a469
PS
283 u.lastname as lastname,
284 esu.iprestriction as iprestriction, esu.validuntil as validuntil,
285 esu.timecreated as timecreated
286 FROM {user} u, {external_services_users} esu
287 WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
86dcc6f0 288 AND esu.userid = u.id
289 AND esu.externalserviceid = ?
290 AND u.id = ?";
291 $user = $DB->get_record_sql($sql, $params);
292 return $user;
293 }
294
229a7099 295 /**
a0a07014
JM
296 * Generate all tokens of a specific user
297 *
298 * @param int $userid user id
229a7099 299 */
300 public function generate_user_ws_tokens($userid) {
301 global $CFG, $DB;
c924a469 302
a0a07014 303 // generate a token for non admin if web service are enable and the user has the capability to create a token
43731030 304 if (!is_siteadmin() && has_capability('moodle/webservice:createtoken', context_system::instance(), $userid) && !empty($CFG->enablewebservices)) {
a0a07014 305 // for every service than the user is authorised on, create a token (if it doesn't already exist)
229a7099 306
a0a07014 307 // get all services which are set to all user (no restricted to specific users)
229a7099 308 $norestrictedservices = $DB->get_records('external_services', array('restrictedusers' => 0));
309 $serviceidlist = array();
310 foreach ($norestrictedservices as $service) {
311 $serviceidlist[] = $service->id;
312 }
313
a0a07014 314 // get all services which are set to the current user (the current user is specified in the restricted user list)
229a7099 315 $servicesusers = $DB->get_records('external_services_users', array('userid' => $userid));
316 foreach ($servicesusers as $serviceuser) {
317 if (!in_array($serviceuser->externalserviceid,$serviceidlist)) {
318 $serviceidlist[] = $serviceuser->externalserviceid;
319 }
320 }
321
a0a07014 322 // get all services which already have a token set for the current user
229a7099 323 $usertokens = $DB->get_records('external_tokens', array('userid' => $userid, 'tokentype' => EXTERNAL_TOKEN_PERMANENT));
324 $tokenizedservice = array();
325 foreach ($usertokens as $token) {
326 $tokenizedservice[] = $token->externalserviceid;
327 }
328
a0a07014 329 // create a token for the service which have no token already
229a7099 330 foreach ($serviceidlist as $serviceid) {
331 if (!in_array($serviceid, $tokenizedservice)) {
a0a07014 332 // create the token for this service
7a424cc4 333 $newtoken = new stdClass();
229a7099 334 $newtoken->token = md5(uniqid(rand(),1));
a0a07014 335 // check that the user has capability on this service
229a7099 336 $newtoken->tokentype = EXTERNAL_TOKEN_PERMANENT;
337 $newtoken->userid = $userid;
338 $newtoken->externalserviceid = $serviceid;
a0a07014 339 // TODO MDL-31190 find a way to get the context - UPDATE FOLLOWING LINE
43731030 340 $newtoken->contextid = context_system::instance()->id;
229a7099 341 $newtoken->creatorid = $userid;
342 $newtoken->timecreated = time();
343
344 $DB->insert_record('external_tokens', $newtoken);
345 }
346 }
347
348
349 }
350 }
351
352 /**
a0a07014
JM
353 * Return all tokens of a specific user
354 * + the service state (enabled/disabled)
355 * + the authorised user mode (restricted/not restricted)
356 *
357 * @param int $userid user id
358 * @return array
229a7099 359 */
360 public function get_user_ws_tokens($userid) {
361 global $DB;
362 //here retrieve token list (including linked users firstname/lastname and linked services name)
363 $sql = "SELECT
e88c193f 364 t.id, t.creatorid, t.token, u.firstname, u.lastname, s.id as wsid, s.name, s.enabled, s.restrictedusers, t.validuntil
229a7099 365 FROM
366 {external_tokens} t, {user} u, {external_services} s
367 WHERE
368 t.userid=? AND t.tokentype = ".EXTERNAL_TOKEN_PERMANENT." AND s.id = t.externalserviceid AND t.userid = u.id";
369 $tokens = $DB->get_records_sql($sql, array( $userid));
370 return $tokens;
371 }
372
373 /**
a0a07014
JM
374 * Return a token that has been created by the user (i.e. to created by an admin)
375 * If no tokens exist an exception is thrown
376 *
377 * The returned value is a stdClass:
9c954e88 378 * ->id token id
379 * ->token
380 * ->firstname user firstname
381 * ->lastname
382 * ->name service name
a0a07014
JM
383 *
384 * @param int $userid user id
385 * @param int $tokenid token id
386 * @return stdClass
229a7099 387 */
388 public function get_created_by_user_ws_token($userid, $tokenid) {
389 global $DB;
390 $sql = "SELECT
391 t.id, t.token, u.firstname, u.lastname, s.name
392 FROM
393 {external_tokens} t, {user} u, {external_services} s
394 WHERE
9c954e88 395 t.creatorid=? AND t.id=? AND t.tokentype = "
396 . EXTERNAL_TOKEN_PERMANENT
397 . " AND s.id = t.externalserviceid AND t.userid = u.id";
398 //must be the token creator
399 $token = $DB->get_record_sql($sql, array($userid, $tokenid), MUST_EXIST);
229a7099 400 return $token;
229a7099 401 }
402
9ef728d6 403 /**
a0a07014
JM
404 * Return a database token record for a token id
405 *
406 * @param int $tokenid token id
9ef728d6 407 * @return object token
408 */
409 public function get_token_by_id($tokenid) {
410 global $DB;
411 return $DB->get_record('external_tokens', array('id' => $tokenid));
412 }
413
229a7099 414 /**
a0a07014
JM
415 * Delete a token
416 *
417 * @param int $tokenid token id
229a7099 418 */
419 public function delete_user_ws_token($tokenid) {
420 global $DB;
421 $DB->delete_records('external_tokens', array('id'=>$tokenid));
422 }
bb98a5b2 423
c25662b0 424 /**
a0a07014
JM
425 * Delete a service
426 * Also delete function references and authorised user references.
427 *
428 * @param int $serviceid service id
c25662b0 429 */
430 public function delete_service($serviceid) {
431 global $DB;
432 $DB->delete_records('external_services_users', array('externalserviceid' => $serviceid));
433 $DB->delete_records('external_services_functions', array('externalserviceid' => $serviceid));
434 $DB->delete_records('external_tokens', array('externalserviceid' => $serviceid));
fc0fcb27 435 $DB->delete_records('external_services', array('id' => $serviceid));
c25662b0 436 }
437
bb98a5b2 438 /**
a0a07014
JM
439 * Get a full database token record for a given token value
440 *
bb98a5b2 441 * @param string $token
442 * @throws moodle_exception if there is multiple result
443 */
444 public function get_user_ws_token($token) {
445 global $DB;
446 return $DB->get_record('external_tokens', array('token'=>$token), '*', MUST_EXIST);
447 }
229a7099 448
72f68b51 449 /**
a0a07014
JM
450 * Get the functions list of a service list (by id)
451 *
452 * @param array $serviceids service ids
453 * @return array of functions
72f68b51 454 */
455 public function get_external_functions($serviceids) {
456 global $DB;
457 if (!empty($serviceids)) {
458 list($serviceids, $params) = $DB->get_in_or_equal($serviceids);
459 $sql = "SELECT f.*
460 FROM {external_functions} f
461 WHERE f.name IN (SELECT sf.functionname
462 FROM {external_services_functions} sf
463 WHERE sf.externalserviceid $serviceids)";
464 $functions = $DB->get_records_sql($sql, $params);
465 } else {
466 $functions = array();
467 }
468 return $functions;
469 }
470
0bf486a6 471 /**
a0a07014
JM
472 * Get the functions of a service list (by shortname). It can return only enabled functions if required.
473 *
474 * @param array $serviceshortnames service shortnames
475 * @param bool $enabledonly if true then only return functions for services that have been enabled
0bf486a6
JM
476 * @return array functions
477 */
478 public function get_external_functions_by_enabled_services($serviceshortnames, $enabledonly = true) {
479 global $DB;
480 if (!empty($serviceshortnames)) {
481 $enabledonlysql = $enabledonly?' AND s.enabled = 1 ':'';
482 list($serviceshortnames, $params) = $DB->get_in_or_equal($serviceshortnames);
483 $sql = "SELECT f.*
484 FROM {external_functions} f
485 WHERE f.name IN (SELECT sf.functionname
486 FROM {external_services_functions} sf, {external_services} s
487 WHERE s.shortname $serviceshortnames
488 AND sf.externalserviceid = s.id
489 " . $enabledonlysql . ")";
490 $functions = $DB->get_records_sql($sql, $params);
491 } else {
492 $functions = array();
493 }
494 return $functions;
72f68b51 495 }
496
9c954e88 497 /**
a0a07014
JM
498 * Get functions not included in a service
499 *
500 * @param int $serviceid service id
9c954e88 501 * @return array functions
502 */
503 public function get_not_associated_external_functions($serviceid) {
504 global $DB;
505 $select = "name NOT IN (SELECT s.functionname
506 FROM {external_services_functions} s
507 WHERE s.externalserviceid = :sid
508 )";
509
510 $functions = $DB->get_records_select('external_functions',
511 $select, array('sid' => $serviceid), 'name');
512
513 return $functions;
514 }
515
72f68b51 516 /**
517 * Get list of required capabilities of a service, sorted by functions
a0a07014 518 * Example of returned value:
72f68b51 519 * Array
520 * (
521 * [moodle_group_create_groups] => Array
522 * (
523 * [0] => moodle/course:managegroups
524 * )
525 *
526 * [moodle_enrol_get_enrolled_users] => Array
527 * (
528 * [0] => moodle/site:viewparticipants
529 * [1] => moodle/course:viewparticipants
530 * [2] => moodle/role:review
531 * [3] => moodle/site:accessallgroups
532 * [4] => moodle/course:enrolreview
533 * )
534 * )
a0a07014
JM
535 *
536 * @param int $serviceid service id
537 * @return array
72f68b51 538 */
539 public function get_service_required_capabilities($serviceid) {
540 $functions = $this->get_external_functions(array($serviceid));
541 $requiredusercaps = array();
542 foreach ($functions as $function) {
8819c47b 543 $functioncaps = explode(',', $function->capabilities);
72f68b51 544 if (!empty($functioncaps) and !empty($functioncaps[0])) {
545 foreach ($functioncaps as $functioncap) {
546 $requiredusercaps[$function->name][] = trim($functioncap);
547 }
548 }
549 }
550 return $requiredusercaps;
551 }
552
553 /**
554 * Get user capabilities (with context)
a0a07014 555 * Only useful for documentation purpose
b449d3b7
JM
556 * WARNING: do not use this "broken" function. It was created in the goal to display some capabilities
557 * required by users. In theory we should not need to display this kind of information
558 * as the front end does not display it itself. In pratice,
559 * admins would like the info, for more info you can follow: MDL-29962
a0a07014
JM
560 *
561 * @param int $userid user id
72f68b51 562 * @return array
563 */
564 public function get_user_capabilities($userid) {
565 global $DB;
566 //retrieve the user capabilities
fbf6cfe6
JM
567 $sql = "SELECT DISTINCT rc.id, rc.capability FROM {role_capabilities} rc, {role_assignments} ra
568 WHERE rc.roleid=ra.roleid AND ra.userid= ? AND rc.permission = ?";
569 $dbusercaps = $DB->get_records_sql($sql, array($userid, CAP_ALLOW));
72f68b51 570 $usercaps = array();
571 foreach ($dbusercaps as $usercap) {
572 $usercaps[$usercap->capability] = true;
573 }
574 return $usercaps;
575 }
576
577 /**
a0a07014 578 * Get missing user capabilities for a given service
b449d3b7
JM
579 * WARNING: do not use this "broken" function. It was created in the goal to display some capabilities
580 * required by users. In theory we should not need to display this kind of information
581 * as the front end does not display it itself. In pratice,
582 * admins would like the info, for more info you can follow: MDL-29962
a0a07014
JM
583 *
584 * @param array $users users
585 * @param int $serviceid service id
586 * @return array of missing capabilities, keys being the user ids
72f68b51 587 */
588 public function get_missing_capabilities_by_users($users, $serviceid) {
589 global $DB;
590 $usersmissingcaps = array();
591
592 //retrieve capabilities required by the service
593 $servicecaps = $this->get_service_required_capabilities($serviceid);
594
595 //retrieve users missing capabilities
596 foreach ($users as $user) {
597 //cast user array into object to be a bit more flexible
598 if (is_array($user)) {
599 $user = (object) $user;
600 }
601 $usercaps = $this->get_user_capabilities($user->id);
602
603 //detect the missing capabilities
604 foreach ($servicecaps as $functioname => $functioncaps) {
605 foreach ($functioncaps as $functioncap) {
12fc8acf 606 if (!array_key_exists($functioncap, $usercaps)) {
72f68b51 607 if (!isset($usersmissingcaps[$user->id])
608 or array_search($functioncap, $usersmissingcaps[$user->id]) === false) {
609 $usersmissingcaps[$user->id][] = $functioncap;
610 }
611 }
612 }
613 }
614 }
615
616 return $usersmissingcaps;
617 }
618
c25662b0 619 /**
a0a07014
JM
620 * Get an external service for a given service id
621 *
622 * @param int $serviceid service id
623 * @param int $strictness IGNORE_MISSING, MUST_EXIST...
624 * @return stdClass external service
c25662b0 625 */
626 public function get_external_service_by_id($serviceid, $strictness=IGNORE_MISSING) {
627 global $DB;
628 $service = $DB->get_record('external_services',
629 array('id' => $serviceid), '*', $strictness);
630 return $service;
631 }
632
c1b65883 633 /**
a0a07014
JM
634 * Get an external service for a given shortname
635 *
636 * @param string $shortname service shortname
637 * @param int $strictness IGNORE_MISSING, MUST_EXIST...
638 * @return stdClass external service
c1b65883
JM
639 */
640 public function get_external_service_by_shortname($shortname, $strictness=IGNORE_MISSING) {
641 global $DB;
642 $service = $DB->get_record('external_services',
643 array('shortname' => $shortname), '*', $strictness);
644 return $service;
645 }
646
72f68b51 647 /**
a0a07014
JM
648 * Get an external function for a given function id
649 *
650 * @param int $functionid function id
651 * @param int $strictness IGNORE_MISSING, MUST_EXIST...
652 * @return stdClass external function
72f68b51 653 */
654 public function get_external_function_by_id($functionid, $strictness=IGNORE_MISSING) {
655 global $DB;
656 $function = $DB->get_record('external_functions',
657 array('id' => $functionid), '*', $strictness);
658 return $function;
659 }
660
661 /**
662 * Add a function to a service
a0a07014
JM
663 *
664 * @param string $functionname function name
665 * @param int $serviceid service id
72f68b51 666 */
667 public function add_external_function_to_service($functionname, $serviceid) {
668 global $DB;
7a424cc4 669 $addedfunction = new stdClass();
72f68b51 670 $addedfunction->externalserviceid = $serviceid;
671 $addedfunction->functionname = $functionname;
672 $DB->insert_record('external_services_functions', $addedfunction);
673 }
674
c25662b0 675 /**
676 * Add a service
a0a07014
JM
677 * It generates the timecreated field automatically.
678 *
679 * @param stdClass $service
c25662b0 680 * @return serviceid integer
681 */
682 public function add_external_service($service) {
683 global $DB;
caee6e6c 684 $service->timecreated = time();
c25662b0 685 $serviceid = $DB->insert_record('external_services', $service);
686 return $serviceid;
687 }
688
a0a07014 689 /**
c25662b0 690 * Update a service
a0a07014
JM
691 * It modifies the timemodified automatically.
692 *
693 * @param stdClass $service
c25662b0 694 */
695 public function update_external_service($service) {
696 global $DB;
caee6e6c 697 $service->timemodified = time();
c25662b0 698 $DB->update_record('external_services', $service);
699 }
700
72f68b51 701 /**
a0a07014
JM
702 * Test whether an external function is already linked to a service
703 *
704 * @param string $functionname function name
705 * @param int $serviceid service id
72f68b51 706 * @return bool true if a matching function exists for the service, else false.
707 * @throws dml_exception if error
708 */
709 public function service_function_exists($functionname, $serviceid) {
710 global $DB;
711 return $DB->record_exists('external_services_functions',
712 array('externalserviceid' => $serviceid,
713 'functionname' => $functionname));
714 }
715
a0a07014
JM
716 /**
717 * Remove a function from a service
718 *
719 * @param string $functionname function name
720 * @param int $serviceid service id
721 */
72f68b51 722 public function remove_external_function_from_service($functionname, $serviceid) {
723 global $DB;
724 $DB->delete_records('external_services_functions',
725 array('externalserviceid' => $serviceid, 'functionname' => $functionname));
726
727 }
728
729
730}
229a7099 731
5593d2dc 732/**
733 * Exception indicating access control problem in web service call
96d3b93b
JM
734 * This exception should return general errors about web service setup.
735 * Errors related to the user like wrong username/password should not use it,
736 * you should not use this exception if you want to let the client implement
737 * some code logic against an access error.
a0a07014
JM
738 *
739 * @package core_webservice
740 * @copyright 2009 Petr Skodak
741 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5593d2dc 742 */
743class webservice_access_exception extends moodle_exception {
a0a07014 744
5593d2dc 745 /**
746 * Constructor
a0a07014
JM
747 *
748 * @param string $debuginfo the debug info
5593d2dc 749 */
750 function __construct($debuginfo) {
e8b21670 751 parent::__construct('accessexception', 'webservice', '', null, $debuginfo);
5593d2dc 752 }
753}
754
f0dafb3c 755/**
a0a07014
JM
756 * Check if a protocol is enabled
757 *
13ae7db2 758 * @param string $protocol name of WS protocol ('rest', 'soap', 'xmlrpc'...)
a0a07014 759 * @return bool true if the protocol is enabled
f0dafb3c 760 */
cc93c7da 761function webservice_protocol_is_enabled($protocol) {
762 global $CFG;
893d7f0f 763
cc93c7da 764 if (empty($CFG->enablewebservices)) {
765 return false;
893d7f0f 766 }
767
cc93c7da 768 $active = explode(',', $CFG->webserviceprotocols);
893d7f0f 769
cc93c7da 770 return(in_array($protocol, $active));
771}
893d7f0f 772
f0dafb3c 773/**
3f599588 774 * Mandatory interface for all test client classes.
a0a07014
JM
775 *
776 * @package core_webservice
777 * @copyright 2009 Petr Skodak
778 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f0dafb3c 779 */
780interface webservice_test_client_interface {
a0a07014 781
f0dafb3c 782 /**
783 * Execute test client WS request
a0a07014
JM
784 *
785 * @param string $serverurl server url (including the token param)
786 * @param string $function web service function name
787 * @param array $params parameters of the web service function
f0dafb3c 788 * @return mixed
789 */
790 public function simpletest($serverurl, $function, $params);
791}
792
06e7fadc 793/**
3f599588 794 * Mandatory interface for all web service protocol classes
a0a07014
JM
795 *
796 * @package core_webservice
797 * @copyright 2009 Petr Skodak
798 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
06e7fadc 799 */
01482a4a 800interface webservice_server_interface {
a0a07014 801
01482a4a
PS
802 /**
803 * Process request from client.
01482a4a
PS
804 */
805 public function run();
806}
88098133 807
01482a4a
PS
808/**
809 * Abstract web service base class.
a0a07014
JM
810 *
811 * @package core_webservice
812 * @copyright 2009 Petr Skodak
813 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
01482a4a
PS
814 */
815abstract class webservice_server implements webservice_server_interface {
88098133 816
a0a07014 817 /** @var string Name of the web server plugin */
88098133 818 protected $wsname = null;
819
a0a07014 820 /** @var string Name of local user */
c187722c
PS
821 protected $username = null;
822
a0a07014 823 /** @var string Password of the local user */
c187722c 824 protected $password = null;
b107e647 825
a0a07014 826 /** @var int The local user */
c3517f05 827 protected $userid = null;
828
a0a07014 829 /** @var integer Authentication method one of WEBSERVICE_AUTHMETHOD_* */
2d0acbd5 830 protected $authmethod;
88098133 831
a0a07014 832 /** @var string Authentication token*/
01482a4a 833 protected $token = null;
88098133 834
a0a07014 835 /** @var stdClass Restricted context */
88098133 836 protected $restricted_context;
837
a0a07014 838 /** @var int Restrict call to one service id*/
01482a4a
PS
839 protected $restricted_serviceid = null;
840
2d0acbd5 841 /**
a0a07014
JM
842 * Constructor
843 *
c924a469 844 * @param integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_*
2d0acbd5
JP
845 */
846 public function __construct($authmethod) {
847 $this->authmethod = $authmethod;
c924a469
PS
848 }
849
850
01482a4a
PS
851 /**
852 * Authenticate user using username+password or token.
853 * This function sets up $USER global.
854 * It is safe to use has_capability() after this.
855 * This method also verifies user is allowed to use this
856 * server.
01482a4a
PS
857 */
858 protected function authenticate_user() {
859 global $CFG, $DB;
860
861 if (!NO_MOODLE_COOKIES) {
862 throw new coding_exception('Cookies must be disabled in WS servers!');
863 }
864
d733a8cc
FM
865 $loginfaileddefaultparams = array(
866 'context' => context_system::instance(),
867 'other' => array(
868 'method' => $this->authmethod,
ee2df1a8 869 'reason' => null
d733a8cc
FM
870 )
871 );
872
2d0acbd5 873 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
01482a4a 874
1839e2f0 875 //we check that authentication plugin is enabled
876 //it is only required by simple authentication
877 if (!is_enabled_auth('webservice')) {
96d3b93b 878 throw new webservice_access_exception('The web service authentication plugin is disabled.');
1839e2f0 879 }
01482a4a 880
1839e2f0 881 if (!$auth = get_auth_plugin('webservice')) {
96d3b93b 882 throw new webservice_access_exception('The web service authentication plugin is missing.');
1839e2f0 883 }
01482a4a 884
43731030 885 $this->restricted_context = context_system::instance();
01482a4a
PS
886
887 if (!$this->username) {
96d3b93b 888 throw new moodle_exception('missingusername', 'webservice');
01482a4a
PS
889 }
890
891 if (!$this->password) {
96d3b93b 892 throw new moodle_exception('missingpassword', 'webservice');
01482a4a
PS
893 }
894
895 if (!$auth->user_login_webservice($this->username, $this->password)) {
d733a8cc
FM
896
897 // Log failed login attempts.
898 $params = $loginfaileddefaultparams;
899 $params['other']['reason'] = 'password';
900 $params['other']['username'] = $this->username;
901 $event = \core\event\webservice_login_failed::create($params);
902 $event->set_legacy_logdata(array(SITEID, 'webservice', get_string('simpleauthlog', 'webservice'), '' ,
903 get_string('failedtolog', 'webservice').": ".$this->username."/".$this->password." - ".getremoteaddr() , 0));
904 $event->trigger();
905
96d3b93b 906 throw new moodle_exception('wrongusernamepassword', 'webservice');
01482a4a
PS
907 }
908
07a90ec3 909 $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
01482a4a 910
2d0acbd5
JP
911 } else if ($this->authmethod == WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN){
912 $user = $this->authenticate_by_token(EXTERNAL_TOKEN_PERMANENT);
01482a4a 913 } else {
2d0acbd5 914 $user = $this->authenticate_by_token(EXTERNAL_TOKEN_EMBEDDED);
01482a4a 915 }
c924a469 916
07a90ec3 917 //Non admin can not authenticate if maintenance mode
43731030 918 $hassiteconfig = has_capability('moodle/site:config', context_system::instance(), $user);
07a90ec3 919 if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
96d3b93b 920 throw new moodle_exception('sitemaintenance', 'admin');
07a90ec3
JM
921 }
922
923 //only confirmed user should be able to call web service
924 if (!empty($user->deleted)) {
d733a8cc
FM
925 $params = $loginfaileddefaultparams;
926 $params['other']['reason'] = 'user_deleted';
927 $params['other']['username'] = $user->username;
928 $event = \core\event\webservice_login_failed::create($params);
929 $event->set_legacy_logdata(array(SITEID, '', '', '', get_string('wsaccessuserdeleted', 'webservice',
930 $user->username) . " - ".getremoteaddr(), 0, $user->id));
931 $event->trigger();
96d3b93b 932 throw new webservice_access_exception('Refused web service access for deleted username: ' . $user->username);
07a90ec3
JM
933 }
934
935 //only confirmed user should be able to call web service
936 if (empty($user->confirmed)) {
d733a8cc
FM
937 $params = $loginfaileddefaultparams;
938 $params['other']['reason'] = 'user_unconfirmed';
939 $params['other']['username'] = $user->username;
940 $event = \core\event\webservice_login_failed::create($params);
941 $event->set_legacy_logdata(array(SITEID, '', '', '', get_string('wsaccessuserunconfirmed', 'webservice',
942 $user->username) . " - ".getremoteaddr(), 0, $user->id));
943 $event->trigger();
96d3b93b 944 throw new moodle_exception('wsaccessuserunconfirmed', 'webservice', '', $user->username);
07a90ec3
JM
945 }
946
947 //check the user is suspended
948 if (!empty($user->suspended)) {
d733a8cc
FM
949 $params = $loginfaileddefaultparams;
950 $params['other']['reason'] = 'user_unconfirmed';
951 $params['other']['username'] = $user->username;
952 $event = \core\event\webservice_login_failed::create($params);
953 $event->set_legacy_logdata(array(SITEID, '', '', '', get_string('wsaccessusersuspended', 'webservice',
954 $user->username) . " - ".getremoteaddr(), 0, $user->id));
955 $event->trigger();
96d3b93b 956 throw new webservice_access_exception('Refused web service access for suspended username: ' . $user->username);
07a90ec3
JM
957 }
958
959 //retrieve the authentication plugin if no previously done
960 if (empty($auth)) {
961 $auth = get_auth_plugin($user->auth);
962 }
963
964 // check if credentials have expired
965 if (!empty($auth->config->expiration) and $auth->config->expiration == 1) {
966 $days2expire = $auth->password_expire($user->username);
967 if (intval($days2expire) < 0 ) {
d733a8cc
FM
968 $params = $loginfaileddefaultparams;
969 $params['other']['reason'] = 'password_expired';
970 $params['other']['username'] = $user->username;
971 $event = \core\event\webservice_login_failed::create($params);
972 $event->set_legacy_logdata(array(SITEID, '', '', '', get_string('wsaccessuserexpired', 'webservice',
973 $user->username) . " - ".getremoteaddr(), 0, $user->id));
974 $event->trigger();
96d3b93b 975 throw new webservice_access_exception('Refused web service access for password expired username: ' . $user->username);
07a90ec3
JM
976 }
977 }
978
979 //check if the auth method is nologin (in this case refuse connection)
980 if ($user->auth=='nologin') {
d733a8cc
FM
981 $params = $loginfaileddefaultparams;
982 $params['other']['reason'] = 'login';
983 $params['other']['username'] = $user->username;
984 $event = \core\event\webservice_login_failed::create($params);
985 $event->set_legacy_logdata(array(SITEID, '', '', '', get_string('wsaccessusernologin', 'webservice',
986 $user->username) . " - ".getremoteaddr(), 0, $user->id));
987 $event->trigger();
96d3b93b 988 throw new webservice_access_exception('Refused web service access for nologin authentication username: ' . $user->username);
07a90ec3
JM
989 }
990
01482a4a 991 // now fake user login, the session is completely empty too
e922fe23 992 enrol_check_plugins($user);
d79d5ac2 993 \core\session\manager::set_user($user);
c3517f05 994 $this->userid = $user->id;
01482a4a 995
0f1e3914 996 if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
96d3b93b 997 throw new webservice_access_exception('You are not allowed to use the {$a} protocol (missing capability: webservice/' . $this->wsname . ':use)');
01482a4a
PS
998 }
999
1000 external_api::set_context_restriction($this->restricted_context);
1001 }
c924a469 1002
a0a07014
JM
1003 /**
1004 * User authentication by token
1005 *
1006 * @param string $tokentype token type (EXTERNAL_TOKEN_EMBEDDED or EXTERNAL_TOKEN_PERMANENT)
1007 * @return stdClass the authenticated user
1008 * @throws webservice_access_exception
1009 */
2d0acbd5
JP
1010 protected function authenticate_by_token($tokentype){
1011 global $DB;
d733a8cc
FM
1012
1013 $loginfaileddefaultparams = array(
1014 'context' => context_system::instance(),
1015 'other' => array(
1016 'method' => $this->authmethod,
ee2df1a8 1017 'reason' => null
d733a8cc
FM
1018 )
1019 );
1020
2d0acbd5 1021 if (!$token = $DB->get_record('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype))) {
d733a8cc
FM
1022 // Log failed login attempts.
1023 $params = $loginfaileddefaultparams;
1024 $params['other']['reason'] = 'invalid_token';
1025 $event = \core\event\webservice_login_failed::create($params);
1026 $event->set_legacy_logdata(array(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' ,
1027 get_string('failedtolog', 'webservice').": ".$this->token. " - ".getremoteaddr() , 0));
1028 $event->trigger();
96d3b93b 1029 throw new moodle_exception('invalidtoken', 'webservice');
2d0acbd5 1030 }
c924a469 1031
2d0acbd5
JP
1032 if ($token->validuntil and $token->validuntil < time()) {
1033 $DB->delete_records('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype));
96d3b93b 1034 throw new webservice_access_exception('Invalid token - token expired - check validuntil time for the token');
2d0acbd5 1035 }
c924a469 1036
2d0acbd5 1037 if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
d79d5ac2 1038 if (!\core\session\manager::session_exists($token->sid)){
2d0acbd5 1039 $DB->delete_records('external_tokens', array('sid'=>$token->sid));
96d3b93b 1040 throw new webservice_access_exception('Invalid session based token - session not found or expired');
2d0acbd5
JP
1041 }
1042 }
1043
1044 if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
d733a8cc
FM
1045 $params = $loginfaileddefaultparams;
1046 $params['other']['reason'] = 'ip_restricted';
1047 $params['other']['tokenid'] = $token->id;
1048 $event = \core\event\webservice_login_failed::create($params);
1049 $event->add_record_snapshot('external_tokens', $token);
1050 $event->set_legacy_logdata(array(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' ,
1051 get_string('failedtolog', 'webservice').": ".getremoteaddr() , 0));
1052 $event->trigger();
96d3b93b
JM
1053 throw new webservice_access_exception('Invalid service - IP:' . getremoteaddr()
1054 . ' is not supported - check this allowed user');
2d0acbd5
JP
1055 }
1056
d197ea43 1057 $this->restricted_context = context::instance_by_id($token->contextid);
2d0acbd5
JP
1058 $this->restricted_serviceid = $token->externalserviceid;
1059
07a90ec3 1060 $user = $DB->get_record('user', array('id'=>$token->userid), '*', MUST_EXIST);
2d0acbd5
JP
1061
1062 // log token access
1063 $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id));
c924a469 1064
2d0acbd5 1065 return $user;
c924a469 1066
2d0acbd5 1067 }
93ce0e82
JM
1068
1069 /**
1070 * Intercept some moodlewssettingXXX $_GET and $_POST parameter
1071 * that are related to the web service call and are not the function parameters
1072 */
1073 protected function set_web_service_call_settings() {
1074 global $CFG;
1075
1076 // Default web service settings.
1077 // Must be the same XXX key name as the external_settings::set_XXX function.
1078 // Must be the same XXX ws parameter name as 'moodlewssettingXXX'.
1079 $externalsettings = array(
1080 'raw' => false,
1081 'fileurl' => true,
1082 'filter' => false);
1083
1084 // Load the external settings with the web service settings.
1085 $settings = external_settings::get_instance();
1086 foreach ($externalsettings as $name => $default) {
1087
1088 $wsparamname = 'moodlewssetting' . $name;
1089
1090 // Retrieve and remove the setting parameter from the request.
1091 $value = optional_param($wsparamname, $default, PARAM_BOOL);
1092 unset($_GET[$wsparamname]);
1093 unset($_POST[$wsparamname]);
1094
1095 $functioname = 'set_' . $name;
1096 $settings->$functioname($value);
1097 }
1098
1099 }
01482a4a
PS
1100}
1101
1102/**
a0a07014
JM
1103 * Special abstraction of our services that allows interaction with stock Zend ws servers.
1104 *
1105 * @package core_webservice
1106 * @copyright 2009 Jerome Mouneyrac <jerome@moodle.com>
1107 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
01482a4a
PS
1108 */
1109abstract class webservice_zend_server extends webservice_server {
1110
13ae7db2 1111 /** @var string Name of the zend server class : moodle_zend_soap_server, Zend_Soap_AutoDiscover, ...*/
01482a4a
PS
1112 protected $zend_class;
1113
a0a07014 1114 /** @var stdClass Zend server instance */
01482a4a
PS
1115 protected $zend_server;
1116
a0a07014 1117 /** @var string Virtual web service class with all functions user name execute, created on the fly */
01482a4a
PS
1118 protected $service_class;
1119
88098133 1120 /**
a0a07014
JM
1121 * Constructor
1122 *
1123 * @param int $authmethod authentication method - one of WEBSERVICE_AUTHMETHOD_*
1124 * @param string $zend_class Name of the zend server class
88098133 1125 */
2d0acbd5
JP
1126 public function __construct($authmethod, $zend_class) {
1127 parent::__construct($authmethod);
88098133 1128 $this->zend_class = $zend_class;
1129 }
1130
1131 /**
1132 * Process request from client.
a0a07014
JM
1133 *
1134 * @uses die
88098133 1135 */
2458e30a 1136 public function run() {
88098133 1137 // we will probably need a lot of memory in some functions
346c5887 1138 raise_memory_limit(MEMORY_EXTRA);
88098133 1139
1140 // set some longer timeout, this script is not sending any output,
1141 // this means we need to manually extend the timeout operations
1142 // that need longer time to finish
1143 external_api::set_timeout();
1144
e8b21670 1145 // now create the instance of zend server
1146 $this->init_zend_server();
1147
88098133 1148 // set up exception handler first, we want to sent them back in correct format that
1149 // the other system understands
1150 // we do not need to call the original default handler because this ws handler does everything
1151 set_exception_handler(array($this, 'exception_handler'));
1152
c187722c
PS
1153 // init all properties from the request data
1154 $this->parse_request();
1155
88098133 1156 // this sets up $USER and $SESSION and context restrictions
1157 $this->authenticate_user();
1158
1159 // make a list of all functions user is allowed to excecute
1160 $this->init_service_class();
1161
2458e30a 1162 // tell server what functions are available
88098133 1163 $this->zend_server->setClass($this->service_class);
d9ad0103 1164
d733a8cc
FM
1165 // Log the web service request.
1166 $params = array(
1167 'other' => array(
1168 'function' => 'unknown'
1169 )
1170 );
1171 $event = \core\event\webservice_function_called::create($params);
1172 $event->set_legacy_logdata(array(SITEID, 'webservice', '', '', $this->zend_class.' '.getremoteaddr(), 0, $this->userid));
1173 $event->trigger();
1e29fe3f 1174
936cabd6
AO
1175 //send headers
1176 $this->send_headers();
1177
2458e30a 1178 // execute and return response, this sends some headers too
88098133 1179 $response = $this->zend_server->handle();
2458e30a 1180
88098133 1181 // session cleanup
1182 $this->session_cleanup();
1183
ca6340bf 1184 //finally send the result
88098133 1185 echo $response;
1186 die;
1187 }
1188
1189 /**
1190 * Load virtual class needed for Zend api
88098133 1191 */
1192 protected function init_service_class() {
1193 global $USER, $DB;
1194
1195 // first ofall get a complete list of services user is allowed to access
88098133 1196
01482a4a
PS
1197 if ($this->restricted_serviceid) {
1198 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
1199 $wscond1 = 'AND s.id = :sid1';
1200 $wscond2 = 'AND s.id = :sid2';
1201 } else {
1202 $params = array();
1203 $wscond1 = '';
1204 $wscond2 = '';
1205 }
88098133 1206
01482a4a
PS
1207 // now make sure the function is listed in at least one service user is allowed to use
1208 // allow access only if:
1209 // 1/ entry in the external_services_users table if required
1210 // 2/ validuntil not reached
1211 // 3/ has capability if specified in service desc
1212 // 4/ iprestriction
88098133 1213
01482a4a
PS
1214 $sql = "SELECT s.*, NULL AS iprestriction
1215 FROM {external_services} s
1216 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
1217 WHERE s.enabled = 1 $wscond1
88098133 1218
01482a4a
PS
1219 UNION
1220
1221 SELECT s.*, su.iprestriction
1222 FROM {external_services} s
1223 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
1224 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
6bfa9c49 1225 WHERE s.enabled = 1 AND (su.validuntil IS NULL OR su.validuntil < :now) $wscond2";
01482a4a
PS
1226
1227 $params = array_merge($params, array('userid'=>$USER->id, 'now'=>time()));
88098133 1228
1229 $serviceids = array();
1230 $rs = $DB->get_recordset_sql($sql, $params);
1231
1232 // now make sure user may access at least one service
1233 $remoteaddr = getremoteaddr();
1234 $allowed = false;
1235 foreach ($rs as $service) {
1236 if (isset($serviceids[$service->id])) {
1237 continue;
1238 }
1239 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
1240 continue; // cap required, sorry
1241 }
1242 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
1243 continue; // wrong request source ip, sorry
1244 }
1245 $serviceids[$service->id] = $service->id;
1246 }
1247 $rs->close();
1248
1249 // now get the list of all functions
72f68b51 1250 $wsmanager = new webservice();
1251 $functions = $wsmanager->get_external_functions($serviceids);
88098133 1252
1253 // now make the virtual WS class with all the fuctions for this particular user
1254 $methods = '';
1255 foreach ($functions as $function) {
1256 $methods .= $this->get_virtual_method_code($function);
1257 }
1258
5593d2dc 1259 // let's use unique class name, there might be problem in unit tests
88098133 1260 $classname = 'webservices_virtual_class_000000';
1261 while(class_exists($classname)) {
1262 $classname++;
1263 }
1264
1265 $code = '
1266/**
1267 * Virtual class web services for user id '.$USER->id.' in context '.$this->restricted_context->id.'.
1268 */
1269class '.$classname.' {
1270'.$methods.'
1271}
1272';
f0dafb3c 1273
88098133 1274 // load the virtual class definition into memory
1275 eval($code);
88098133 1276 $this->service_class = $classname;
1277 }
1278
1279 /**
1280 * returns virtual method code
a0a07014
JM
1281 *
1282 * @param stdClass $function a record from external_function
88098133 1283 * @return string PHP code
1284 */
1285 protected function get_virtual_method_code($function) {
1286 global $CFG;
1287
5593d2dc 1288 $function = external_function_info($function);
88098133 1289
b437f681
JP
1290 //arguments in function declaration line with defaults.
1291 $paramanddefaults = array();
1292 //arguments used as parameters for external lib call.
88098133 1293 $params = array();
1294 $params_desc = array();
453a7a85 1295 foreach ($function->parameters_desc->keys as $name=>$keydesc) {
a8dd0325 1296 $param = '$'.$name;
b437f681 1297 $paramanddefault = $param;
a8dd0325 1298 //need to generate the default if there is any
1299 if ($keydesc instanceof external_value) {
1300 if ($keydesc->required == VALUE_DEFAULT) {
1301 if ($keydesc->default===null) {
b437f681 1302 $paramanddefault .= '=null';
a8dd0325 1303 } else {
1304 switch($keydesc->type) {
1305 case PARAM_BOOL:
4d3fd60d 1306 $paramanddefault .= '='. (int) $keydesc->default; break;
a8dd0325 1307 case PARAM_INT:
b437f681 1308 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 1309 case PARAM_FLOAT;
b437f681 1310 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 1311 default:
b437f681 1312 $paramanddefault .= '=\''.$keydesc->default.'\'';
a8dd0325 1313 }
1314 }
1315 } else if ($keydesc->required == VALUE_OPTIONAL) {
bd132709
MG
1316 // It does not make sense to declare a parameter VALUE_OPTIONAL.
1317 // VALUE_OPTIONAL is used only for array/object key.
1318 throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
a8dd0325 1319 }
1320 } else { //for the moment we do not support default for other structure types
774b1b0f 1321 if ($keydesc->required == VALUE_DEFAULT) {
1322 //accept empty array as default
1323 if (isset($keydesc->default) and is_array($keydesc->default)
1324 and empty($keydesc->default)) {
fde884c3 1325 $paramanddefault .= '=array()';
774b1b0f 1326 } else {
1327 throw new moodle_exception('errornotemptydefaultparamarray', 'webservice', '', $name);
1328 }
1329 }
1330 if ($keydesc->required == VALUE_OPTIONAL) {
1331 throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
a8dd0325 1332 }
1333 }
b437f681
JP
1334 $params[] = $param;
1335 $paramanddefaults[] = $paramanddefault;
cc7fb382 1336 $type = $this->get_phpdoc_type($keydesc);
453a7a85 1337 $params_desc[] = ' * @param '.$type.' $'.$name.' '.$keydesc->desc;
88098133 1338 }
b437f681
JP
1339 $params = implode(', ', $params);
1340 $paramanddefaults = implode(', ', $paramanddefaults);
1341 $params_desc = implode("\n", $params_desc);
fde884c3 1342
94a9b9e7 1343 $serviceclassmethodbody = $this->service_class_method_body($function, $params);
88098133 1344
453a7a85 1345 if (is_null($function->returns_desc)) {
1346 $return = ' * @return void';
1347 } else {
cc7fb382 1348 $type = $this->get_phpdoc_type($function->returns_desc);
453a7a85 1349 $return = ' * @return '.$type.' '.$function->returns_desc->desc;
1350 }
d4e764ab 1351
01482a4a 1352 // now crate the virtual method that calls the ext implementation
88098133 1353
1354 $code = '
1355 /**
5593d2dc 1356 * '.$function->description.'
1357 *
88098133 1358'.$params_desc.'
453a7a85 1359'.$return.'
88098133 1360 */
b437f681 1361 public function '.$function->name.'('.$paramanddefaults.') {
c77e75a3 1362'.$serviceclassmethodbody.'
88098133 1363 }
1364';
1365 return $code;
1366 }
c924a469 1367
a0a07014
JM
1368 /**
1369 * Get the phpdoc type for an external_description
1370 * external_value => int, double or string
1371 * external_single_structure => object|struct, on-fly generated stdClass name, ...
1372 * external_multiple_structure => array
1373 *
1374 * @param string $keydesc any of PARAM_*
1375 * @return string phpdoc type (string, double, int, array...)
1376 */
cc7fb382
TH
1377 protected function get_phpdoc_type($keydesc) {
1378 if ($keydesc instanceof external_value) {
1379 switch($keydesc->type) {
1380 case PARAM_BOOL: // 0 or 1 only for now
1381 case PARAM_INT:
1382 $type = 'int'; break;
1383 case PARAM_FLOAT;
1384 $type = 'double'; break;
1385 default:
1386 $type = 'string';
1387 }
1388
1389 } else if ($keydesc instanceof external_single_structure) {
1390 $classname = $this->generate_simple_struct_class($keydesc);
1391 $type = $classname;
1392
1393 } else if ($keydesc instanceof external_multiple_structure) {
1394 $type = 'array';
1395 }
1396
1397 return $type;
1398 }
1399
a0a07014
JM
1400 /**
1401 * Generate 'struct'/'object' type name
1402 * Some servers (our Zend ones) parse the phpdoc to know the parameter types.
1403 * The purpose to this function is to be overwritten when the common object|struct type are not understood by the server.
1404 * See webservice/soap/locallib.php - the SOAP server requires detailed structure)
1405 *
1406 * @param external_single_structure $structdesc the structure for which we generate the phpdoc type
1407 * @return string the phpdoc type
1408 */
cc7fb382
TH
1409 protected function generate_simple_struct_class(external_single_structure $structdesc) {
1410 return 'object|struct'; //only 'object' is supported by SOAP, 'struct' by XML-RPC MDL-23083
1411 }
1412
94a9b9e7
JP
1413 /**
1414 * You can override this function in your child class to add extra code into the dynamically
13ae7db2 1415 * created service class.
a0a07014
JM
1416 *
1417 * @param stdClass $function a record from external_function
1418 * @param array $params web service function parameters
94a9b9e7
JP
1419 * @return string body of the method for $function ie. everything within the {} of the method declaration.
1420 */
1421 protected function service_class_method_body($function, $params){
c06503b8 1422 //cast the param from object to array (validate_parameters except array only)
1423 $castingcode = '';
1424 if ($params){
7785dc2e 1425 $paramstocast = explode(',', $params);
c06503b8 1426 foreach ($paramstocast as $paramtocast) {
02998b3f 1427 //clean the parameter from any white space
c924a469 1428 $paramtocast = trim($paramtocast);
c06503b8 1429 $castingcode .= $paramtocast .
1430 '=webservice_zend_server::cast_objects_to_array('.$paramtocast.');';
1431 }
1432
1433 }
1434
d07ff72d 1435 $descriptionmethod = $function->methodname.'_returns()';
2d0acbd5 1436 $callforreturnvaluedesc = $function->classname.'::'.$descriptionmethod;
c06503b8 1437 return $castingcode . ' if ('.$callforreturnvaluedesc.' == null) {'.
1438 $function->classname.'::'.$function->methodname.'('.$params.');
99152d09 1439 return null;
1440 }
1441 return external_api::clean_returnvalue('.$callforreturnvaluedesc.', '.$function->classname.'::'.$function->methodname.'('.$params.'));';
94a9b9e7 1442 }
d07ff72d 1443
88098133 1444 /**
c06503b8 1445 * Recursive function to recurse down into a complex variable and convert all
1446 * objects to arrays.
a0a07014 1447 *
c06503b8 1448 * @param mixed $param value to cast
1449 * @return mixed Cast value
1450 */
1451 public static function cast_objects_to_array($param){
1452 if (is_object($param)){
1453 $param = (array)$param;
1454 }
1455 if (is_array($param)){
1456 $toreturn = array();
1457 foreach ($param as $key=> $param){
1458 $toreturn[$key] = self::cast_objects_to_array($param);
1459 }
1460 return $toreturn;
1461 } else {
1462 return $param;
1463 }
1464 }
1465
1466 /**
b107e647 1467 * Set up zend service class
88098133 1468 */
1469 protected function init_zend_server() {
88098133 1470 $this->zend_server = new $this->zend_class();
88098133 1471 }
1472
c187722c 1473 /**
dcd902a0 1474 * This method parses the $_POST and $_GET superglobals and looks for
c187722c
PS
1475 * the following information:
1476 * 1/ user authentication - username+password or token (wsusername, wspassword and wstoken parameters)
1477 *
1478 * @return void
1479 */
1480 protected function parse_request() {
dcd902a0 1481
93ce0e82
JM
1482 // We are going to clean the POST/GET parameters from the parameters specific to the server.
1483 parent::set_web_service_call_settings();
1484
1485 // Get GET and POST paramters.
dcd902a0
JM
1486 $methodvariables = array_merge($_GET,$_POST);
1487
2d0acbd5 1488 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
01482a4a 1489 //note: some clients have problems with entity encoding :-(
dcd902a0
JM
1490 if (isset($methodvariables['wsusername'])) {
1491 $this->username = $methodvariables['wsusername'];
c187722c 1492 }
dcd902a0
JM
1493 if (isset($methodvariables['wspassword'])) {
1494 $this->password = $methodvariables['wspassword'];
c187722c
PS
1495 }
1496 } else {
dcd902a0
JM
1497 if (isset($methodvariables['wstoken'])) {
1498 $this->token = $methodvariables['wstoken'];
88098133 1499 }
88098133 1500 }
88098133 1501 }
1502
ca6340bf 1503 /**
1504 * Internal implementation - sending of page headers.
ca6340bf 1505 */
1506 protected function send_headers() {
1507 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
1508 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1509 header('Pragma: no-cache');
1510 header('Accept-Ranges: none');
1511 }
1512
88098133 1513 /**
1514 * Specialised exception handler, we can not use the standard one because
1515 * it can not just print html to output.
1516 *
1517 * @param exception $ex
a0a07014 1518 * @uses exit
88098133 1519 */
1520 public function exception_handler($ex) {
88098133 1521 // detect active db transactions, rollback and log as error
3086dd59 1522 abort_all_db_transactions();
88098133 1523
88098133 1524 // some hacks might need a cleanup hook
1525 $this->session_cleanup($ex);
1526
ca6340bf 1527 // now let the plugin send the exception to client
b107e647 1528 $this->send_error($ex);
ca6340bf 1529
88098133 1530 // not much else we can do now, add some logging later
1531 exit(1);
1532 }
1533
b107e647
PS
1534 /**
1535 * Send the error information to the WS client
1536 * formatted as XML document.
a0a07014 1537 *
b107e647 1538 * @param exception $ex
b107e647
PS
1539 */
1540 protected function send_error($ex=null) {
1541 $this->send_headers();
1542 echo $this->zend_server->fault($ex);
1543 }
01482a4a 1544
88098133 1545 /**
1546 * Future hook needed for emulated sessions.
a0a07014 1547 *
88098133 1548 * @param exception $exception null means normal termination, $exception received when WS call failed
88098133 1549 */
1550 protected function session_cleanup($exception=null) {
2d0acbd5 1551 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
88098133 1552 // nothing needs to be done, there is no persistent session
1553 } else {
1554 // close emulated session if used
1555 }
1556 }
1557
cc93c7da 1558}
1559
886d7556 1560/**
a0a07014
JM
1561 * Web Service server base class.
1562 *
1563 * This class handles both simple and token authentication.
1564 *
1565 * @package core_webservice
1566 * @copyright 2009 Petr Skodak
1567 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
886d7556 1568 */
01482a4a 1569abstract class webservice_base_server extends webservice_server {
88098133 1570
a0a07014 1571 /** @var array The function parameters - the real values submitted in the request */
cc93c7da 1572 protected $parameters = null;
1573
a0a07014 1574 /** @var string The name of the function that is executed */
cc93c7da 1575 protected $functionname = null;
1576
a0a07014 1577 /** @var stdClass Full function description */
cc93c7da 1578 protected $function = null;
1579
a0a07014 1580 /** @var mixed Function return value */
cc93c7da 1581 protected $returns = null;
06e7fadc 1582
24350e06 1583 /**
cc93c7da 1584 * This method parses the request input, it needs to get:
1585 * 1/ user authentication - username+password or token
1586 * 2/ function name
1587 * 3/ function parameters
24350e06 1588 */
cc93c7da 1589 abstract protected function parse_request();
24350e06 1590
cc93c7da 1591 /**
1592 * Send the result of function call to the WS client.
cc93c7da 1593 */
1594 abstract protected function send_response();
24350e06 1595
fa0797ec 1596 /**
cc93c7da 1597 * Send the error information to the WS client.
a0a07014 1598 *
cc93c7da 1599 * @param exception $ex
fa0797ec 1600 */
cc93c7da 1601 abstract protected function send_error($ex=null);
fa0797ec 1602
cc93c7da 1603 /**
1604 * Process request from client.
a0a07014
JM
1605 *
1606 * @uses die
cc93c7da 1607 */
2458e30a 1608 public function run() {
cc93c7da 1609 // we will probably need a lot of memory in some functions
346c5887 1610 raise_memory_limit(MEMORY_EXTRA);
fa0797ec 1611
cc93c7da 1612 // set some longer timeout, this script is not sending any output,
1613 // this means we need to manually extend the timeout operations
1614 // that need longer time to finish
1615 external_api::set_timeout();
fa0797ec 1616
cc93c7da 1617 // set up exception handler first, we want to sent them back in correct format that
1618 // the other system understands
1619 // we do not need to call the original default handler because this ws handler does everything
1620 set_exception_handler(array($this, 'exception_handler'));
06e7fadc 1621
cc93c7da 1622 // init all properties from the request data
1623 $this->parse_request();
06e7fadc 1624
cc93c7da 1625 // authenticate user, this has to be done after the request parsing
1626 // this also sets up $USER and $SESSION
1627 $this->authenticate_user();
06e7fadc 1628
cc93c7da 1629 // find all needed function info and make sure user may actually execute the function
1630 $this->load_function_info();
c924a469 1631
d733a8cc
FM
1632 // Log the web service request.
1633 $params = array(
1634 'other' => array(
1635 'function' => $this->functionname
1636 )
1637 );
1638 $event = \core\event\webservice_function_called::create($params);
1639 $event->set_legacy_logdata(array(SITEID, 'webservice', $this->functionname, '' , getremoteaddr() , 0, $this->userid));
1640 $event->trigger();
f7631e73 1641
cc93c7da 1642 // finally, execute the function - any errors are catched by the default exception handler
1643 $this->execute();
06e7fadc 1644
cc93c7da 1645 // send the results back in correct format
1646 $this->send_response();
06e7fadc 1647
cc93c7da 1648 // session cleanup
1649 $this->session_cleanup();
06e7fadc 1650
cc93c7da 1651 die;
f7631e73 1652 }
1653
cc93c7da 1654 /**
1655 * Specialised exception handler, we can not use the standard one because
1656 * it can not just print html to output.
1657 *
1658 * @param exception $ex
a0a07014 1659 * $uses exit
cc93c7da 1660 */
1661 public function exception_handler($ex) {
cc93c7da 1662 // detect active db transactions, rollback and log as error
3086dd59 1663 abort_all_db_transactions();
06e7fadc 1664
cc93c7da 1665 // some hacks might need a cleanup hook
1666 $this->session_cleanup($ex);
06e7fadc 1667
ca6340bf 1668 // now let the plugin send the exception to client
1669 $this->send_error($ex);
1670
cc93c7da 1671 // not much else we can do now, add some logging later
1672 exit(1);
f7631e73 1673 }
1674
1675 /**
cc93c7da 1676 * Future hook needed for emulated sessions.
a0a07014 1677 *
cc93c7da 1678 * @param exception $exception null means normal termination, $exception received when WS call failed
f7631e73 1679 */
cc93c7da 1680 protected function session_cleanup($exception=null) {
ad8b5ba2 1681 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
cc93c7da 1682 // nothing needs to be done, there is no persistent session
1683 } else {
1684 // close emulated session if used
1685 }
f7631e73 1686 }
1687
24350e06 1688 /**
cc93c7da 1689 * Fetches the function description from database,
1690 * verifies user is allowed to use this function and
1691 * loads all paremeters and return descriptions.
24350e06 1692 */
cc93c7da 1693 protected function load_function_info() {
1694 global $DB, $USER, $CFG;
40f024c9 1695
cc93c7da 1696 if (empty($this->functionname)) {
1697 throw new invalid_parameter_exception('Missing function name');
1698 }
24350e06 1699
cc93c7da 1700 // function must exist
5593d2dc 1701 $function = external_function_info($this->functionname);
cc93c7da 1702
01482a4a
PS
1703 if ($this->restricted_serviceid) {
1704 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
1705 $wscond1 = 'AND s.id = :sid1';
1706 $wscond2 = 'AND s.id = :sid2';
1707 } else {
1708 $params = array();
1709 $wscond1 = '';
1710 $wscond2 = '';
1711 }
1712
cc93c7da 1713 // now let's verify access control
b8c5309e 1714
1715 // now make sure the function is listed in at least one service user is allowed to use
1716 // allow access only if:
1717 // 1/ entry in the external_services_users table if required
1718 // 2/ validuntil not reached
1719 // 3/ has capability if specified in service desc
1720 // 4/ iprestriction
1721
1722 $sql = "SELECT s.*, NULL AS iprestriction
1723 FROM {external_services} s
1724 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0 AND sf.functionname = :name1)
1725 WHERE s.enabled = 1 $wscond1
1726
1727 UNION
1728
1729 SELECT s.*, su.iprestriction
1730 FROM {external_services} s
1731 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1 AND sf.functionname = :name2)
1732 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
4c7f5363 1733 WHERE s.enabled = 1 AND (su.validuntil IS NULL OR su.validuntil < :now) $wscond2";
b8c5309e 1734 $params = array_merge($params, array('userid'=>$USER->id, 'name1'=>$function->name, 'name2'=>$function->name, 'now'=>time()));
88098133 1735
1736 $rs = $DB->get_recordset_sql($sql, $params);
1737 // now make sure user may access at least one service
1738 $remoteaddr = getremoteaddr();
1739 $allowed = false;
1740 foreach ($rs as $service) {
1741 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
1742 continue; // cap required, sorry
cc93c7da 1743 }
88098133 1744 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
1745 continue; // wrong request source ip, sorry
cc93c7da 1746 }
88098133 1747 $allowed = true;
1748 break; // one service is enough, no need to continue
1749 }
1750 $rs->close();
1751 if (!$allowed) {
96d3b93b
JM
1752 throw new webservice_access_exception(
1753 'Access to the function '.$this->functionname.'() is not allowed.
ca11d390
JM
1754 There could be multiple reasons for this:
1755 1. The service linked to the user token does not contain the function.
1756 2. The service is user-restricted and the user is not listed.
1757 3. The service is IP-restricted and the user IP is not listed.
1758 4. The service is time-restricted and the time has expired.
1759 5. The token is time-restricted and the time has expired.
1760 6. The service requires a specific capability which the user does not have.
1761 7. The function is called with username/password (no user token is sent)
1762 and none of the services has the function to allow the user.
1763 These settings can be found in Administration > Site administration
1764 > Plugins > Web services > External services and Manage tokens.');
cc93c7da 1765 }
9baf6825 1766
cc93c7da 1767 // we have all we need now
1768 $this->function = $function;
1769 }
1770
1771 /**
1772 * Execute previously loaded function using parameters parsed from the request data.
cc93c7da 1773 */
1774 protected function execute() {
1775 // validate params, this also sorts the params properly, we need the correct order in the next part
1776 $params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
9baf6825 1777
cc93c7da 1778 // execute - yay!
1779 $this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), array_values($params));
9baf6825 1780 }
1781}
1782
1783