weekly release 2.2dev
[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
2d0acbd5
JP
28define('WEBSERVICE_AUTHMETHOD_USERNAME', 0);
29define('WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN', 1);
30define('WEBSERVICE_AUTHMETHOD_SESSION_TOKEN', 2);
31
229a7099 32/**
33 * General web service library
34 */
35class webservice {
36
86dcc6f0 37 /**
38 * Add a user to the list of authorised user of a given service
39 * @param object $user
40 */
41 public function add_ws_authorised_user($user) {
42 global $DB;
fc0fcb27 43 $user->timecreated = mktime();
86dcc6f0 44 $DB->insert_record('external_services_users', $user);
45 }
46
47 /**
48 * Remove a user from a list of allowed user of a service
49 * @param object $user
50 * @param int $serviceid
51 */
52 public function remove_ws_authorised_user($user, $serviceid) {
53 global $DB;
54 $DB->delete_records('external_services_users',
55 array('externalserviceid' => $serviceid, 'userid' => $user->id));
56 }
57
58 /**
59 * Update service allowed user settings
60 * @param object $user
61 */
62 public function update_ws_authorised_user($user) {
63 global $DB;
64 $DB->update_record('external_services_users', $user);
65 }
66
67 /**
68 * Return list of allowed users with their options (ip/timecreated / validuntil...)
69 * for a given service
70 * @param int $serviceid
71 * @return array $users
72 */
73 public function get_ws_authorised_users($serviceid) {
c924a469
PS
74 global $DB, $CFG;
75 $params = array($CFG->siteguest, $serviceid);
86dcc6f0 76 $sql = " SELECT u.id as id, esu.id as serviceuserid, u.email as email, u.firstname as firstname,
c924a469
PS
77 u.lastname as lastname,
78 esu.iprestriction as iprestriction, esu.validuntil as validuntil,
79 esu.timecreated as timecreated
80 FROM {user} u, {external_services_users} esu
81 WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
86dcc6f0 82 AND esu.userid = u.id
83 AND esu.externalserviceid = ?";
86dcc6f0 84
85 $users = $DB->get_records_sql($sql, $params);
86 return $users;
87 }
88
89 /**
90 * Return a authorised user with his options (ip/timecreated / validuntil...)
91 * @param int $serviceid
92 * @param int $userid
93 * @return object
94 */
95 public function get_ws_authorised_user($serviceid, $userid) {
c924a469
PS
96 global $DB, $CFG;
97 $params = array($CFG->siteguest, $serviceid, $userid);
86dcc6f0 98 $sql = " SELECT u.id as id, esu.id as serviceuserid, u.email as email, u.firstname as firstname,
c924a469
PS
99 u.lastname as lastname,
100 esu.iprestriction as iprestriction, esu.validuntil as validuntil,
101 esu.timecreated as timecreated
102 FROM {user} u, {external_services_users} esu
103 WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
86dcc6f0 104 AND esu.userid = u.id
105 AND esu.externalserviceid = ?
106 AND u.id = ?";
107 $user = $DB->get_record_sql($sql, $params);
108 return $user;
109 }
110
229a7099 111 /**
112 * Generate all ws token needed by a user
113 * @param int $userid
114 */
115 public function generate_user_ws_tokens($userid) {
116 global $CFG, $DB;
c924a469 117
229a7099 118 /// generate a token for non admin if web service are enable and the user has the capability to create a token
119 if (!is_siteadmin() && has_capability('moodle/webservice:createtoken', get_context_instance(CONTEXT_SYSTEM), $userid) && !empty($CFG->enablewebservices)) {
120 /// for every service than the user is authorised on, create a token (if it doesn't already exist)
121
122 ///get all services which are set to all user (no restricted to specific users)
123 $norestrictedservices = $DB->get_records('external_services', array('restrictedusers' => 0));
124 $serviceidlist = array();
125 foreach ($norestrictedservices as $service) {
126 $serviceidlist[] = $service->id;
127 }
128
129 //get all services which are set to the current user (the current user is specified in the restricted user list)
130 $servicesusers = $DB->get_records('external_services_users', array('userid' => $userid));
131 foreach ($servicesusers as $serviceuser) {
132 if (!in_array($serviceuser->externalserviceid,$serviceidlist)) {
133 $serviceidlist[] = $serviceuser->externalserviceid;
134 }
135 }
136
137 //get all services which already have a token set for the current user
138 $usertokens = $DB->get_records('external_tokens', array('userid' => $userid, 'tokentype' => EXTERNAL_TOKEN_PERMANENT));
139 $tokenizedservice = array();
140 foreach ($usertokens as $token) {
141 $tokenizedservice[] = $token->externalserviceid;
142 }
143
144 //create a token for the service which have no token already
145 foreach ($serviceidlist as $serviceid) {
146 if (!in_array($serviceid, $tokenizedservice)) {
147 //create the token for this service
7a424cc4 148 $newtoken = new stdClass();
229a7099 149 $newtoken->token = md5(uniqid(rand(),1));
150 //check that the user has capability on this service
151 $newtoken->tokentype = EXTERNAL_TOKEN_PERMANENT;
152 $newtoken->userid = $userid;
153 $newtoken->externalserviceid = $serviceid;
154 //TODO: find a way to get the context - UPDATE FOLLOWING LINE
155 $newtoken->contextid = get_context_instance(CONTEXT_SYSTEM)->id;
156 $newtoken->creatorid = $userid;
157 $newtoken->timecreated = time();
158
159 $DB->insert_record('external_tokens', $newtoken);
160 }
161 }
162
163
164 }
165 }
166
167 /**
168 * Return all ws user token
169 * @param integer $userid
170 * @return array of token
171 */
172 public function get_user_ws_tokens($userid) {
173 global $DB;
174 //here retrieve token list (including linked users firstname/lastname and linked services name)
175 $sql = "SELECT
176 t.id, t.creatorid, t.token, u.firstname, u.lastname, s.name, t.validuntil
177 FROM
178 {external_tokens} t, {user} u, {external_services} s
179 WHERE
180 t.userid=? AND t.tokentype = ".EXTERNAL_TOKEN_PERMANENT." AND s.id = t.externalserviceid AND t.userid = u.id";
181 $tokens = $DB->get_records_sql($sql, array( $userid));
182 return $tokens;
183 }
184
185 /**
186 * Return a user token that has been created by the user
187 * If doesn't exist a exception is thrown
188 * @param integer $userid
189 * @param integer $tokenid
9c954e88 190 * @return object token
191 * ->id token id
192 * ->token
193 * ->firstname user firstname
194 * ->lastname
195 * ->name service name
229a7099 196 */
197 public function get_created_by_user_ws_token($userid, $tokenid) {
198 global $DB;
199 $sql = "SELECT
200 t.id, t.token, u.firstname, u.lastname, s.name
201 FROM
202 {external_tokens} t, {user} u, {external_services} s
203 WHERE
9c954e88 204 t.creatorid=? AND t.id=? AND t.tokentype = "
205 . EXTERNAL_TOKEN_PERMANENT
206 . " AND s.id = t.externalserviceid AND t.userid = u.id";
207 //must be the token creator
208 $token = $DB->get_record_sql($sql, array($userid, $tokenid), MUST_EXIST);
229a7099 209 return $token;
229a7099 210 }
211
9ef728d6 212 /**
213 * Return a token for a given id
214 * @param integer $tokenid
215 * @return object token
216 */
217 public function get_token_by_id($tokenid) {
218 global $DB;
219 return $DB->get_record('external_tokens', array('id' => $tokenid));
220 }
221
229a7099 222 /**
223 * Delete a user token
224 * @param int $tokenid
225 */
226 public function delete_user_ws_token($tokenid) {
227 global $DB;
228 $DB->delete_records('external_tokens', array('id'=>$tokenid));
229 }
bb98a5b2 230
c25662b0 231 /**
232 * Delete a service - it also delete the functions and users references to this service
233 * @param int $serviceid
234 */
235 public function delete_service($serviceid) {
236 global $DB;
237 $DB->delete_records('external_services_users', array('externalserviceid' => $serviceid));
238 $DB->delete_records('external_services_functions', array('externalserviceid' => $serviceid));
239 $DB->delete_records('external_tokens', array('externalserviceid' => $serviceid));
fc0fcb27 240 $DB->delete_records('external_services', array('id' => $serviceid));
c25662b0 241 }
242
bb98a5b2 243 /**
244 * Get a user token by token
245 * @param string $token
246 * @throws moodle_exception if there is multiple result
247 */
248 public function get_user_ws_token($token) {
249 global $DB;
250 return $DB->get_record('external_tokens', array('token'=>$token), '*', MUST_EXIST);
251 }
229a7099 252
72f68b51 253 /**
254 * Get the list of all functions for given service ids
255 * @param array $serviceids
256 * @return array functions
257 */
258 public function get_external_functions($serviceids) {
259 global $DB;
260 if (!empty($serviceids)) {
261 list($serviceids, $params) = $DB->get_in_or_equal($serviceids);
262 $sql = "SELECT f.*
263 FROM {external_functions} f
264 WHERE f.name IN (SELECT sf.functionname
265 FROM {external_services_functions} sf
266 WHERE sf.externalserviceid $serviceids)";
267 $functions = $DB->get_records_sql($sql, $params);
268 } else {
269 $functions = array();
270 }
271 return $functions;
272 }
273
0bf486a6
JM
274 /**
275 * Get the list of all functions for given service shortnames
276 * @param array $serviceshortnames
277 * @param $enabledonly if true then only return function for the service that has been enabled
278 * @return array functions
279 */
280 public function get_external_functions_by_enabled_services($serviceshortnames, $enabledonly = true) {
281 global $DB;
282 if (!empty($serviceshortnames)) {
283 $enabledonlysql = $enabledonly?' AND s.enabled = 1 ':'';
284 list($serviceshortnames, $params) = $DB->get_in_or_equal($serviceshortnames);
285 $sql = "SELECT f.*
286 FROM {external_functions} f
287 WHERE f.name IN (SELECT sf.functionname
288 FROM {external_services_functions} sf, {external_services} s
289 WHERE s.shortname $serviceshortnames
290 AND sf.externalserviceid = s.id
291 " . $enabledonlysql . ")";
292 $functions = $DB->get_records_sql($sql, $params);
293 } else {
294 $functions = array();
295 }
296 return $functions;
72f68b51 297 }
298
9c954e88 299 /**
300 * Get the list of all functions not in the given service id
301 * @param int $serviceid
302 * @return array functions
303 */
304 public function get_not_associated_external_functions($serviceid) {
305 global $DB;
306 $select = "name NOT IN (SELECT s.functionname
307 FROM {external_services_functions} s
308 WHERE s.externalserviceid = :sid
309 )";
310
311 $functions = $DB->get_records_select('external_functions',
312 $select, array('sid' => $serviceid), 'name');
313
314 return $functions;
315 }
316
72f68b51 317 /**
318 * Get list of required capabilities of a service, sorted by functions
319 * @param integer $serviceid
320 * @return array
321 * example of return value:
322 * Array
323 * (
324 * [moodle_group_create_groups] => Array
325 * (
326 * [0] => moodle/course:managegroups
327 * )
328 *
329 * [moodle_enrol_get_enrolled_users] => Array
330 * (
331 * [0] => moodle/site:viewparticipants
332 * [1] => moodle/course:viewparticipants
333 * [2] => moodle/role:review
334 * [3] => moodle/site:accessallgroups
335 * [4] => moodle/course:enrolreview
336 * )
337 * )
338 */
339 public function get_service_required_capabilities($serviceid) {
340 $functions = $this->get_external_functions(array($serviceid));
341 $requiredusercaps = array();
342 foreach ($functions as $function) {
8819c47b 343 $functioncaps = explode(',', $function->capabilities);
72f68b51 344 if (!empty($functioncaps) and !empty($functioncaps[0])) {
345 foreach ($functioncaps as $functioncap) {
346 $requiredusercaps[$function->name][] = trim($functioncap);
347 }
348 }
349 }
350 return $requiredusercaps;
351 }
352
353 /**
354 * Get user capabilities (with context)
355 * Only usefull for documentation purpose
356 * @param integer $userid
357 * @return array
358 */
359 public function get_user_capabilities($userid) {
360 global $DB;
361 //retrieve the user capabilities
362 $sql = "SELECT rc.id, rc.capability FROM {role_capabilities} rc, {role_assignments} ra
b2cde2cb 363 WHERE rc.roleid=ra.roleid AND ra.userid= ?";
72f68b51 364 $dbusercaps = $DB->get_records_sql($sql, array($userid));
365 $usercaps = array();
366 foreach ($dbusercaps as $usercap) {
367 $usercaps[$usercap->capability] = true;
368 }
369 return $usercaps;
370 }
371
372 /**
373 * Get users missing capabilities for a given service
374 * @param array $users
375 * @param integer $serviceid
376 * @return array of missing capabilities, the key being the user id
377 */
378 public function get_missing_capabilities_by_users($users, $serviceid) {
379 global $DB;
380 $usersmissingcaps = array();
381
382 //retrieve capabilities required by the service
383 $servicecaps = $this->get_service_required_capabilities($serviceid);
384
385 //retrieve users missing capabilities
386 foreach ($users as $user) {
387 //cast user array into object to be a bit more flexible
388 if (is_array($user)) {
389 $user = (object) $user;
390 }
391 $usercaps = $this->get_user_capabilities($user->id);
392
393 //detect the missing capabilities
394 foreach ($servicecaps as $functioname => $functioncaps) {
395 foreach ($functioncaps as $functioncap) {
396 if (!key_exists($functioncap, $usercaps)) {
397 if (!isset($usersmissingcaps[$user->id])
398 or array_search($functioncap, $usersmissingcaps[$user->id]) === false) {
399 $usersmissingcaps[$user->id][] = $functioncap;
400 }
401 }
402 }
403 }
404 }
405
406 return $usersmissingcaps;
407 }
408
c25662b0 409 /**
410 * Get a external service for a given id
411 * @param service id $serviceid
412 * @param integer $strictness IGNORE_MISSING, MUST_EXIST...
413 * @return object external service
414 */
415 public function get_external_service_by_id($serviceid, $strictness=IGNORE_MISSING) {
416 global $DB;
417 $service = $DB->get_record('external_services',
418 array('id' => $serviceid), '*', $strictness);
419 return $service;
420 }
421
c1b65883
JM
422 /**
423 * Get a external service for a given shortname
424 * @param service shortname $shortname
425 * @param integer $strictness IGNORE_MISSING, MUST_EXIST...
426 * @return object external service
427 */
428 public function get_external_service_by_shortname($shortname, $strictness=IGNORE_MISSING) {
429 global $DB;
430 $service = $DB->get_record('external_services',
431 array('shortname' => $shortname), '*', $strictness);
432 return $service;
433 }
434
72f68b51 435 /**
436 * Get a external function for a given id
437 * @param function id $functionid
438 * @param integer $strictness IGNORE_MISSING, MUST_EXIST...
439 * @return object external function
440 */
441 public function get_external_function_by_id($functionid, $strictness=IGNORE_MISSING) {
442 global $DB;
443 $function = $DB->get_record('external_functions',
444 array('id' => $functionid), '*', $strictness);
445 return $function;
446 }
447
448 /**
449 * Add a function to a service
450 * @param string $functionname
451 * @param integer $serviceid
452 */
453 public function add_external_function_to_service($functionname, $serviceid) {
454 global $DB;
7a424cc4 455 $addedfunction = new stdClass();
72f68b51 456 $addedfunction->externalserviceid = $serviceid;
457 $addedfunction->functionname = $functionname;
458 $DB->insert_record('external_services_functions', $addedfunction);
459 }
460
c25662b0 461 /**
462 * Add a service
463 * @param object $service
464 * @return serviceid integer
465 */
466 public function add_external_service($service) {
467 global $DB;
468 $service->timecreated = mktime();
469 $serviceid = $DB->insert_record('external_services', $service);
470 return $serviceid;
471 }
472
473 /**
474 * Update a service
475 * @param object $service
476 */
477 public function update_external_service($service) {
478 global $DB;
479 $service->timemodified = mktime();
480 $DB->update_record('external_services', $service);
481 }
482
72f68b51 483 /**
484 * Test whether a external function is already linked to a service
485 * @param string $functionname
486 * @param integer $serviceid
487 * @return bool true if a matching function exists for the service, else false.
488 * @throws dml_exception if error
489 */
490 public function service_function_exists($functionname, $serviceid) {
491 global $DB;
492 return $DB->record_exists('external_services_functions',
493 array('externalserviceid' => $serviceid,
494 'functionname' => $functionname));
495 }
496
497 public function remove_external_function_from_service($functionname, $serviceid) {
498 global $DB;
499 $DB->delete_records('external_services_functions',
500 array('externalserviceid' => $serviceid, 'functionname' => $functionname));
501
502 }
503
504
505}
229a7099 506
5593d2dc 507/**
508 * Exception indicating access control problem in web service call
01482a4a 509 * @author Petr Skoda (skodak)
5593d2dc 510 */
511class webservice_access_exception extends moodle_exception {
512 /**
513 * Constructor
514 */
515 function __construct($debuginfo) {
e8b21670 516 parent::__construct('accessexception', 'webservice', '', null, $debuginfo);
5593d2dc 517 }
518}
519
f0dafb3c 520/**
521 * Is protocol enabled?
522 * @param string $protocol name of WS protocol
523 * @return bool
524 */
cc93c7da 525function webservice_protocol_is_enabled($protocol) {
526 global $CFG;
893d7f0f 527
cc93c7da 528 if (empty($CFG->enablewebservices)) {
529 return false;
893d7f0f 530 }
531
cc93c7da 532 $active = explode(',', $CFG->webserviceprotocols);
893d7f0f 533
cc93c7da 534 return(in_array($protocol, $active));
535}
893d7f0f 536
01482a4a 537//=== WS classes ===
893d7f0f 538
f0dafb3c 539/**
3f599588 540 * Mandatory interface for all test client classes.
01482a4a 541 * @author Petr Skoda (skodak)
f0dafb3c 542 */
543interface webservice_test_client_interface {
544 /**
545 * Execute test client WS request
546 * @param string $serverurl
547 * @param string $function
548 * @param array $params
549 * @return mixed
550 */
551 public function simpletest($serverurl, $function, $params);
552}
553
06e7fadc 554/**
3f599588 555 * Mandatory interface for all web service protocol classes
01482a4a 556 * @author Petr Skoda (skodak)
06e7fadc 557 */
01482a4a
PS
558interface webservice_server_interface {
559 /**
560 * Process request from client.
561 * @return void
562 */
563 public function run();
564}
88098133 565
01482a4a
PS
566/**
567 * Abstract web service base class.
568 * @author Petr Skoda (skodak)
569 */
570abstract class webservice_server implements webservice_server_interface {
88098133 571
572 /** @property string $wsname name of the web server plugin */
573 protected $wsname = null;
574
c187722c
PS
575 /** @property string $username name of local user */
576 protected $username = null;
577
578 /** @property string $password password of the local user */
579 protected $password = null;
b107e647 580
c3517f05 581 /** @property int $userid the local user */
582 protected $userid = null;
583
2d0acbd5
JP
584 /** @property integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_* */
585 protected $authmethod;
88098133 586
01482a4a
PS
587 /** @property string $token authentication token*/
588 protected $token = null;
88098133 589
590 /** @property object restricted context */
591 protected $restricted_context;
592
01482a4a
PS
593 /** @property int restrict call to one service id*/
594 protected $restricted_serviceid = null;
595
2d0acbd5
JP
596 /**
597 * Contructor
c924a469 598 * @param integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_*
2d0acbd5
JP
599 */
600 public function __construct($authmethod) {
601 $this->authmethod = $authmethod;
c924a469
PS
602 }
603
604
01482a4a
PS
605 /**
606 * Authenticate user using username+password or token.
607 * This function sets up $USER global.
608 * It is safe to use has_capability() after this.
609 * This method also verifies user is allowed to use this
610 * server.
611 * @return void
612 */
613 protected function authenticate_user() {
614 global $CFG, $DB;
615
616 if (!NO_MOODLE_COOKIES) {
617 throw new coding_exception('Cookies must be disabled in WS servers!');
618 }
619
2d0acbd5 620 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
01482a4a 621
1839e2f0 622 //we check that authentication plugin is enabled
623 //it is only required by simple authentication
624 if (!is_enabled_auth('webservice')) {
625 throw new webservice_access_exception(get_string('wsauthnotenabled', 'webservice'));
626 }
01482a4a 627
1839e2f0 628 if (!$auth = get_auth_plugin('webservice')) {
629 throw new webservice_access_exception(get_string('wsauthmissing', 'webservice'));
630 }
01482a4a 631
01482a4a
PS
632 $this->restricted_context = get_context_instance(CONTEXT_SYSTEM);
633
634 if (!$this->username) {
b0a9a0cd 635 throw new webservice_access_exception(get_string('missingusername', 'webservice'));
01482a4a
PS
636 }
637
638 if (!$this->password) {
b0a9a0cd 639 throw new webservice_access_exception(get_string('missingpassword', 'webservice'));
01482a4a
PS
640 }
641
642 if (!$auth->user_login_webservice($this->username, $this->password)) {
1e29fe3f 643 // log failed login attempts
136e69c4 644 add_to_log(SITEID, 'webservice', get_string('simpleauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".$this->username."/".$this->password." - ".getremoteaddr() , 0);
b0a9a0cd 645 throw new webservice_access_exception(get_string('wrongusernamepassword', 'webservice'));
01482a4a
PS
646 }
647
648 $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0), '*', MUST_EXIST);
649
2d0acbd5
JP
650 } else if ($this->authmethod == WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN){
651 $user = $this->authenticate_by_token(EXTERNAL_TOKEN_PERMANENT);
01482a4a 652 } else {
2d0acbd5 653 $user = $this->authenticate_by_token(EXTERNAL_TOKEN_EMBEDDED);
01482a4a 654 }
c924a469 655
01482a4a
PS
656 // now fake user login, the session is completely empty too
657 session_set_user($user);
c3517f05 658 $this->userid = $user->id;
01482a4a 659
0f1e3914 660 if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
b0a9a0cd 661 throw new webservice_access_exception(get_string('accessnotallowed', 'webservice'));
01482a4a
PS
662 }
663
664 external_api::set_context_restriction($this->restricted_context);
665 }
c924a469 666
2d0acbd5
JP
667 protected function authenticate_by_token($tokentype){
668 global $DB;
669 if (!$token = $DB->get_record('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype))) {
670 // log failed login attempts
136e69c4 671 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".$this->token. " - ".getremoteaddr() , 0);
2d0acbd5
JP
672 throw new webservice_access_exception(get_string('invalidtoken', 'webservice'));
673 }
c924a469 674
2d0acbd5
JP
675 if ($token->validuntil and $token->validuntil < time()) {
676 $DB->delete_records('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype));
677 throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice'));
678 }
c924a469 679
2d0acbd5
JP
680 if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
681 $session = session_get_instance();
682 if (!$session->session_exists($token->sid)){
683 $DB->delete_records('external_tokens', array('sid'=>$token->sid));
684 throw new webservice_access_exception(get_string('invalidtokensession', 'webservice'));
685 }
686 }
687
688 if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
136e69c4 689 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr() , 0);
2d0acbd5
JP
690 throw new webservice_access_exception(get_string('invalidiptoken', 'webservice'));
691 }
692
693 $this->restricted_context = get_context_instance_by_id($token->contextid);
694 $this->restricted_serviceid = $token->externalserviceid;
695
696 $user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST);
697
698 // log token access
699 $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id));
c924a469 700
2d0acbd5 701 return $user;
c924a469 702
2d0acbd5 703 }
01482a4a
PS
704}
705
706/**
707 * Special abstraction of our srvices that allows
708 * interaction with stock Zend ws servers.
709 * @author Petr Skoda (skodak)
710 */
711abstract class webservice_zend_server extends webservice_server {
712
abf7dc44 713 /** @property string name of the zend server class : Zend_XmlRpc_Server, Zend_Soap_Server, Zend_Soap_AutoDiscover, ...*/
01482a4a
PS
714 protected $zend_class;
715
716 /** @property object Zend server instance */
717 protected $zend_server;
718
719 /** @property string $service_class virtual web service class with all functions user name execute, created on the fly */
720 protected $service_class;
721
88098133 722 /**
723 * Contructor
2d0acbd5 724 * @param integer $authmethod authentication method - one of WEBSERVICE_AUTHMETHOD_*
88098133 725 */
2d0acbd5
JP
726 public function __construct($authmethod, $zend_class) {
727 parent::__construct($authmethod);
88098133 728 $this->zend_class = $zend_class;
729 }
730
731 /**
732 * Process request from client.
733 * @param bool $simple use simple authentication
734 * @return void
735 */
2458e30a 736 public function run() {
88098133 737 // we will probably need a lot of memory in some functions
346c5887 738 raise_memory_limit(MEMORY_EXTRA);
88098133 739
740 // set some longer timeout, this script is not sending any output,
741 // this means we need to manually extend the timeout operations
742 // that need longer time to finish
743 external_api::set_timeout();
744
e8b21670 745 // now create the instance of zend server
746 $this->init_zend_server();
747
88098133 748 // set up exception handler first, we want to sent them back in correct format that
749 // the other system understands
750 // we do not need to call the original default handler because this ws handler does everything
751 set_exception_handler(array($this, 'exception_handler'));
752
c187722c
PS
753 // init all properties from the request data
754 $this->parse_request();
755
88098133 756 // this sets up $USER and $SESSION and context restrictions
757 $this->authenticate_user();
758
759 // make a list of all functions user is allowed to excecute
760 $this->init_service_class();
761
2458e30a 762 // tell server what functions are available
88098133 763 $this->zend_server->setClass($this->service_class);
d9ad0103 764
1e29fe3f 765 //log the web service request
136e69c4 766 add_to_log(SITEID, 'webservice', '', '' , $this->zend_class." ".getremoteaddr() , 0, $this->userid);
1e29fe3f 767
2458e30a 768 // execute and return response, this sends some headers too
88098133 769 $response = $this->zend_server->handle();
2458e30a 770
88098133 771 // session cleanup
772 $this->session_cleanup();
773
ca6340bf 774 //finally send the result
775 $this->send_headers();
88098133 776 echo $response;
777 die;
778 }
779
780 /**
781 * Load virtual class needed for Zend api
782 * @return void
783 */
784 protected function init_service_class() {
785 global $USER, $DB;
786
787 // first ofall get a complete list of services user is allowed to access
88098133 788
01482a4a
PS
789 if ($this->restricted_serviceid) {
790 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
791 $wscond1 = 'AND s.id = :sid1';
792 $wscond2 = 'AND s.id = :sid2';
793 } else {
794 $params = array();
795 $wscond1 = '';
796 $wscond2 = '';
797 }
88098133 798
01482a4a
PS
799 // now make sure the function is listed in at least one service user is allowed to use
800 // allow access only if:
801 // 1/ entry in the external_services_users table if required
802 // 2/ validuntil not reached
803 // 3/ has capability if specified in service desc
804 // 4/ iprestriction
88098133 805
01482a4a
PS
806 $sql = "SELECT s.*, NULL AS iprestriction
807 FROM {external_services} s
808 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
809 WHERE s.enabled = 1 $wscond1
88098133 810
01482a4a
PS
811 UNION
812
813 SELECT s.*, su.iprestriction
814 FROM {external_services} s
815 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
816 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
817 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
818
819 $params = array_merge($params, array('userid'=>$USER->id, 'now'=>time()));
88098133 820
821 $serviceids = array();
822 $rs = $DB->get_recordset_sql($sql, $params);
823
824 // now make sure user may access at least one service
825 $remoteaddr = getremoteaddr();
826 $allowed = false;
827 foreach ($rs as $service) {
828 if (isset($serviceids[$service->id])) {
829 continue;
830 }
831 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
832 continue; // cap required, sorry
833 }
834 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
835 continue; // wrong request source ip, sorry
836 }
837 $serviceids[$service->id] = $service->id;
838 }
839 $rs->close();
840
841 // now get the list of all functions
72f68b51 842 $wsmanager = new webservice();
843 $functions = $wsmanager->get_external_functions($serviceids);
88098133 844
845 // now make the virtual WS class with all the fuctions for this particular user
846 $methods = '';
847 foreach ($functions as $function) {
848 $methods .= $this->get_virtual_method_code($function);
849 }
850
5593d2dc 851 // let's use unique class name, there might be problem in unit tests
88098133 852 $classname = 'webservices_virtual_class_000000';
853 while(class_exists($classname)) {
854 $classname++;
855 }
856
857 $code = '
858/**
859 * Virtual class web services for user id '.$USER->id.' in context '.$this->restricted_context->id.'.
860 */
861class '.$classname.' {
862'.$methods.'
863}
864';
f0dafb3c 865
88098133 866 // load the virtual class definition into memory
867 eval($code);
88098133 868 $this->service_class = $classname;
869 }
870
871 /**
872 * returns virtual method code
873 * @param object $function
874 * @return string PHP code
875 */
876 protected function get_virtual_method_code($function) {
877 global $CFG;
878
5593d2dc 879 $function = external_function_info($function);
88098133 880
b437f681
JP
881 //arguments in function declaration line with defaults.
882 $paramanddefaults = array();
883 //arguments used as parameters for external lib call.
88098133 884 $params = array();
885 $params_desc = array();
453a7a85 886 foreach ($function->parameters_desc->keys as $name=>$keydesc) {
a8dd0325 887 $param = '$'.$name;
b437f681 888 $paramanddefault = $param;
a8dd0325 889 //need to generate the default if there is any
890 if ($keydesc instanceof external_value) {
891 if ($keydesc->required == VALUE_DEFAULT) {
892 if ($keydesc->default===null) {
b437f681 893 $paramanddefault .= '=null';
a8dd0325 894 } else {
895 switch($keydesc->type) {
896 case PARAM_BOOL:
b437f681 897 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 898 case PARAM_INT:
b437f681 899 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 900 case PARAM_FLOAT;
b437f681 901 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 902 default:
b437f681 903 $paramanddefault .= '=\''.$keydesc->default.'\'';
a8dd0325 904 }
905 }
906 } else if ($keydesc->required == VALUE_OPTIONAL) {
907 //it does make sens to declare a parameter VALUE_OPTIONAL
908 //VALUE_OPTIONAL is used only for array/object key
909 throw new moodle_exception('parametercannotbevalueoptional');
910 }
911 } else { //for the moment we do not support default for other structure types
774b1b0f 912 if ($keydesc->required == VALUE_DEFAULT) {
913 //accept empty array as default
914 if (isset($keydesc->default) and is_array($keydesc->default)
915 and empty($keydesc->default)) {
fde884c3 916 $paramanddefault .= '=array()';
774b1b0f 917 } else {
918 throw new moodle_exception('errornotemptydefaultparamarray', 'webservice', '', $name);
919 }
920 }
921 if ($keydesc->required == VALUE_OPTIONAL) {
922 throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
a8dd0325 923 }
924 }
b437f681
JP
925 $params[] = $param;
926 $paramanddefaults[] = $paramanddefault;
453a7a85 927 $type = 'string';
928 if ($keydesc instanceof external_value) {
929 switch($keydesc->type) {
930 case PARAM_BOOL: // 0 or 1 only for now
931 case PARAM_INT:
932 $type = 'int'; break;
933 case PARAM_FLOAT;
934 $type = 'double'; break;
935 default:
936 $type = 'string';
937 }
938 } else if ($keydesc instanceof external_single_structure) {
c06503b8 939 $type = 'object|struct';
453a7a85 940 } else if ($keydesc instanceof external_multiple_structure) {
941 $type = 'array';
942 }
943 $params_desc[] = ' * @param '.$type.' $'.$name.' '.$keydesc->desc;
88098133 944 }
b437f681
JP
945 $params = implode(', ', $params);
946 $paramanddefaults = implode(', ', $paramanddefaults);
947 $params_desc = implode("\n", $params_desc);
fde884c3 948
94a9b9e7 949 $serviceclassmethodbody = $this->service_class_method_body($function, $params);
88098133 950
453a7a85 951 if (is_null($function->returns_desc)) {
952 $return = ' * @return void';
953 } else {
954 $type = 'string';
955 if ($function->returns_desc instanceof external_value) {
956 switch($function->returns_desc->type) {
957 case PARAM_BOOL: // 0 or 1 only for now
958 case PARAM_INT:
959 $type = 'int'; break;
960 case PARAM_FLOAT;
961 $type = 'double'; break;
962 default:
963 $type = 'string';
964 }
965 } else if ($function->returns_desc instanceof external_single_structure) {
e93b82d7 966 $type = 'object|struct'; //only 'object' is supported by SOAP, 'struct' by XML-RPC MDL-23083
453a7a85 967 } else if ($function->returns_desc instanceof external_multiple_structure) {
968 $type = 'array';
969 }
970 $return = ' * @return '.$type.' '.$function->returns_desc->desc;
971 }
d4e764ab 972
01482a4a 973 // now crate the virtual method that calls the ext implementation
88098133 974
975 $code = '
976 /**
5593d2dc 977 * '.$function->description.'
978 *
88098133 979'.$params_desc.'
453a7a85 980'.$return.'
88098133 981 */
b437f681 982 public function '.$function->name.'('.$paramanddefaults.') {
c77e75a3 983'.$serviceclassmethodbody.'
88098133 984 }
985';
986 return $code;
987 }
c924a469 988
94a9b9e7
JP
989 /**
990 * You can override this function in your child class to add extra code into the dynamically
991 * created service class. For example it is used in the amf server to cast types of parameters and to
992 * cast the return value to the types as specified in the return value description.
fc0fcb27
PS
993 * @param stdClass $function
994 * @param array $params
94a9b9e7
JP
995 * @return string body of the method for $function ie. everything within the {} of the method declaration.
996 */
997 protected function service_class_method_body($function, $params){
c06503b8 998 //cast the param from object to array (validate_parameters except array only)
999 $castingcode = '';
1000 if ($params){
7785dc2e 1001 $paramstocast = explode(',', $params);
c06503b8 1002 foreach ($paramstocast as $paramtocast) {
02998b3f 1003 //clean the parameter from any white space
c924a469 1004 $paramtocast = trim($paramtocast);
c06503b8 1005 $castingcode .= $paramtocast .
1006 '=webservice_zend_server::cast_objects_to_array('.$paramtocast.');';
1007 }
1008
1009 }
1010
d07ff72d 1011 $descriptionmethod = $function->methodname.'_returns()';
2d0acbd5 1012 $callforreturnvaluedesc = $function->classname.'::'.$descriptionmethod;
c06503b8 1013 return $castingcode . ' if ('.$callforreturnvaluedesc.' == null) {'.
1014 $function->classname.'::'.$function->methodname.'('.$params.');
99152d09 1015 return null;
1016 }
1017 return external_api::clean_returnvalue('.$callforreturnvaluedesc.', '.$function->classname.'::'.$function->methodname.'('.$params.'));';
94a9b9e7 1018 }
d07ff72d 1019
88098133 1020 /**
c06503b8 1021 * Recursive function to recurse down into a complex variable and convert all
1022 * objects to arrays.
1023 * @param mixed $param value to cast
1024 * @return mixed Cast value
1025 */
1026 public static function cast_objects_to_array($param){
1027 if (is_object($param)){
1028 $param = (array)$param;
1029 }
1030 if (is_array($param)){
1031 $toreturn = array();
1032 foreach ($param as $key=> $param){
1033 $toreturn[$key] = self::cast_objects_to_array($param);
1034 }
1035 return $toreturn;
1036 } else {
1037 return $param;
1038 }
1039 }
1040
1041 /**
b107e647 1042 * Set up zend service class
88098133 1043 * @return void
1044 */
1045 protected function init_zend_server() {
88098133 1046 $this->zend_server = new $this->zend_class();
88098133 1047 }
1048
c187722c
PS
1049 /**
1050 * This method parses the $_REQUEST superglobal and looks for
1051 * the following information:
1052 * 1/ user authentication - username+password or token (wsusername, wspassword and wstoken parameters)
1053 *
1054 * @return void
1055 */
1056 protected function parse_request() {
2d0acbd5 1057 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
01482a4a 1058 //note: some clients have problems with entity encoding :-(
c187722c
PS
1059 if (isset($_REQUEST['wsusername'])) {
1060 $this->username = $_REQUEST['wsusername'];
c187722c
PS
1061 }
1062 if (isset($_REQUEST['wspassword'])) {
1063 $this->password = $_REQUEST['wspassword'];
c187722c
PS
1064 }
1065 } else {
01482a4a
PS
1066 if (isset($_REQUEST['wstoken'])) {
1067 $this->token = $_REQUEST['wstoken'];
88098133 1068 }
88098133 1069 }
88098133 1070 }
1071
ca6340bf 1072 /**
1073 * Internal implementation - sending of page headers.
1074 * @return void
1075 */
1076 protected function send_headers() {
1077 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
1078 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1079 header('Pragma: no-cache');
1080 header('Accept-Ranges: none');
1081 }
1082
88098133 1083 /**
1084 * Specialised exception handler, we can not use the standard one because
1085 * it can not just print html to output.
1086 *
1087 * @param exception $ex
1088 * @return void does not return
1089 */
1090 public function exception_handler($ex) {
88098133 1091 // detect active db transactions, rollback and log as error
3086dd59 1092 abort_all_db_transactions();
88098133 1093
88098133 1094 // some hacks might need a cleanup hook
1095 $this->session_cleanup($ex);
1096
ca6340bf 1097 // now let the plugin send the exception to client
b107e647 1098 $this->send_error($ex);
ca6340bf 1099
88098133 1100 // not much else we can do now, add some logging later
1101 exit(1);
1102 }
1103
b107e647
PS
1104 /**
1105 * Send the error information to the WS client
1106 * formatted as XML document.
1107 * @param exception $ex
1108 * @return void
1109 */
1110 protected function send_error($ex=null) {
1111 $this->send_headers();
1112 echo $this->zend_server->fault($ex);
1113 }
01482a4a 1114
88098133 1115 /**
1116 * Future hook needed for emulated sessions.
1117 * @param exception $exception null means normal termination, $exception received when WS call failed
1118 * @return void
1119 */
1120 protected function session_cleanup($exception=null) {
2d0acbd5 1121 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
88098133 1122 // nothing needs to be done, there is no persistent session
1123 } else {
1124 // close emulated session if used
1125 }
1126 }
1127
cc93c7da 1128}
1129
886d7556 1130/**
cc93c7da 1131 * Web Service server base class, this class handles both
1132 * simple and token authentication.
1133 * @author Petr Skoda (skodak)
886d7556 1134 */
01482a4a 1135abstract class webservice_base_server extends webservice_server {
88098133 1136
cc93c7da 1137 /** @property array $parameters the function parameters - the real values submitted in the request */
1138 protected $parameters = null;
1139
1140 /** @property string $functionname the name of the function that is executed */
1141 protected $functionname = null;
1142
1143 /** @property object $function full function description */
1144 protected $function = null;
1145
1146 /** @property mixed $returns function return value */
1147 protected $returns = null;
06e7fadc 1148
24350e06 1149 /**
cc93c7da 1150 * This method parses the request input, it needs to get:
1151 * 1/ user authentication - username+password or token
1152 * 2/ function name
1153 * 3/ function parameters
1154 *
1155 * @return void
24350e06 1156 */
cc93c7da 1157 abstract protected function parse_request();
24350e06 1158
cc93c7da 1159 /**
1160 * Send the result of function call to the WS client.
1161 * @return void
1162 */
1163 abstract protected function send_response();
24350e06 1164
fa0797ec 1165 /**
cc93c7da 1166 * Send the error information to the WS client.
1167 * @param exception $ex
1168 * @return void
fa0797ec 1169 */
cc93c7da 1170 abstract protected function send_error($ex=null);
fa0797ec 1171
cc93c7da 1172 /**
1173 * Process request from client.
cc93c7da 1174 * @return void
1175 */
2458e30a 1176 public function run() {
cc93c7da 1177 // we will probably need a lot of memory in some functions
346c5887 1178 raise_memory_limit(MEMORY_EXTRA);
fa0797ec 1179
cc93c7da 1180 // set some longer timeout, this script is not sending any output,
1181 // this means we need to manually extend the timeout operations
1182 // that need longer time to finish
1183 external_api::set_timeout();
fa0797ec 1184
cc93c7da 1185 // set up exception handler first, we want to sent them back in correct format that
1186 // the other system understands
1187 // we do not need to call the original default handler because this ws handler does everything
1188 set_exception_handler(array($this, 'exception_handler'));
06e7fadc 1189
cc93c7da 1190 // init all properties from the request data
1191 $this->parse_request();
06e7fadc 1192
cc93c7da 1193 // authenticate user, this has to be done after the request parsing
1194 // this also sets up $USER and $SESSION
1195 $this->authenticate_user();
06e7fadc 1196
cc93c7da 1197 // find all needed function info and make sure user may actually execute the function
1198 $this->load_function_info();
c924a469 1199
c3517f05 1200 //log the web service request
136e69c4 1201 add_to_log(SITEID, 'webservice', $this->functionname, '' , getremoteaddr() , 0, $this->userid);
f7631e73 1202
cc93c7da 1203 // finally, execute the function - any errors are catched by the default exception handler
1204 $this->execute();
06e7fadc 1205
cc93c7da 1206 // send the results back in correct format
1207 $this->send_response();
06e7fadc 1208
cc93c7da 1209 // session cleanup
1210 $this->session_cleanup();
06e7fadc 1211
cc93c7da 1212 die;
f7631e73 1213 }
1214
cc93c7da 1215 /**
1216 * Specialised exception handler, we can not use the standard one because
1217 * it can not just print html to output.
1218 *
1219 * @param exception $ex
1220 * @return void does not return
1221 */
1222 public function exception_handler($ex) {
cc93c7da 1223 // detect active db transactions, rollback and log as error
3086dd59 1224 abort_all_db_transactions();
06e7fadc 1225
cc93c7da 1226 // some hacks might need a cleanup hook
1227 $this->session_cleanup($ex);
06e7fadc 1228
ca6340bf 1229 // now let the plugin send the exception to client
1230 $this->send_error($ex);
1231
cc93c7da 1232 // not much else we can do now, add some logging later
1233 exit(1);
f7631e73 1234 }
1235
1236 /**
cc93c7da 1237 * Future hook needed for emulated sessions.
1238 * @param exception $exception null means normal termination, $exception received when WS call failed
1239 * @return void
f7631e73 1240 */
cc93c7da 1241 protected function session_cleanup($exception=null) {
ad8b5ba2 1242 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
cc93c7da 1243 // nothing needs to be done, there is no persistent session
1244 } else {
1245 // close emulated session if used
1246 }
f7631e73 1247 }
1248
24350e06 1249 /**
cc93c7da 1250 * Fetches the function description from database,
1251 * verifies user is allowed to use this function and
1252 * loads all paremeters and return descriptions.
1253 * @return void
24350e06 1254 */
cc93c7da 1255 protected function load_function_info() {
1256 global $DB, $USER, $CFG;
40f024c9 1257
cc93c7da 1258 if (empty($this->functionname)) {
1259 throw new invalid_parameter_exception('Missing function name');
1260 }
24350e06 1261
cc93c7da 1262 // function must exist
5593d2dc 1263 $function = external_function_info($this->functionname);
cc93c7da 1264
01482a4a
PS
1265 if ($this->restricted_serviceid) {
1266 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
1267 $wscond1 = 'AND s.id = :sid1';
1268 $wscond2 = 'AND s.id = :sid2';
1269 } else {
1270 $params = array();
1271 $wscond1 = '';
1272 $wscond2 = '';
1273 }
1274
cc93c7da 1275 // now let's verify access control
b8c5309e 1276
1277 // now make sure the function is listed in at least one service user is allowed to use
1278 // allow access only if:
1279 // 1/ entry in the external_services_users table if required
1280 // 2/ validuntil not reached
1281 // 3/ has capability if specified in service desc
1282 // 4/ iprestriction
1283
1284 $sql = "SELECT s.*, NULL AS iprestriction
1285 FROM {external_services} s
1286 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0 AND sf.functionname = :name1)
1287 WHERE s.enabled = 1 $wscond1
1288
1289 UNION
1290
1291 SELECT s.*, su.iprestriction
1292 FROM {external_services} s
1293 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1 AND sf.functionname = :name2)
1294 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
1295 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
1296 $params = array_merge($params, array('userid'=>$USER->id, 'name1'=>$function->name, 'name2'=>$function->name, 'now'=>time()));
88098133 1297
1298 $rs = $DB->get_recordset_sql($sql, $params);
1299 // now make sure user may access at least one service
1300 $remoteaddr = getremoteaddr();
1301 $allowed = false;
1302 foreach ($rs as $service) {
1303 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
1304 continue; // cap required, sorry
cc93c7da 1305 }
88098133 1306 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
1307 continue; // wrong request source ip, sorry
cc93c7da 1308 }
88098133 1309 $allowed = true;
1310 break; // one service is enough, no need to continue
1311 }
1312 $rs->close();
1313 if (!$allowed) {
c91cc5ef 1314 throw new webservice_access_exception('Access to external function not allowed');
cc93c7da 1315 }
9baf6825 1316
cc93c7da 1317 // we have all we need now
1318 $this->function = $function;
1319 }
1320
1321 /**
1322 * Execute previously loaded function using parameters parsed from the request data.
1323 * @return void
1324 */
1325 protected function execute() {
1326 // validate params, this also sorts the params properly, we need the correct order in the next part
1327 $params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
9baf6825 1328
cc93c7da 1329 // execute - yay!
1330 $this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), array_values($params));
9baf6825 1331 }
1332}
1333
1334