MDL-28646 Web service: core_course_get_contents()
[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 /**
e88c193f 168 * Return all ws user token with ws enabled/disabled and ws restricted users mode.
229a7099 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
e88c193f 176 t.id, t.creatorid, t.token, u.firstname, u.lastname, s.id as wsid, s.name, s.enabled, s.restrictedusers, t.validuntil
229a7099 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
07a90ec3 648 $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
01482a4a 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
07a90ec3
JM
656 //Non admin can not authenticate if maintenance mode
657 $hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user);
658 if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) {
659 throw new webservice_access_exception(get_string('sitemaintenance', 'admin'));
660 }
661
662 //only confirmed user should be able to call web service
663 if (!empty($user->deleted)) {
664 add_to_log(SITEID, '', '', '', get_string('wsaccessuserdeleted', 'webservice', $user->username) . " - ".getremoteaddr(), 0, $user->id);
665 throw new webservice_access_exception(get_string('wsaccessuserdeleted', 'webservice', $user->username));
666 }
667
668 //only confirmed user should be able to call web service
669 if (empty($user->confirmed)) {
670 add_to_log(SITEID, '', '', '', get_string('wsaccessuserunconfirmed', 'webservice', $user->username) . " - ".getremoteaddr(), 0, $user->id);
671 throw new webservice_access_exception(get_string('wsaccessuserunconfirmed', 'webservice', $user->username));
672 }
673
674 //check the user is suspended
675 if (!empty($user->suspended)) {
676 add_to_log(SITEID, '', '', '', get_string('wsaccessusersuspended', 'webservice', $user->username) . " - ".getremoteaddr(), 0, $user->id);
677 throw new webservice_access_exception(get_string('wsaccessusersuspended', 'webservice', $user->username));
678 }
679
680 //retrieve the authentication plugin if no previously done
681 if (empty($auth)) {
682 $auth = get_auth_plugin($user->auth);
683 }
684
685 // check if credentials have expired
686 if (!empty($auth->config->expiration) and $auth->config->expiration == 1) {
687 $days2expire = $auth->password_expire($user->username);
688 if (intval($days2expire) < 0 ) {
689 add_to_log(SITEID, '', '', '', get_string('wsaccessuserexpired', 'webservice', $user->username) . " - ".getremoteaddr(), 0, $user->id);
690 throw new webservice_access_exception(get_string('wsaccessuserexpired', 'webservice', $user->username));
691 }
692 }
693
694 //check if the auth method is nologin (in this case refuse connection)
695 if ($user->auth=='nologin') {
696 add_to_log(SITEID, '', '', '', get_string('wsaccessusernologin', 'webservice', $user->username) . " - ".getremoteaddr(), 0, $user->id);
697 throw new webservice_access_exception(get_string('wsaccessusernologin', 'webservice', $user->username));
698 }
699
01482a4a 700 // now fake user login, the session is completely empty too
e922fe23 701 enrol_check_plugins($user);
01482a4a 702 session_set_user($user);
c3517f05 703 $this->userid = $user->id;
01482a4a 704
0f1e3914 705 if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
729ca373 706 throw new webservice_access_exception(get_string('protocolnotallowed', 'webservice', $this->wsname));
01482a4a
PS
707 }
708
709 external_api::set_context_restriction($this->restricted_context);
710 }
c924a469 711
2d0acbd5
JP
712 protected function authenticate_by_token($tokentype){
713 global $DB;
714 if (!$token = $DB->get_record('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype))) {
715 // log failed login attempts
136e69c4 716 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".$this->token. " - ".getremoteaddr() , 0);
2d0acbd5
JP
717 throw new webservice_access_exception(get_string('invalidtoken', 'webservice'));
718 }
c924a469 719
2d0acbd5
JP
720 if ($token->validuntil and $token->validuntil < time()) {
721 $DB->delete_records('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype));
722 throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice'));
723 }
c924a469 724
2d0acbd5
JP
725 if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
726 $session = session_get_instance();
727 if (!$session->session_exists($token->sid)){
728 $DB->delete_records('external_tokens', array('sid'=>$token->sid));
729 throw new webservice_access_exception(get_string('invalidtokensession', 'webservice'));
730 }
731 }
732
733 if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
136e69c4 734 add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr() , 0);
2d0acbd5
JP
735 throw new webservice_access_exception(get_string('invalidiptoken', 'webservice'));
736 }
737
738 $this->restricted_context = get_context_instance_by_id($token->contextid);
739 $this->restricted_serviceid = $token->externalserviceid;
740
07a90ec3 741 $user = $DB->get_record('user', array('id'=>$token->userid), '*', MUST_EXIST);
2d0acbd5
JP
742
743 // log token access
744 $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id));
c924a469 745
2d0acbd5 746 return $user;
c924a469 747
2d0acbd5 748 }
01482a4a
PS
749}
750
751/**
752 * Special abstraction of our srvices that allows
753 * interaction with stock Zend ws servers.
754 * @author Petr Skoda (skodak)
755 */
756abstract class webservice_zend_server extends webservice_server {
757
abf7dc44 758 /** @property string name of the zend server class : Zend_XmlRpc_Server, Zend_Soap_Server, Zend_Soap_AutoDiscover, ...*/
01482a4a
PS
759 protected $zend_class;
760
761 /** @property object Zend server instance */
762 protected $zend_server;
763
764 /** @property string $service_class virtual web service class with all functions user name execute, created on the fly */
765 protected $service_class;
766
88098133 767 /**
768 * Contructor
2d0acbd5 769 * @param integer $authmethod authentication method - one of WEBSERVICE_AUTHMETHOD_*
88098133 770 */
2d0acbd5
JP
771 public function __construct($authmethod, $zend_class) {
772 parent::__construct($authmethod);
88098133 773 $this->zend_class = $zend_class;
774 }
775
776 /**
777 * Process request from client.
778 * @param bool $simple use simple authentication
779 * @return void
780 */
2458e30a 781 public function run() {
88098133 782 // we will probably need a lot of memory in some functions
346c5887 783 raise_memory_limit(MEMORY_EXTRA);
88098133 784
785 // set some longer timeout, this script is not sending any output,
786 // this means we need to manually extend the timeout operations
787 // that need longer time to finish
788 external_api::set_timeout();
789
e8b21670 790 // now create the instance of zend server
791 $this->init_zend_server();
792
88098133 793 // set up exception handler first, we want to sent them back in correct format that
794 // the other system understands
795 // we do not need to call the original default handler because this ws handler does everything
796 set_exception_handler(array($this, 'exception_handler'));
797
c187722c
PS
798 // init all properties from the request data
799 $this->parse_request();
800
88098133 801 // this sets up $USER and $SESSION and context restrictions
802 $this->authenticate_user();
803
804 // make a list of all functions user is allowed to excecute
805 $this->init_service_class();
806
2458e30a 807 // tell server what functions are available
88098133 808 $this->zend_server->setClass($this->service_class);
d9ad0103 809
1e29fe3f 810 //log the web service request
136e69c4 811 add_to_log(SITEID, 'webservice', '', '' , $this->zend_class." ".getremoteaddr() , 0, $this->userid);
1e29fe3f 812
936cabd6
AO
813 //send headers
814 $this->send_headers();
815
2458e30a 816 // execute and return response, this sends some headers too
88098133 817 $response = $this->zend_server->handle();
2458e30a 818
88098133 819 // session cleanup
820 $this->session_cleanup();
821
ca6340bf 822 //finally send the result
88098133 823 echo $response;
824 die;
825 }
826
827 /**
828 * Load virtual class needed for Zend api
829 * @return void
830 */
831 protected function init_service_class() {
832 global $USER, $DB;
833
834 // first ofall get a complete list of services user is allowed to access
88098133 835
01482a4a
PS
836 if ($this->restricted_serviceid) {
837 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
838 $wscond1 = 'AND s.id = :sid1';
839 $wscond2 = 'AND s.id = :sid2';
840 } else {
841 $params = array();
842 $wscond1 = '';
843 $wscond2 = '';
844 }
88098133 845
01482a4a
PS
846 // now make sure the function is listed in at least one service user is allowed to use
847 // allow access only if:
848 // 1/ entry in the external_services_users table if required
849 // 2/ validuntil not reached
850 // 3/ has capability if specified in service desc
851 // 4/ iprestriction
88098133 852
01482a4a
PS
853 $sql = "SELECT s.*, NULL AS iprestriction
854 FROM {external_services} s
855 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
856 WHERE s.enabled = 1 $wscond1
88098133 857
01482a4a
PS
858 UNION
859
860 SELECT s.*, su.iprestriction
861 FROM {external_services} s
862 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
863 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
864 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
865
866 $params = array_merge($params, array('userid'=>$USER->id, 'now'=>time()));
88098133 867
868 $serviceids = array();
869 $rs = $DB->get_recordset_sql($sql, $params);
870
871 // now make sure user may access at least one service
872 $remoteaddr = getremoteaddr();
873 $allowed = false;
874 foreach ($rs as $service) {
875 if (isset($serviceids[$service->id])) {
876 continue;
877 }
878 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
879 continue; // cap required, sorry
880 }
881 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
882 continue; // wrong request source ip, sorry
883 }
884 $serviceids[$service->id] = $service->id;
885 }
886 $rs->close();
887
888 // now get the list of all functions
72f68b51 889 $wsmanager = new webservice();
890 $functions = $wsmanager->get_external_functions($serviceids);
88098133 891
892 // now make the virtual WS class with all the fuctions for this particular user
893 $methods = '';
894 foreach ($functions as $function) {
895 $methods .= $this->get_virtual_method_code($function);
896 }
897
5593d2dc 898 // let's use unique class name, there might be problem in unit tests
88098133 899 $classname = 'webservices_virtual_class_000000';
900 while(class_exists($classname)) {
901 $classname++;
902 }
903
904 $code = '
905/**
906 * Virtual class web services for user id '.$USER->id.' in context '.$this->restricted_context->id.'.
907 */
908class '.$classname.' {
909'.$methods.'
910}
911';
f0dafb3c 912
88098133 913 // load the virtual class definition into memory
914 eval($code);
88098133 915 $this->service_class = $classname;
916 }
917
918 /**
919 * returns virtual method code
920 * @param object $function
921 * @return string PHP code
922 */
923 protected function get_virtual_method_code($function) {
924 global $CFG;
925
5593d2dc 926 $function = external_function_info($function);
88098133 927
b437f681
JP
928 //arguments in function declaration line with defaults.
929 $paramanddefaults = array();
930 //arguments used as parameters for external lib call.
88098133 931 $params = array();
932 $params_desc = array();
453a7a85 933 foreach ($function->parameters_desc->keys as $name=>$keydesc) {
a8dd0325 934 $param = '$'.$name;
b437f681 935 $paramanddefault = $param;
a8dd0325 936 //need to generate the default if there is any
937 if ($keydesc instanceof external_value) {
938 if ($keydesc->required == VALUE_DEFAULT) {
939 if ($keydesc->default===null) {
b437f681 940 $paramanddefault .= '=null';
a8dd0325 941 } else {
942 switch($keydesc->type) {
943 case PARAM_BOOL:
b437f681 944 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 945 case PARAM_INT:
b437f681 946 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 947 case PARAM_FLOAT;
b437f681 948 $paramanddefault .= '='.$keydesc->default; break;
a8dd0325 949 default:
b437f681 950 $paramanddefault .= '=\''.$keydesc->default.'\'';
a8dd0325 951 }
952 }
953 } else if ($keydesc->required == VALUE_OPTIONAL) {
954 //it does make sens to declare a parameter VALUE_OPTIONAL
955 //VALUE_OPTIONAL is used only for array/object key
956 throw new moodle_exception('parametercannotbevalueoptional');
957 }
958 } else { //for the moment we do not support default for other structure types
774b1b0f 959 if ($keydesc->required == VALUE_DEFAULT) {
960 //accept empty array as default
961 if (isset($keydesc->default) and is_array($keydesc->default)
962 and empty($keydesc->default)) {
fde884c3 963 $paramanddefault .= '=array()';
774b1b0f 964 } else {
965 throw new moodle_exception('errornotemptydefaultparamarray', 'webservice', '', $name);
966 }
967 }
968 if ($keydesc->required == VALUE_OPTIONAL) {
969 throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
a8dd0325 970 }
971 }
b437f681
JP
972 $params[] = $param;
973 $paramanddefaults[] = $paramanddefault;
cc7fb382 974 $type = $this->get_phpdoc_type($keydesc);
453a7a85 975 $params_desc[] = ' * @param '.$type.' $'.$name.' '.$keydesc->desc;
88098133 976 }
b437f681
JP
977 $params = implode(', ', $params);
978 $paramanddefaults = implode(', ', $paramanddefaults);
979 $params_desc = implode("\n", $params_desc);
fde884c3 980
94a9b9e7 981 $serviceclassmethodbody = $this->service_class_method_body($function, $params);
88098133 982
453a7a85 983 if (is_null($function->returns_desc)) {
984 $return = ' * @return void';
985 } else {
cc7fb382 986 $type = $this->get_phpdoc_type($function->returns_desc);
453a7a85 987 $return = ' * @return '.$type.' '.$function->returns_desc->desc;
988 }
d4e764ab 989
01482a4a 990 // now crate the virtual method that calls the ext implementation
88098133 991
992 $code = '
993 /**
5593d2dc 994 * '.$function->description.'
995 *
88098133 996'.$params_desc.'
453a7a85 997'.$return.'
88098133 998 */
b437f681 999 public function '.$function->name.'('.$paramanddefaults.') {
c77e75a3 1000'.$serviceclassmethodbody.'
88098133 1001 }
1002';
1003 return $code;
1004 }
c924a469 1005
cc7fb382
TH
1006 protected function get_phpdoc_type($keydesc) {
1007 if ($keydesc instanceof external_value) {
1008 switch($keydesc->type) {
1009 case PARAM_BOOL: // 0 or 1 only for now
1010 case PARAM_INT:
1011 $type = 'int'; break;
1012 case PARAM_FLOAT;
1013 $type = 'double'; break;
1014 default:
1015 $type = 'string';
1016 }
1017
1018 } else if ($keydesc instanceof external_single_structure) {
1019 $classname = $this->generate_simple_struct_class($keydesc);
1020 $type = $classname;
1021
1022 } else if ($keydesc instanceof external_multiple_structure) {
1023 $type = 'array';
1024 }
1025
1026 return $type;
1027 }
1028
1029 protected function generate_simple_struct_class(external_single_structure $structdesc) {
1030 return 'object|struct'; //only 'object' is supported by SOAP, 'struct' by XML-RPC MDL-23083
1031 }
1032
94a9b9e7
JP
1033 /**
1034 * You can override this function in your child class to add extra code into the dynamically
1035 * created service class. For example it is used in the amf server to cast types of parameters and to
1036 * cast the return value to the types as specified in the return value description.
fc0fcb27
PS
1037 * @param stdClass $function
1038 * @param array $params
94a9b9e7
JP
1039 * @return string body of the method for $function ie. everything within the {} of the method declaration.
1040 */
1041 protected function service_class_method_body($function, $params){
c06503b8 1042 //cast the param from object to array (validate_parameters except array only)
1043 $castingcode = '';
1044 if ($params){
7785dc2e 1045 $paramstocast = explode(',', $params);
c06503b8 1046 foreach ($paramstocast as $paramtocast) {
02998b3f 1047 //clean the parameter from any white space
c924a469 1048 $paramtocast = trim($paramtocast);
c06503b8 1049 $castingcode .= $paramtocast .
1050 '=webservice_zend_server::cast_objects_to_array('.$paramtocast.');';
1051 }
1052
1053 }
1054
d07ff72d 1055 $descriptionmethod = $function->methodname.'_returns()';
2d0acbd5 1056 $callforreturnvaluedesc = $function->classname.'::'.$descriptionmethod;
c06503b8 1057 return $castingcode . ' if ('.$callforreturnvaluedesc.' == null) {'.
1058 $function->classname.'::'.$function->methodname.'('.$params.');
99152d09 1059 return null;
1060 }
1061 return external_api::clean_returnvalue('.$callforreturnvaluedesc.', '.$function->classname.'::'.$function->methodname.'('.$params.'));';
94a9b9e7 1062 }
d07ff72d 1063
88098133 1064 /**
c06503b8 1065 * Recursive function to recurse down into a complex variable and convert all
1066 * objects to arrays.
1067 * @param mixed $param value to cast
1068 * @return mixed Cast value
1069 */
1070 public static function cast_objects_to_array($param){
1071 if (is_object($param)){
1072 $param = (array)$param;
1073 }
1074 if (is_array($param)){
1075 $toreturn = array();
1076 foreach ($param as $key=> $param){
1077 $toreturn[$key] = self::cast_objects_to_array($param);
1078 }
1079 return $toreturn;
1080 } else {
1081 return $param;
1082 }
1083 }
1084
1085 /**
b107e647 1086 * Set up zend service class
88098133 1087 * @return void
1088 */
1089 protected function init_zend_server() {
88098133 1090 $this->zend_server = new $this->zend_class();
88098133 1091 }
1092
c187722c
PS
1093 /**
1094 * This method parses the $_REQUEST superglobal and looks for
1095 * the following information:
1096 * 1/ user authentication - username+password or token (wsusername, wspassword and wstoken parameters)
1097 *
1098 * @return void
1099 */
1100 protected function parse_request() {
2d0acbd5 1101 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
01482a4a 1102 //note: some clients have problems with entity encoding :-(
c187722c
PS
1103 if (isset($_REQUEST['wsusername'])) {
1104 $this->username = $_REQUEST['wsusername'];
c187722c
PS
1105 }
1106 if (isset($_REQUEST['wspassword'])) {
1107 $this->password = $_REQUEST['wspassword'];
c187722c
PS
1108 }
1109 } else {
01482a4a
PS
1110 if (isset($_REQUEST['wstoken'])) {
1111 $this->token = $_REQUEST['wstoken'];
88098133 1112 }
88098133 1113 }
88098133 1114 }
1115
ca6340bf 1116 /**
1117 * Internal implementation - sending of page headers.
1118 * @return void
1119 */
1120 protected function send_headers() {
1121 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
1122 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
1123 header('Pragma: no-cache');
1124 header('Accept-Ranges: none');
1125 }
1126
88098133 1127 /**
1128 * Specialised exception handler, we can not use the standard one because
1129 * it can not just print html to output.
1130 *
1131 * @param exception $ex
1132 * @return void does not return
1133 */
1134 public function exception_handler($ex) {
88098133 1135 // detect active db transactions, rollback and log as error
3086dd59 1136 abort_all_db_transactions();
88098133 1137
88098133 1138 // some hacks might need a cleanup hook
1139 $this->session_cleanup($ex);
1140
ca6340bf 1141 // now let the plugin send the exception to client
b107e647 1142 $this->send_error($ex);
ca6340bf 1143
88098133 1144 // not much else we can do now, add some logging later
1145 exit(1);
1146 }
1147
b107e647
PS
1148 /**
1149 * Send the error information to the WS client
1150 * formatted as XML document.
1151 * @param exception $ex
1152 * @return void
1153 */
1154 protected function send_error($ex=null) {
1155 $this->send_headers();
1156 echo $this->zend_server->fault($ex);
1157 }
01482a4a 1158
88098133 1159 /**
1160 * Future hook needed for emulated sessions.
1161 * @param exception $exception null means normal termination, $exception received when WS call failed
1162 * @return void
1163 */
1164 protected function session_cleanup($exception=null) {
2d0acbd5 1165 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
88098133 1166 // nothing needs to be done, there is no persistent session
1167 } else {
1168 // close emulated session if used
1169 }
1170 }
1171
cc93c7da 1172}
1173
886d7556 1174/**
cc93c7da 1175 * Web Service server base class, this class handles both
1176 * simple and token authentication.
1177 * @author Petr Skoda (skodak)
886d7556 1178 */
01482a4a 1179abstract class webservice_base_server extends webservice_server {
88098133 1180
cc93c7da 1181 /** @property array $parameters the function parameters - the real values submitted in the request */
1182 protected $parameters = null;
1183
1184 /** @property string $functionname the name of the function that is executed */
1185 protected $functionname = null;
1186
1187 /** @property object $function full function description */
1188 protected $function = null;
1189
1190 /** @property mixed $returns function return value */
1191 protected $returns = null;
06e7fadc 1192
24350e06 1193 /**
cc93c7da 1194 * This method parses the request input, it needs to get:
1195 * 1/ user authentication - username+password or token
1196 * 2/ function name
1197 * 3/ function parameters
1198 *
1199 * @return void
24350e06 1200 */
cc93c7da 1201 abstract protected function parse_request();
24350e06 1202
cc93c7da 1203 /**
1204 * Send the result of function call to the WS client.
1205 * @return void
1206 */
1207 abstract protected function send_response();
24350e06 1208
fa0797ec 1209 /**
cc93c7da 1210 * Send the error information to the WS client.
1211 * @param exception $ex
1212 * @return void
fa0797ec 1213 */
cc93c7da 1214 abstract protected function send_error($ex=null);
fa0797ec 1215
cc93c7da 1216 /**
1217 * Process request from client.
cc93c7da 1218 * @return void
1219 */
2458e30a 1220 public function run() {
cc93c7da 1221 // we will probably need a lot of memory in some functions
346c5887 1222 raise_memory_limit(MEMORY_EXTRA);
fa0797ec 1223
cc93c7da 1224 // set some longer timeout, this script is not sending any output,
1225 // this means we need to manually extend the timeout operations
1226 // that need longer time to finish
1227 external_api::set_timeout();
fa0797ec 1228
cc93c7da 1229 // set up exception handler first, we want to sent them back in correct format that
1230 // the other system understands
1231 // we do not need to call the original default handler because this ws handler does everything
1232 set_exception_handler(array($this, 'exception_handler'));
06e7fadc 1233
cc93c7da 1234 // init all properties from the request data
1235 $this->parse_request();
06e7fadc 1236
cc93c7da 1237 // authenticate user, this has to be done after the request parsing
1238 // this also sets up $USER and $SESSION
1239 $this->authenticate_user();
06e7fadc 1240
cc93c7da 1241 // find all needed function info and make sure user may actually execute the function
1242 $this->load_function_info();
c924a469 1243
c3517f05 1244 //log the web service request
136e69c4 1245 add_to_log(SITEID, 'webservice', $this->functionname, '' , getremoteaddr() , 0, $this->userid);
f7631e73 1246
cc93c7da 1247 // finally, execute the function - any errors are catched by the default exception handler
1248 $this->execute();
06e7fadc 1249
cc93c7da 1250 // send the results back in correct format
1251 $this->send_response();
06e7fadc 1252
cc93c7da 1253 // session cleanup
1254 $this->session_cleanup();
06e7fadc 1255
cc93c7da 1256 die;
f7631e73 1257 }
1258
cc93c7da 1259 /**
1260 * Specialised exception handler, we can not use the standard one because
1261 * it can not just print html to output.
1262 *
1263 * @param exception $ex
1264 * @return void does not return
1265 */
1266 public function exception_handler($ex) {
cc93c7da 1267 // detect active db transactions, rollback and log as error
3086dd59 1268 abort_all_db_transactions();
06e7fadc 1269
cc93c7da 1270 // some hacks might need a cleanup hook
1271 $this->session_cleanup($ex);
06e7fadc 1272
ca6340bf 1273 // now let the plugin send the exception to client
1274 $this->send_error($ex);
1275
cc93c7da 1276 // not much else we can do now, add some logging later
1277 exit(1);
f7631e73 1278 }
1279
1280 /**
cc93c7da 1281 * Future hook needed for emulated sessions.
1282 * @param exception $exception null means normal termination, $exception received when WS call failed
1283 * @return void
f7631e73 1284 */
cc93c7da 1285 protected function session_cleanup($exception=null) {
ad8b5ba2 1286 if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
cc93c7da 1287 // nothing needs to be done, there is no persistent session
1288 } else {
1289 // close emulated session if used
1290 }
f7631e73 1291 }
1292
24350e06 1293 /**
cc93c7da 1294 * Fetches the function description from database,
1295 * verifies user is allowed to use this function and
1296 * loads all paremeters and return descriptions.
1297 * @return void
24350e06 1298 */
cc93c7da 1299 protected function load_function_info() {
1300 global $DB, $USER, $CFG;
40f024c9 1301
cc93c7da 1302 if (empty($this->functionname)) {
1303 throw new invalid_parameter_exception('Missing function name');
1304 }
24350e06 1305
cc93c7da 1306 // function must exist
5593d2dc 1307 $function = external_function_info($this->functionname);
cc93c7da 1308
01482a4a
PS
1309 if ($this->restricted_serviceid) {
1310 $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
1311 $wscond1 = 'AND s.id = :sid1';
1312 $wscond2 = 'AND s.id = :sid2';
1313 } else {
1314 $params = array();
1315 $wscond1 = '';
1316 $wscond2 = '';
1317 }
1318
cc93c7da 1319 // now let's verify access control
b8c5309e 1320
1321 // now make sure the function is listed in at least one service user is allowed to use
1322 // allow access only if:
1323 // 1/ entry in the external_services_users table if required
1324 // 2/ validuntil not reached
1325 // 3/ has capability if specified in service desc
1326 // 4/ iprestriction
1327
1328 $sql = "SELECT s.*, NULL AS iprestriction
1329 FROM {external_services} s
1330 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0 AND sf.functionname = :name1)
1331 WHERE s.enabled = 1 $wscond1
1332
1333 UNION
1334
1335 SELECT s.*, su.iprestriction
1336 FROM {external_services} s
1337 JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1 AND sf.functionname = :name2)
1338 JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
1339 WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
1340 $params = array_merge($params, array('userid'=>$USER->id, 'name1'=>$function->name, 'name2'=>$function->name, 'now'=>time()));
88098133 1341
1342 $rs = $DB->get_recordset_sql($sql, $params);
1343 // now make sure user may access at least one service
1344 $remoteaddr = getremoteaddr();
1345 $allowed = false;
1346 foreach ($rs as $service) {
1347 if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
1348 continue; // cap required, sorry
cc93c7da 1349 }
88098133 1350 if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
1351 continue; // wrong request source ip, sorry
cc93c7da 1352 }
88098133 1353 $allowed = true;
1354 break; // one service is enough, no need to continue
1355 }
1356 $rs->close();
1357 if (!$allowed) {
ccda0134 1358 throw new webservice_access_exception(get_string('accesstofunctionnotallowed', 'webservice', $this->functionname));
cc93c7da 1359 }
9baf6825 1360
cc93c7da 1361 // we have all we need now
1362 $this->function = $function;
1363 }
1364
1365 /**
1366 * Execute previously loaded function using parameters parsed from the request data.
1367 * @return void
1368 */
1369 protected function execute() {
1370 // validate params, this also sorts the params properly, we need the correct order in the next part
1371 $params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
9baf6825 1372
cc93c7da 1373 // execute - yay!
1374 $this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), array_values($params));
9baf6825 1375 }
1376}
1377
1378