MDL-20632 latest zend framework lib import
[moodle.git] / webservice / lib.php
CommitLineData
06e7fadc 1<?php
cc93c7da 2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
06e7fadc 18/**
cc93c7da 19 * Web services utility functions and classes
06e7fadc 20 *
06e7fadc 21 * @package webservice
551f4420 22 * @copyright 2009 Moodle Pty Ltd (http://moodle.com)
cc93c7da 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
5593d2dc 28/**
29 * Exception indicating access control problem in web service call
30 */
31class webservice_access_exception extends moodle_exception {
32 /**
33 * Constructor
34 */
35 function __construct($debuginfo) {
e8b21670 36 parent::__construct('accessexception', 'webservice', '', null, $debuginfo);
5593d2dc 37 }
38}
39
f0dafb3c 40/**
41 * Is protocol enabled?
42 * @param string $protocol name of WS protocol
43 * @return bool
44 */
cc93c7da 45function webservice_protocol_is_enabled($protocol) {
46 global $CFG;
893d7f0f 47
cc93c7da 48 if (empty($CFG->enablewebservices)) {
49 return false;
893d7f0f 50 }
51
cc93c7da 52 $active = explode(',', $CFG->webserviceprotocols);
893d7f0f 53
cc93c7da 54 return(in_array($protocol, $active));
55}
893d7f0f 56
cc93c7da 57/**
58 * Mandatory web service server interface
59 * @author Petr Skoda (skodak)
60 */
61interface webservice_server {
62 /**
63 * Process request from client.
cc93c7da 64 * @return void
65 */
2458e30a 66 public function run();
893d7f0f 67}
68
f0dafb3c 69/**
70 * Mandatory test client interface.
71 */
72interface webservice_test_client_interface {
73 /**
74 * Execute test client WS request
75 * @param string $serverurl
76 * @param string $function
77 * @param array $params
78 * @return mixed
79 */
80 public function simpletest($serverurl, $function, $params);
81}
82
06e7fadc 83/**
cc93c7da 84 * Special abstraction of our srvices that allows
85 * interaction with stock Zend ws servers.
86 * @author skodak
06e7fadc 87 */
cc93c7da 88abstract class webservice_zend_server implements webservice_server {
88098133 89
90 /** @property string name of the zend server class */
91 protected $zend_class;
92
93 /** @property object Zend server instance */
94 protected $zend_server;
95
96 /** @property string $wsname name of the web server plugin */
97 protected $wsname = null;
98
c187722c
PS
99 /** @property string $username name of local user */
100 protected $username = null;
101
102 /** @property string $password password of the local user */
103 protected $password = null;
104
88098133 105 /** @property bool $simple true if simple auth used */
106 protected $simple;
107
108 /** @property string $service_class virtual web service class with all functions user name execute, created on the fly */
109 protected $service_class;
110
111 /** @property object restricted context */
112 protected $restricted_context;
113
114 /**
115 * Contructor
2458e30a 116 * @param bool $simple use simple authentication
88098133 117 */
2458e30a
PS
118 public function __construct($simple, $zend_class) {
119 $this->simple = $simple;
88098133 120 $this->zend_class = $zend_class;
121 }
122
123 /**
124 * Process request from client.
125 * @param bool $simple use simple authentication
126 * @return void
127 */
2458e30a 128 public function run() {
88098133 129 // we will probably need a lot of memory in some functions
130 @raise_memory_limit('128M');
131
132 // set some longer timeout, this script is not sending any output,
133 // this means we need to manually extend the timeout operations
134 // that need longer time to finish
135 external_api::set_timeout();
136
e8b21670 137 // now create the instance of zend server
138 $this->init_zend_server();
139
88098133 140 // set up exception handler first, we want to sent them back in correct format that
141 // the other system understands
142 // we do not need to call the original default handler because this ws handler does everything
143 set_exception_handler(array($this, 'exception_handler'));
144
c187722c
PS
145 // init all properties from the request data
146 $this->parse_request();
147
88098133 148 // this sets up $USER and $SESSION and context restrictions
149 $this->authenticate_user();
150
151 // make a list of all functions user is allowed to excecute
152 $this->init_service_class();
153
2458e30a 154 // tell server what functions are available
88098133 155 $this->zend_server->setClass($this->service_class);
2458e30a
PS
156
157 // execute and return response, this sends some headers too
88098133 158 $response = $this->zend_server->handle();
2458e30a 159
5593d2dc 160/*
161 $grrr = ob_get_clean();
162 error_log($grrr);
163 error_log($response);
164*/
88098133 165 // session cleanup
166 $this->session_cleanup();
167
ca6340bf 168 //finally send the result
169 $this->send_headers();
88098133 170 echo $response;
171 die;
172 }
173
174 /**
175 * Load virtual class needed for Zend api
176 * @return void
177 */
178 protected function init_service_class() {
179 global $USER, $DB;
180
181 // first ofall get a complete list of services user is allowed to access
182 if ($this->simple) {
183 // now make sure the function is listed in at least one service user is allowed to use
184 // allow access only if:
185 // 1/ entry in the external_services_users table if required
186 // 2/ validuntil not reached
187 // 3/ has capability if specified in service desc
188 // 4/ iprestriction
189
190 $sql = "SELECT s.*, NULL AS iprestriction
191 FROM {external_services} s
192 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
193 WHERE s.enabled = 1
194
195 UNION
196
197 SELECT s.*, su.iprestriction
198 FROM {external_services} s
199 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
200 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
201 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now";
202 $params = array('userid'=>$USER->id, 'now'=>time());
203 } else {
204
205 //TODO: token may restrict access to one service only
206 die('not implemented yet');
207 }
208
209 $serviceids = array();
210 $rs = $DB->get_recordset_sql($sql, $params);
211
212 // now make sure user may access at least one service
213 $remoteaddr = getremoteaddr();
214 $allowed = false;
215 foreach ($rs as $service) {
216 if (isset($serviceids[$service->id])) {
217 continue;
218 }
219 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
220 continue; // cap required, sorry
221 }
222 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
223 continue; // wrong request source ip, sorry
224 }
225 $serviceids[$service->id] = $service->id;
226 }
227 $rs->close();
228
229 // now get the list of all functions
230 if ($serviceids) {
231 list($serviceids, $params) = $DB->get_in_or_equal($serviceids);
232 $sql = "SELECT f.*
233 FROM {external_functions} f
234 WHERE f.name IN (SELECT sf.functionname
235 FROM {external_services_functions} sf
236 WHERE sf.externalserviceid $serviceids)";
237 $functions = $DB->get_records_sql($sql, $params);
238 } else {
239 $functions = array();
240 }
241
242 // now make the virtual WS class with all the fuctions for this particular user
243 $methods = '';
244 foreach ($functions as $function) {
245 $methods .= $this->get_virtual_method_code($function);
246 }
247
5593d2dc 248 // let's use unique class name, there might be problem in unit tests
88098133 249 $classname = 'webservices_virtual_class_000000';
250 while(class_exists($classname)) {
251 $classname++;
252 }
253
254 $code = '
255/**
256 * Virtual class web services for user id '.$USER->id.' in context '.$this->restricted_context->id.'.
257 */
258class '.$classname.' {
259'.$methods.'
260}
261';
f0dafb3c 262
88098133 263 // load the virtual class definition into memory
264 eval($code);
88098133 265 $this->service_class = $classname;
266 }
267
268 /**
269 * returns virtual method code
270 * @param object $function
271 * @return string PHP code
272 */
273 protected function get_virtual_method_code($function) {
274 global $CFG;
275
5593d2dc 276 $function = external_function_info($function);
88098133 277
278 $params = array();
279 $params_desc = array();
453a7a85 280 foreach ($function->parameters_desc->keys as $name=>$keydesc) {
88098133 281 $params[] = '$'.$name;
453a7a85 282 $type = 'string';
283 if ($keydesc instanceof external_value) {
284 switch($keydesc->type) {
285 case PARAM_BOOL: // 0 or 1 only for now
286 case PARAM_INT:
287 $type = 'int'; break;
288 case PARAM_FLOAT;
289 $type = 'double'; break;
290 default:
291 $type = 'string';
292 }
293 } else if ($keydesc instanceof external_single_structure) {
294 $type = 'struct';
295 } else if ($keydesc instanceof external_multiple_structure) {
296 $type = 'array';
297 }
298 $params_desc[] = ' * @param '.$type.' $'.$name.' '.$keydesc->desc;
88098133 299 }
300 $params = implode(', ', $params);
301 $params_desc = implode("\n", $params_desc);
302
453a7a85 303 if (is_null($function->returns_desc)) {
304 $return = ' * @return void';
305 } else {
306 $type = 'string';
307 if ($function->returns_desc instanceof external_value) {
308 switch($function->returns_desc->type) {
309 case PARAM_BOOL: // 0 or 1 only for now
310 case PARAM_INT:
311 $type = 'int'; break;
312 case PARAM_FLOAT;
313 $type = 'double'; break;
314 default:
315 $type = 'string';
316 }
317 } else if ($function->returns_desc instanceof external_single_structure) {
318 $type = 'struct';
319 } else if ($function->returns_desc instanceof external_multiple_structure) {
320 $type = 'array';
321 }
322 $return = ' * @return '.$type.' '.$function->returns_desc->desc;
323 }
d4e764ab 324
88098133 325 // now crate a virtual method that calls the ext implemenation
326 // TODO: add PHP docs and all missing info here
327
328 $code = '
329 /**
5593d2dc 330 * '.$function->description.'
331 *
88098133 332'.$params_desc.'
453a7a85 333'.$return.'
88098133 334 */
335 public function '.$function->name.'('.$params.') {
336 return '.$function->classname.'::'.$function->methodname.'('.$params.');
337 }
338';
339 return $code;
340 }
341
342 /**
343 * Set up zend serice class
344 * @return void
345 */
346 protected function init_zend_server() {
88098133 347 $this->zend_server = new $this->zend_class();
88098133 348 }
349
c187722c
PS
350 /**
351 * This method parses the $_REQUEST superglobal and looks for
352 * the following information:
353 * 1/ user authentication - username+password or token (wsusername, wspassword and wstoken parameters)
354 *
355 * @return void
356 */
357 protected function parse_request() {
358 if ($this->simple) {
359 //note: some clients have problems with entity encoding, this is a horrible hack that solves this
360 if (isset($_REQUEST['wsusername'])) {
361 $this->username = $_REQUEST['wsusername'];
362 } else {
363 $this->username = null;
364 }
365 if (isset($_REQUEST['wspassword'])) {
366 $this->password = $_REQUEST['wspassword'];
367 } else {
368 $this->password = null;
369 }
370 } else {
371 //TODO
372 die('not implemented yet');
373 }
374 }
375
88098133 376 /**
377 * Authenticate user using username+password or token.
378 * This function sets up $USER global.
379 * It is safe to use has_capability() after this.
380 * This method also verifies user is allowed to use this
381 * server.
382 * @return void
383 */
384 protected function authenticate_user() {
385 global $CFG, $DB;
386
387 if (!NO_MOODLE_COOKIES) {
388 throw new coding_exception('Cookies must be disabled in WS servers!');
389 }
390
391 if ($this->simple) {
392 $this->restricted_context = get_context_instance(CONTEXT_SYSTEM);
393
394 if (!is_enabled_auth('webservice')) {
5593d2dc 395 throw new webservice_access_exception('WS auth not enabled');
88098133 396 }
397
398 if (!$auth = get_auth_plugin('webservice')) {
5593d2dc 399 throw new webservice_access_exception('WS auth missing');
88098133 400 }
401
402 // the username is hardcoded as URL parameter because we can not easily parse the request data :-(
c187722c 403 if (!$this->username) {
5593d2dc 404 throw new webservice_access_exception('Missing username');
88098133 405 }
406
407 // the password is hardcoded as URL parameter because we can not easily parse the request data :-(
c187722c 408 if (!$this->password) {
5593d2dc 409 throw new webservice_access_exception('Missing password');
88098133 410 }
411
c187722c 412 if (!$auth->user_login_webservice($this->username, $this->password)) {
5593d2dc 413 throw new webservice_access_exception('Wrong username or password');
88098133 414 }
415
c187722c 416 $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0), '*', MUST_EXIST);
88098133 417
418 // now fake user login, the session is completely empty too
419 session_set_user($user);
420
421 } else {
422
423 //TODO: not implemented yet
424 die('token login not implemented yet');
425 //TODO: $this->restricted_context is derived from the token context
426 }
427
428 if (!has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
5593d2dc 429 throw new webservice_access_exception('Access to web service not allowed');
88098133 430 }
431
432 external_api::set_context_restriction($this->restricted_context);
433 }
434
ca6340bf 435 /**
436 * Internal implementation - sending of page headers.
437 * @return void
438 */
439 protected function send_headers() {
440 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
441 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
442 header('Pragma: no-cache');
443 header('Accept-Ranges: none');
444 }
445
88098133 446 /**
447 * Specialised exception handler, we can not use the standard one because
448 * it can not just print html to output.
449 *
450 * @param exception $ex
451 * @return void does not return
452 */
453 public function exception_handler($ex) {
454 global $CFG, $DB, $SCRIPT;
455
456 // detect active db transactions, rollback and log as error
457 if ($DB->is_transaction_started()) {
458 error_log('Database transaction aborted by exception in ' . $CFG->dirroot . $SCRIPT);
459 try {
460 // note: transaction blocks should never change current $_SESSION
461 $DB->rollback_sql();
462 } catch (Exception $ignored) {
463 }
464 }
465
88098133 466 // some hacks might need a cleanup hook
467 $this->session_cleanup($ex);
468
ca6340bf 469 // now let the plugin send the exception to client
470 $this->send_headers();
471 echo $this->zend_server->fault($ex);
472
88098133 473 // not much else we can do now, add some logging later
474 exit(1);
475 }
476
477 /**
478 * Future hook needed for emulated sessions.
479 * @param exception $exception null means normal termination, $exception received when WS call failed
480 * @return void
481 */
482 protected function session_cleanup($exception=null) {
483 if ($this->simple) {
484 // nothing needs to be done, there is no persistent session
485 } else {
486 // close emulated session if used
487 }
488 }
489
cc93c7da 490}
491
06e7fadc 492
886d7556 493/**
cc93c7da 494 * Web Service server base class, this class handles both
495 * simple and token authentication.
496 * @author Petr Skoda (skodak)
886d7556 497 */
cc93c7da 498abstract class webservice_base_server implements webservice_server {
499
500 /** @property string $wsname name of the web server plugin */
501 protected $wsname = null;
502
503 /** @property bool $simple true if simple auth used */
504 protected $simple;
505
506 /** @property string $username name of local user */
507 protected $username = null;
508
509 /** @property string $password password of the local user */
510 protected $password = null;
511
512 /** @property string $token authentication token*/
513 protected $token = null;
514
88098133 515 /** @property object restricted context */
516 protected $restricted_context;
517
cc93c7da 518 /** @property array $parameters the function parameters - the real values submitted in the request */
519 protected $parameters = null;
520
521 /** @property string $functionname the name of the function that is executed */
522 protected $functionname = null;
523
524 /** @property object $function full function description */
525 protected $function = null;
526
527 /** @property mixed $returns function return value */
528 protected $returns = null;
06e7fadc 529
530 /**
cc93c7da 531 * Contructor
2458e30a 532 * @param bool $simple use simple authentication
06e7fadc 533 */
2458e30a
PS
534 public function __construct($simple) {
535 $this->simple = $simple;
06e7fadc 536 }
537
24350e06 538 /**
cc93c7da 539 * This method parses the request input, it needs to get:
540 * 1/ user authentication - username+password or token
541 * 2/ function name
542 * 3/ function parameters
543 *
544 * @return void
24350e06 545 */
cc93c7da 546 abstract protected function parse_request();
24350e06 547
cc93c7da 548 /**
549 * Send the result of function call to the WS client.
550 * @return void
551 */
552 abstract protected function send_response();
24350e06 553
fa0797ec 554 /**
cc93c7da 555 * Send the error information to the WS client.
556 * @param exception $ex
557 * @return void
fa0797ec 558 */
cc93c7da 559 abstract protected function send_error($ex=null);
fa0797ec 560
cc93c7da 561 /**
562 * Process request from client.
cc93c7da 563 * @return void
564 */
2458e30a 565 public function run() {
cc93c7da 566 // we will probably need a lot of memory in some functions
567 @raise_memory_limit('128M');
fa0797ec 568
cc93c7da 569 // set some longer timeout, this script is not sending any output,
570 // this means we need to manually extend the timeout operations
571 // that need longer time to finish
572 external_api::set_timeout();
fa0797ec 573
cc93c7da 574 // set up exception handler first, we want to sent them back in correct format that
575 // the other system understands
576 // we do not need to call the original default handler because this ws handler does everything
577 set_exception_handler(array($this, 'exception_handler'));
06e7fadc 578
cc93c7da 579 // init all properties from the request data
580 $this->parse_request();
06e7fadc 581
cc93c7da 582 // authenticate user, this has to be done after the request parsing
583 // this also sets up $USER and $SESSION
584 $this->authenticate_user();
06e7fadc 585
cc93c7da 586 // find all needed function info and make sure user may actually execute the function
587 $this->load_function_info();
f7631e73 588
cc93c7da 589 // finally, execute the function - any errors are catched by the default exception handler
590 $this->execute();
06e7fadc 591
cc93c7da 592 // send the results back in correct format
593 $this->send_response();
06e7fadc 594
cc93c7da 595 // session cleanup
596 $this->session_cleanup();
06e7fadc 597
cc93c7da 598 die;
f7631e73 599 }
600
cc93c7da 601 /**
602 * Specialised exception handler, we can not use the standard one because
603 * it can not just print html to output.
604 *
605 * @param exception $ex
606 * @return void does not return
607 */
608 public function exception_handler($ex) {
609 global $CFG, $DB, $SCRIPT;
610
611 // detect active db transactions, rollback and log as error
612 if ($DB->is_transaction_started()) {
613 error_log('Database transaction aborted by exception in ' . $CFG->dirroot . $SCRIPT);
614 try {
615 // note: transaction blocks should never change current $_SESSION
616 $DB->rollback_sql();
617 } catch (Exception $ignored) {
618 }
619 }
06e7fadc 620
cc93c7da 621 // some hacks might need a cleanup hook
622 $this->session_cleanup($ex);
06e7fadc 623
ca6340bf 624 // now let the plugin send the exception to client
625 $this->send_error($ex);
626
cc93c7da 627 // not much else we can do now, add some logging later
628 exit(1);
f7631e73 629 }
630
631 /**
cc93c7da 632 * Future hook needed for emulated sessions.
633 * @param exception $exception null means normal termination, $exception received when WS call failed
634 * @return void
f7631e73 635 */
cc93c7da 636 protected function session_cleanup($exception=null) {
637 if ($this->simple) {
638 // nothing needs to be done, there is no persistent session
639 } else {
640 // close emulated session if used
641 }
f7631e73 642 }
643
cc93c7da 644 /**
645 * Authenticate user using username+password or token.
646 * This function sets up $USER global.
647 * It is safe to use has_capability() after this.
648 * This method also verifies user is allowed to use this
649 * server.
650 * @return void
651 */
652 protected function authenticate_user() {
653 global $CFG, $DB;
06e7fadc 654
cc93c7da 655 if (!NO_MOODLE_COOKIES) {
656 throw new coding_exception('Cookies must be disabled in WS servers!');
657 }
658
659 if ($this->simple) {
88098133 660 $this->restricted_context = get_context_instance(CONTEXT_SYSTEM);
661
cc93c7da 662 if (!is_enabled_auth('webservice')) {
5593d2dc 663 throw new webservice_access_exception('WS auth not enabled');
cc93c7da 664 }
665
666 if (!$auth = get_auth_plugin('webservice')) {
5593d2dc 667 throw new webservice_access_exception('WS auth missing');
cc93c7da 668 }
669
670 if (!$this->username) {
5593d2dc 671 throw new webservice_access_exception('Missing username');
cc93c7da 672 }
673
674 if (!$this->password) {
5593d2dc 675 throw new webservice_access_exception('Missing password');
cc93c7da 676 }
677
678 if (!$auth->user_login_webservice($this->username, $this->password)) {
5593d2dc 679 throw new webservice_access_exception('Wrong username or password');
cc93c7da 680 }
681
682 $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0), '*', MUST_EXIST);
683
684 // now fake user login, the session is completely empty too
685 session_set_user($user);
06e7fadc 686 } else {
88098133 687
cc93c7da 688 //TODO: not implemented yet
689 die('token login not implemented yet');
88098133 690 //TODO: $this->restricted_context is derived from the token context
691 }
cc93c7da 692
88098133 693 if (!has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
5593d2dc 694 throw new webservice_access_exception('Access to web service not allowed');
886d7556 695 }
88098133 696
697 external_api::set_context_restriction($this->restricted_context);
06e7fadc 698 }
24350e06 699
700 /**
cc93c7da 701 * Fetches the function description from database,
702 * verifies user is allowed to use this function and
703 * loads all paremeters and return descriptions.
704 * @return void
24350e06 705 */
cc93c7da 706 protected function load_function_info() {
707 global $DB, $USER, $CFG;
40f024c9 708
cc93c7da 709 if (empty($this->functionname)) {
710 throw new invalid_parameter_exception('Missing function name');
711 }
24350e06 712
cc93c7da 713 // function must exist
5593d2dc 714 $function = external_function_info($this->functionname);
cc93c7da 715
716 // now let's verify access control
717 if ($this->simple) {
718 // now make sure the function is listed in at least one service user is allowed to use
719 // allow access only if:
720 // 1/ entry in the external_services_users table if required
721 // 2/ validuntil not reached
722 // 3/ has capability if specified in service desc
723 // 4/ iprestriction
724
725 $sql = "SELECT s.*, NULL AS iprestriction
726 FROM {external_services} s
727 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0 AND sf.functionname = :name1)
728 WHERE s.enabled = 1
729
730 UNION
731
732 SELECT s.*, su.iprestriction
733 FROM {external_services} s
734 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1 AND sf.functionname = :name2)
735 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
736 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now";
88098133 737 $params = array('userid'=>$USER->id, 'name1'=>$function->name, 'name2'=>$function->name, 'now'=>time());
738 } else {
cc93c7da 739
88098133 740 //TODO: token may restrict access to one service only
741 die('not implemented yet');
742 }
743
744 $rs = $DB->get_recordset_sql($sql, $params);
745 // now make sure user may access at least one service
746 $remoteaddr = getremoteaddr();
747 $allowed = false;
748 foreach ($rs as $service) {
749 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
750 continue; // cap required, sorry
cc93c7da 751 }
88098133 752 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
753 continue; // wrong request source ip, sorry
cc93c7da 754 }
88098133 755 $allowed = true;
756 break; // one service is enough, no need to continue
757 }
758 $rs->close();
759 if (!$allowed) {
c91cc5ef 760 throw new webservice_access_exception('Access to external function not allowed');
cc93c7da 761 }
9baf6825 762
cc93c7da 763 // we have all we need now
764 $this->function = $function;
765 }
766
767 /**
768 * Execute previously loaded function using parameters parsed from the request data.
769 * @return void
770 */
771 protected function execute() {
772 // validate params, this also sorts the params properly, we need the correct order in the next part
773 $params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
9baf6825 774
cc93c7da 775 // execute - yay!
776 $this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), array_values($params));
9baf6825 777 }
778}
779
780