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 656 // now fake user login, the session is completely empty too
e922fe23 657 enrol_check_plugins($user);
01482a4a 658 session_set_user($user);
c3517f05 659 $this->userid = $user->id;
01482a4a 660
0f1e3914 661 if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
729ca373 662 throw new webservice_access_exception(get_string('protocolnotallowed', 'webservice', $this->wsname));
01482a4a
PS
663 }
664
665 external_api::set_context_restriction($this->restricted_context);
666 }
c924a469 667
2d0acbd5
JP
668 protected function authenticate_by_token($tokentype){
669 global $DB;
670 if (!$token = $DB->get_record('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype))) {
671 // log failed login attempts
136e69c4 672 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".$this->token. " - ".getremoteaddr() , 0);
2d0acbd5
JP
673 throw new webservice_access_exception(get_string('invalidtoken', 'webservice'));
674 }
c924a469 675
2d0acbd5
JP
676 if ($token->validuntil and $token->validuntil < time()) {
677 $DB->delete_records('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype));
678 throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice'));
679 }
c924a469 680
2d0acbd5
JP
681 if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
682 $session = session_get_instance();
683 if (!$session->session_exists($token->sid)){
684 $DB->delete_records('external_tokens', array('sid'=>$token->sid));
685 throw new webservice_access_exception(get_string('invalidtokensession', 'webservice'));
686 }
687 }
688
689 if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
136e69c4 690 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr() , 0);
2d0acbd5
JP
691 throw new webservice_access_exception(get_string('invalidiptoken', 'webservice'));
692 }
693
694 $this->restricted_context = get_context_instance_by_id($token->contextid);
695 $this->restricted_serviceid = $token->externalserviceid;
696
697 $user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST);
698
699 // log token access
700 $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id));
c924a469 701
2d0acbd5 702 return $user;
c924a469 703
2d0acbd5 704 }
01482a4a
PS
705}
706
707/**
708 * Special abstraction of our srvices that allows
709 * interaction with stock Zend ws servers.
710 * @author Petr Skoda (skodak)
711 */
712abstract class webservice_zend_server extends webservice_server {
713
abf7dc44 714 /** @property string name of the zend server class : Zend_XmlRpc_Server, Zend_Soap_Server, Zend_Soap_AutoDiscover, ...*/
01482a4a
PS
715 protected $zend_class;
716
717 /** @property object Zend server instance */
718 protected $zend_server;
719
720 /** @property string $service_class virtual web service class with all functions user name execute, created on the fly */
721 protected $service_class;
722
88098133 723 /**
724 * Contructor
2d0acbd5 725 * @param integer $authmethod authentication method - one of WEBSERVICE_AUTHMETHOD_*
88098133 726 */
2d0acbd5
JP
727 public function __construct($authmethod, $zend_class) {
728 parent::__construct($authmethod);
88098133 729 $this->zend_class = $zend_class;
730 }
731
732 /**
733 * Process request from client.
734 * @param bool $simple use simple authentication
735 * @return void
736 */
2458e30a 737 public function run() {
88098133 738 // we will probably need a lot of memory in some functions
346c5887 739 raise_memory_limit(MEMORY_EXTRA);
88098133 740
741 // set some longer timeout, this script is not sending any output,
742 // this means we need to manually extend the timeout operations
743 // that need longer time to finish
744 external_api::set_timeout();
745
e8b21670 746 // now create the instance of zend server
747 $this->init_zend_server();
748
88098133 749 // set up exception handler first, we want to sent them back in correct format that
750 // the other system understands
751 // we do not need to call the original default handler because this ws handler does everything
752 set_exception_handler(array($this, 'exception_handler'));
753
c187722c
PS
754 // init all properties from the request data
755 $this->parse_request();
756
88098133 757 // this sets up $USER and $SESSION and context restrictions
758 $this->authenticate_user();
759
760 // make a list of all functions user is allowed to excecute
761 $this->init_service_class();
762
2458e30a 763 // tell server what functions are available
88098133 764 $this->zend_server->setClass($this->service_class);
d9ad0103 765
1e29fe3f 766 //log the web service request
136e69c4 767 add_to_log(SITEID, 'webservice', '', '' , $this->zend_class." ".getremoteaddr() , 0, $this->userid);
1e29fe3f 768
936cabd6
AO
769 //send headers
770 $this->send_headers();
771
2458e30a 772 // execute and return response, this sends some headers too
88098133 773 $response = $this->zend_server->handle();
2458e30a 774
88098133 775 // session cleanup
776 $this->session_cleanup();
777
ca6340bf 778 //finally send the result
88098133 779 echo $response;
780 die;
781 }
782
783 /**
784 * Load virtual class needed for Zend api
785 * @return void
786 */
787 protected function init_service_class() {
788 global $USER, $DB;
789
790 // first ofall get a complete list of services user is allowed to access
88098133 791
01482a4a
PS
792 if ($this->restricted_serviceid) {
793 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
794 $wscond1 = 'AND s.id = :sid1';
795 $wscond2 = 'AND s.id = :sid2';
796 } else {
797 $params = array();
798 $wscond1 = '';
799 $wscond2 = '';
800 }
88098133 801
01482a4a
PS
802 // now make sure the function is listed in at least one service user is allowed to use
803 // allow access only if:
804 // 1/ entry in the external_services_users table if required
805 // 2/ validuntil not reached
806 // 3/ has capability if specified in service desc
807 // 4/ iprestriction
88098133 808
01482a4a
PS
809 $sql = "SELECT s.*, NULL AS iprestriction
810 FROM {external_services} s
811 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
812 WHERE s.enabled = 1 $wscond1
88098133 813
01482a4a
PS
814 UNION
815
816 SELECT s.*, su.iprestriction
817 FROM {external_services} s
818 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
819 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
820 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
821
822 $params = array_merge($params, array('userid'=>$USER->id, 'now'=>time()));
88098133 823
824 $serviceids = array();
825 $rs = $DB->get_recordset_sql($sql, $params);
826
827 // now make sure user may access at least one service
828 $remoteaddr = getremoteaddr();
829 $allowed = false;
830 foreach ($rs as $service) {
831 if (isset($serviceids[$service->id])) {
832 continue;
833 }
834 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
835 continue; // cap required, sorry
836 }
837 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
838 continue; // wrong request source ip, sorry
839 }
840 $serviceids[$service->id] = $service->id;
841 }
842 $rs->close();
843
844 // now get the list of all functions
72f68b51 845 $wsmanager = new webservice();
846 $functions = $wsmanager->get_external_functions($serviceids);
88098133 847
848 // now make the virtual WS class with all the fuctions for this particular user
849 $methods = '';
850 foreach ($functions as $function) {
851 $methods .= $this->get_virtual_method_code($function);
852 }
853
5593d2dc 854 // let's use unique class name, there might be problem in unit tests
88098133 855 $classname = 'webservices_virtual_class_000000';
856 while(class_exists($classname)) {
857 $classname++;
858 }
859
860 $code = '
861/**
862 * Virtual class web services for user id '.$USER->id.' in context '.$this->restricted_context->id.'.
863 */
864class '.$classname.' {
865'.$methods.'
866}
867';
f0dafb3c 868
88098133 869 // load the virtual class definition into memory
870 eval($code);
88098133 871 $this->service_class = $classname;
872 }
873
874 /**
875 * returns virtual method code
876 * @param object $function
877 * @return string PHP code
878 */
879 protected function get_virtual_method_code($function) {
880 global $CFG;
881
5593d2dc 882 $function = external_function_info($function);
88098133 883
b437f681
JP
884 //arguments in function declaration line with defaults.
885 $paramanddefaults = array();
886 //arguments used as parameters for external lib call.
88098133 887 $params = array();
888 $params_desc = array();
453a7a85 889 foreach ($function->parameters_desc->keys as $name=>$keydesc) {
a8dd0325 890 $param = '$'.$name;
b437f681 891 $paramanddefault = $param;
a8dd0325 892 //need to generate the default if there is any
893 if ($keydesc instanceof external_value) {
894 if ($keydesc->required == VALUE_DEFAULT) {
895 if ($keydesc->default===null) {
b437f681 896 $paramanddefault .= '=null';
a8dd0325 897 } else {
898 switch($keydesc->type) {
899 case PARAM_BOOL:
b437f681 900 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 901 case PARAM_INT:
b437f681 902 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 903 case PARAM_FLOAT;
b437f681 904 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 905 default:
b437f681 906 $paramanddefault .= '=\''.$keydesc->default.'\'';
a8dd0325 907 }
908 }
909 } else if ($keydesc->required == VALUE_OPTIONAL) {
910 //it does make sens to declare a parameter VALUE_OPTIONAL
911 //VALUE_OPTIONAL is used only for array/object key
912 throw new moodle_exception('parametercannotbevalueoptional');
913 }
914 } else { //for the moment we do not support default for other structure types
774b1b0f 915 if ($keydesc->required == VALUE_DEFAULT) {
916 //accept empty array as default
917 if (isset($keydesc->default) and is_array($keydesc->default)
918 and empty($keydesc->default)) {
fde884c3 919 $paramanddefault .= '=array()';
774b1b0f 920 } else {
921 throw new moodle_exception('errornotemptydefaultparamarray', 'webservice', '', $name);
922 }
923 }
924 if ($keydesc->required == VALUE_OPTIONAL) {
925 throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
a8dd0325 926 }
927 }
b437f681
JP
928 $params[] = $param;
929 $paramanddefaults[] = $paramanddefault;
cc7fb382 930 $type = $this->get_phpdoc_type($keydesc);
453a7a85 931 $params_desc[] = ' * @param '.$type.' $'.$name.' '.$keydesc->desc;
88098133 932 }
b437f681
JP
933 $params = implode(', ', $params);
934 $paramanddefaults = implode(', ', $paramanddefaults);
935 $params_desc = implode("\n", $params_desc);
fde884c3 936
94a9b9e7 937 $serviceclassmethodbody = $this->service_class_method_body($function, $params);
88098133 938
453a7a85 939 if (is_null($function->returns_desc)) {
940 $return = ' * @return void';
941 } else {
cc7fb382 942 $type = $this->get_phpdoc_type($function->returns_desc);
453a7a85 943 $return = ' * @return '.$type.' '.$function->returns_desc->desc;
944 }
d4e764ab 945
01482a4a 946 // now crate the virtual method that calls the ext implementation
88098133 947
948 $code = '
949 /**
5593d2dc 950 * '.$function->description.'
951 *
88098133 952'.$params_desc.'
453a7a85 953'.$return.'
88098133 954 */
b437f681 955 public function '.$function->name.'('.$paramanddefaults.') {
c77e75a3 956'.$serviceclassmethodbody.'
88098133 957 }
958';
959 return $code;
960 }
c924a469 961
cc7fb382
TH
962 protected function get_phpdoc_type($keydesc) {
963 if ($keydesc instanceof external_value) {
964 switch($keydesc->type) {
965 case PARAM_BOOL: // 0 or 1 only for now
966 case PARAM_INT:
967 $type = 'int'; break;
968 case PARAM_FLOAT;
969 $type = 'double'; break;
970 default:
971 $type = 'string';
972 }
973
974 } else if ($keydesc instanceof external_single_structure) {
975 $classname = $this->generate_simple_struct_class($keydesc);
976 $type = $classname;
977
978 } else if ($keydesc instanceof external_multiple_structure) {
979 $type = 'array';
980 }
981
982 return $type;
983 }
984
985 protected function generate_simple_struct_class(external_single_structure $structdesc) {
986 return 'object|struct'; //only 'object' is supported by SOAP, 'struct' by XML-RPC MDL-23083
987 }
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) {
ccda0134 1314 throw new webservice_access_exception(get_string('accesstofunctionnotallowed', 'webservice', $this->functionname));
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